package io.fathom.cloud.install; import io.fathom.cloud.CloudException; import io.fathom.cloud.commands.TypedCmdlet; import io.fathom.cloud.compute.actions.StartInstancesAction; import io.fathom.cloud.compute.services.DerivedMetadata; import io.fathom.cloud.compute.services.SecurityGroups; import io.fathom.cloud.compute.services.SshKeyPairs; import io.fathom.cloud.protobuf.CloudModel.InstanceData; import io.fathom.cloud.protobuf.CloudModel.KeyPairData; import io.fathom.cloud.protobuf.CloudModel.MetadataData; import io.fathom.cloud.protobuf.CloudModel.MetadataEntryData; import io.fathom.cloud.protobuf.CloudModel.ReservationData; import io.fathom.cloud.protobuf.CloudModel.SecurityGroupData; import io.fathom.cloud.protobuf.CloudModel.SecurityGroupRuleData; import io.fathom.cloud.server.auth.Auth; import io.fathom.cloud.server.model.Project; import io.fathom.cloud.services.AuthService; import io.fathom.cloud.services.ImageImports; import io.fathom.cloud.services.ImageService; import io.fathom.cloud.services.ImageService.Image; import io.fathom.cloud.ssh.SshContext; import java.io.IOException; import java.security.PublicKey; import java.util.List; import javax.inject.Inject; import javax.inject.Provider; import org.kohsuke.args4j.Option; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; public class InstanceBootstrapCmdlet extends TypedCmdlet { private static final Logger log = LoggerFactory.getLogger(InstanceBootstrapCmdlet.class); @Option(name = "-u", usage = "username", required = true) public String username; @Option(name = "-p", usage = "password", required = true) public String password; @Option(name = "-url", usage = "image url", required = true) public String imageUrl; // @Option(name = "-ip", usage = "ip", required = true) // public String ip; @Option(name = "-name", usage = "name", required = true) public String name; @Option(name = "-host", usage = "host") public String host; public InstanceBootstrapCmdlet() { super("compute-instance-bootstrap"); } @Inject Provider<StartInstancesAction> startInstancesActionProvider; @Inject AuthService authService; @Inject SshKeyPairs keypairs; @Inject SecurityGroups securityGroups; @Inject ImageService imageService; @Inject ImageImports imageImports; @Inject SshContext sshContext; private Auth auth; private Project project; @Override protected InstanceData run0() throws Exception { authenticate(); KeyPairData keyPair = buildKeypair(); List<Long> securityGroupIds = buildSecurityGroups(); Image image = buildImage(); return startInstance(image, securityGroupIds, keyPair); } private void authenticate() throws CloudException { auth = authService.authenticate(null, username, password); if (auth == null) { throw new IllegalStateException(); } log.info("Authenticated as: {}", username); String projectName = "__system__"; List<Long> projectIds = authService.resolveProjectName(auth, projectName); Long projectId; if (projectIds.isEmpty()) { log.info("Creating project: {}", projectName); projectId = authService.createProject(auth, projectName); } else { if (projectIds.size() != 1) { throw new IllegalStateException("Found multiple projects with name: " + projectName); } projectId = projectIds.get(0); log.info("Found project: {}", projectName); } auth = authService.authenticate(projectId, username, password); if (auth == null) { throw new IllegalStateException("Error authenticating to project"); } log.info("Authenticated to project"); project = auth.getProject(); } private KeyPairData buildKeypair() throws CloudException, IOException { PublicKey sshPublicKey = sshContext.getPublicKey(); String keypairName = "__system__"; KeyPairData keyPair = keypairs.findKeyPair(project, keypairName); if (keyPair == null) { log.info("Creating keypair: {}", keypairName); keypairs.create(project, keypairName, sshPublicKey); } else { log.info("Found keypair: {}", keypairName); } return keyPair; } private List<Long> buildSecurityGroups() throws CloudException { List<Long> securityGroupIds = Lists.newArrayList(); String securityGroupName = "__system__"; SecurityGroupData securityGroup = securityGroups.find(project, securityGroupName); if (securityGroup == null) { log.info("Creating security group: {}", securityGroupName); SecurityGroupData.Builder b = SecurityGroupData.newBuilder(); b.setName(securityGroupName); b.setProjectId(project.getId()); securityGroup = securityGroups.create(project, b); } else { log.info("Found security group: {}", securityGroupName); } if (securityGroup.getRulesCount() == 0) { SecurityGroupRuleData.Builder sgb = SecurityGroupRuleData.newBuilder(); sgb.setFromSecurityGroup(securityGroup.getId()); securityGroups.addRule(auth, project, securityGroup.getId(), sgb); } securityGroupIds.add(securityGroup.getId()); return securityGroupIds; } private Image buildImage() throws Exception, CloudException { ImageImports.Metadata metadata = imageImports.getImageMetadata(imageUrl); List<Image> images = imageService.listImages(project); Image image = null; for (Image i : images) { String imageChecksum = i.getChecksum(); if (imageChecksum.equalsIgnoreCase(metadata.getChecksum())) { image = i; break; } } if (image == null) { log.info("Importing image from: {}", imageUrl); image = imageImports.importImage(project.getId(), imageUrl); } else { log.info("Found image: {}", image.getName()); } return image; } private InstanceData startInstance(Image image, List<Long> securityGroupIds, KeyPairData keyPair) throws CloudException { StartInstancesAction action = startInstancesActionProvider.get(); action.project = project; action.auth = auth; action.minCount = 1; action.maxCount = 1; { ReservationData.Builder reservation = ReservationData.newBuilder(); reservation.setImageId(image.getId()); action.reservationTemplate = reservation.build(); } { InstanceData.Builder instance = InstanceData.newBuilder(); instance.setName(name); if (keyPair != null) { instance.setKeyPair(keyPair); } { MetadataData.Builder metadataBuilder = instance.getMetadataBuilder(); if (host != null) { MetadataEntryData.Builder entryBuilder = metadataBuilder.addEntryBuilder(); entryBuilder.setKey(DerivedMetadata.KEY_DNS_HOST); entryBuilder.setValue(host); } } instance.setImageId(image.getId()); for (Long securityGroupId : securityGroupIds) { instance.addSecurityGroupId(securityGroupId); } action.instanceTemplate = instance.build(); } // action.ip = this.ip; log.info("Starting instance"); StartInstancesAction.Result result = action.go(); if (result.instances.size() != 1) { throw new IllegalStateException(); } return result.instances.get(0); } }