package hudson.plugins.ec2; import com.xerox.amazonws.ec2.EC2Exception; import com.xerox.amazonws.ec2.ImageDescription; import com.xerox.amazonws.ec2.InstanceType; import com.xerox.amazonws.ec2.Jec2; import com.xerox.amazonws.ec2.KeyPairInfo; import com.xerox.amazonws.ec2.ReservationDescription.Instance; import hudson.model.Describable; import hudson.model.Descriptor; import hudson.model.Descriptor.FormException; import hudson.model.Hudson; import hudson.model.TaskListener; import hudson.model.Label; import hudson.model.Node; import hudson.Extension; import hudson.Util; import hudson.util.FormValidation; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; import javax.servlet.ServletException; import java.io.IOException; import java.io.PrintStream; import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.Set; /** * Template of {@link EC2Slave} to launch. * * @author Kohsuke Kawaguchi */ public class SlaveTemplate implements Describable<SlaveTemplate> { public final String ami; public final String description; public final String remoteFS; public final InstanceType type; public final String labels; public final String initScript; public final String userData; public final String numExecutors; public final String remoteAdmin; public final String rootCommandPrefix; public final String jvmopts; protected transient EC2Cloud parent; private transient /*almost final*/ Set<Label> labelSet; @DataBoundConstructor public SlaveTemplate(String ami, String remoteFS, InstanceType type, String labelString, String description, String initScript, String userData, String numExecutors, String remoteAdmin, String rootCommandPrefix, String jvmopts) { this.ami = ami; this.remoteFS = remoteFS; this.type = type; this.labels = Util.fixNull(labelString); this.description = description; this.initScript = initScript; this.userData = userData; this.numExecutors = Util.fixNull(numExecutors).trim(); this.remoteAdmin = remoteAdmin; this.rootCommandPrefix = rootCommandPrefix; this.jvmopts = jvmopts; readResolve(); // initialize } public EC2Cloud getParent() { return parent; } public String getLabelString() { return labels; } public String getDisplayName() { return description+" ("+ami+")"; } public int getNumExecutors() { try { return Integer.parseInt(numExecutors); } catch (NumberFormatException e) { return EC2Slave.toNumExecutors(type); } } public String getRemoteAdmin() { return remoteAdmin; } public String getRootCommandPrefix() { return rootCommandPrefix; } /** * Does this contain the given label? * * @param l * can be null to indicate "don't care". */ public boolean containsLabel(Label l) { return l==null || labelSet.contains(l); } /** * Provisions a new EC2 slave. * * @return always non-null. This needs to be then added to {@link Hudson#addNode(Node)}. */ public EC2Slave provision(TaskListener listener) throws EC2Exception, IOException { PrintStream logger = listener.getLogger(); Jec2 ec2 = getParent().connect(); try { logger.println("Launching "+ami); KeyPairInfo keyPair = parent.getPrivateKey().find(ec2); if(keyPair==null) throw new EC2Exception("No matching keypair found on EC2. Is the EC2 private key a valid one?"); Instance inst = ec2.runInstances(ami, 1, 1, Collections.<String>emptyList(), userData, keyPair.getKeyName(), type).getInstances().get(0); return newSlave(inst); } catch (FormException e) { throw new AssertionError(); // we should have discovered all configuration issues upfront } } private EC2Slave newSlave(Instance inst) throws FormException, IOException { return new EC2Slave(inst.getInstanceId(), description, remoteFS, getNumExecutors(), labels, initScript, remoteAdmin, rootCommandPrefix, jvmopts); } /** * Provisions a new EC2 slave based on the currently running instance on EC2, * instead of starting a new one. */ public EC2Slave attach(String instanceId, TaskListener listener) throws EC2Exception, IOException { PrintStream logger = listener.getLogger(); Jec2 ec2 = getParent().connect(); try { logger.println("Attaching to "+instanceId); Instance inst = ec2.describeInstances(Collections.singletonList(instanceId)).get(0).getInstances().get(0); return newSlave(inst); } catch (FormException e) { throw new AssertionError(); // we should have discovered all configuration issues upfront } } /** * Initializes data structure that we don't persist. */ protected Object readResolve() { labelSet = Label.parse(labels); return this; } public Descriptor<SlaveTemplate> getDescriptor() { return Hudson.getInstance().getDescriptor(getClass()); } @Extension public static final class DescriptorImpl extends Descriptor<SlaveTemplate> { public String getDisplayName() { return null; } /** * Since this shares much of the configuration with {@link EC2Computer}, check its help page, too. */ @Override public String getHelpFile(String fieldName) { String p = super.getHelpFile(fieldName); if (p==null) p = Hudson.getInstance().getDescriptor(EC2Slave.class).getHelpFile(fieldName); return p; } /*** * Check that the AMI requested is available in the cloud and can be used. */ public FormValidation doValidateAmi( @QueryParameter String accessId, @QueryParameter String secretKey, @QueryParameter String ec2EndpointUrl, final @QueryParameter String ami) throws IOException, ServletException { Jec2 jec2 = EC2Cloud.connect(accessId, secretKey, EC2Cloud.checkEndPoint(ec2EndpointUrl)); if(jec2!=null) { try { List<String> images = new LinkedList<String>(); images.add(ami); List<String> owners = new LinkedList<String>(); List<String> users = new LinkedList<String>(); users.add("self"); // if we can't run it its not useful. List<ImageDescription> img = jec2.describeImages( images, owners, users, null); if(img==null || img.isEmpty()) // de-registered AMI causes an empty list to be returned. so be defensive // against other possibilities return FormValidation.error("No such AMI, or not usable with this accessId: "+ami); return FormValidation.ok(img.get(0).getImageLocation()+" by "+img.get(0).getImageOwnerId()); } catch (EC2Exception e) { return FormValidation.error(e.getMessage()); } } else return FormValidation.ok(); // can't test } } }