diff --git a/pom.xml b/pom.xml index da170c0..4eebcfd 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,13 @@ jar compile + + commons-lang + commons-lang + 2.6 + jar + compile + diff --git a/src/main/java/jenkins/plugins/ec2slave/EC2ImageLaunchWrapper.java b/src/main/java/jenkins/plugins/ec2slave/EC2ImageLaunchWrapper.java index e6a3e4a..afa9cf7 100644 --- a/src/main/java/jenkins/plugins/ec2slave/EC2ImageLaunchWrapper.java +++ b/src/main/java/jenkins/plugins/ec2slave/EC2ImageLaunchWrapper.java @@ -37,19 +37,28 @@ import java.io.IOException; import java.io.PrintStream; import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; import java.util.logging.Logger; +import org.apache.commons.lang.StringUtils; + import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonServiceException; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.services.ec2.AmazonEC2Client; +import com.amazonaws.services.ec2.model.AvailabilityZone; +import com.amazonaws.services.ec2.model.DescribeAvailabilityZonesResult; import com.amazonaws.services.ec2.model.DescribeInstancesRequest; +import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.InstanceStateName; +import com.amazonaws.services.ec2.model.Placement; import com.amazonaws.services.ec2.model.Reservation; import com.amazonaws.services.ec2.model.RunInstancesRequest; import com.amazonaws.services.ec2.model.RunInstancesResult; +import com.amazonaws.services.ec2.model.SecurityGroup; import com.amazonaws.services.ec2.model.TerminateInstancesRequest; /** @@ -73,7 +82,11 @@ public class EC2ImageLaunchWrapper extends ComputerLauncher { private String ami; private String instanceType; - + + private String securityGroup; + + private String availabilityZone; + private String keypairName; private int retryIntervalSeconds = 10; @@ -93,12 +106,14 @@ public class EC2ImageLaunchWrapper extends ComputerLauncher { private transient boolean preLaunchOk = false; public EC2ImageLaunchWrapper(ComputerConnector computerConnector, String secretKey, String accessKey, String ami, - String instanceType, String keypairName) { + String instanceType, String keypairName, String securityGroup, String availabilityZone) { this.ami = ami; this.computerConnector = computerConnector; // TODO: make a combobox for instance type in the slave config this.instanceType = instanceType; this.keypairName = keypairName; + this.securityGroup = securityGroup; + this.availabilityZone = availabilityZone; AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); ec2 = new AmazonEC2Client(credentials); @@ -109,7 +124,18 @@ public EC2ImageLaunchWrapper(ComputerConnector computerConnector, String secretK // protected String launchInstanceFromImage() { RunInstancesRequest req = new RunInstancesRequest().withImageId(ami).withInstanceType(instanceType) - .withKeyName(keypairName).withSecurityGroups("default").withMinCount(1).withMaxCount(1); + .withKeyName(keypairName).withMinCount(1).withMaxCount(1); + + if(!StringUtils.isEmpty(securityGroup)) { + req.withSecurityGroups(securityGroup); + } else { + req.withSecurityGroups("default"); + } + + if(!StringUtils.isEmpty(availabilityZone)) { + req.setPlacement(new Placement(availabilityZone)); + } + RunInstancesResult res = ec2.runInstances(req); Reservation rvn = res.getReservation(); @@ -136,6 +162,24 @@ public void terminateInstance(PrintStream logger) { ec2.terminateInstances(new TerminateInstancesRequest().withInstanceIds(curInstanceId)); } + + public List getAvailabilityZones() { + DescribeAvailabilityZonesResult res = ec2.describeAvailabilityZones(); + ArrayList ret = new ArrayList(); + for(AvailabilityZone z : res.getAvailabilityZones()) { + ret.add(z.getZoneName()); + } + return ret; + } + + public List getSecurityGroups() { + DescribeSecurityGroupsResult res = ec2.describeSecurityGroups(); + ArrayList ret = new ArrayList(); + for(SecurityGroup s : res.getSecurityGroups()) { + ret.add(s.getGroupName()); + } + return ret; + } // //// diff --git a/src/main/java/jenkins/plugins/ec2slave/EC2ImageSlave.java b/src/main/java/jenkins/plugins/ec2slave/EC2ImageSlave.java index 0b842e4..50e9a6a 100644 --- a/src/main/java/jenkins/plugins/ec2slave/EC2ImageSlave.java +++ b/src/main/java/jenkins/plugins/ec2slave/EC2ImageSlave.java @@ -34,11 +34,22 @@ import hudson.slaves.ComputerLauncher; import hudson.slaves.DumbSlave; import hudson.slaves.RetentionStrategy; +import hudson.util.FormValidation; import java.io.IOException; import java.util.List; +import java.util.logging.Logger; import org.kohsuke.stapler.DataBoundConstructor; +import org.kohsuke.stapler.QueryParameter; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.auth.AWSCredentials; +import com.amazonaws.auth.BasicAWSCredentials; +import com.amazonaws.services.ec2.AmazonEC2Client; +import com.amazonaws.services.ec2.model.DescribeImagesRequest; +import com.amazonaws.services.ec2.model.DescribeImagesResult; +import com.amazonaws.services.ec2.model.Image; /** * The {@link EC2ImageSlave} is a slave in the same way {@link DumbSlave} is, i.e. @@ -56,7 +67,9 @@ public final class EC2ImageSlave extends Slave { private static final long serialVersionUID = -3392496004371742586L; - private String instanceType, accessKey, imageId, secretKey, keypairName; + private static final Logger LOGGER = Logger.getLogger(EC2ImageSlave.class.getName()); + + private String instanceType, accessKey, imageId, secretKey, keypairName, securityGroup, availabilityZone; private transient EC2ImageLaunchWrapper ec2ImageLaunchWrapper; @@ -68,9 +81,10 @@ public final class EC2ImageSlave extends Slave { @DataBoundConstructor public EC2ImageSlave(String secretKey, String accessKey, String imageId, String instanceType, String keypairName, - String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String labelString, - ComputerConnector computerConnector, RetentionStrategy retentionStrategy, - List> nodeProperties) throws FormException, IOException { + String securityGroup, String availabilityZone, String name, String nodeDescription, String remoteFS, + String numExecutors, Mode mode, String labelString, ComputerConnector computerConnector, + RetentionStrategy retentionStrategy, List> nodeProperties) throws FormException, + IOException { super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, null /* null launcher because we create it dynamically in getLauncher */, retentionStrategy, nodeProperties); @@ -95,6 +109,8 @@ public EC2ImageSlave(String secretKey, String accessKey, String imageId, String this.imageId = imageId; this.instanceType = instanceType; this.keypairName = keypairName; + this.securityGroup = securityGroup; + this.availabilityZone = availabilityZone; } @Override @@ -104,7 +120,7 @@ public ComputerLauncher getLauncher() { //hostname already set. This implies that the EC2ImageSlave config will be displaying //Computer *Connector* descriptor stuff rather than *Launcher* ec2ImageLaunchWrapper = new EC2ImageLaunchWrapper(computerConnector, secretKey, accessKey, imageId, instanceType, - keypairName); + keypairName, securityGroup, availabilityZone); setLauncher(ec2ImageLaunchWrapper); @@ -117,15 +133,37 @@ public String getDisplayName() { return "EC2 Image Slave"; } - // public FormValidation doCheckImageId(@QueryParameter String value, - // @QueryParameter("secretKey") String secretKey, - // @QueryParameter("accessKey") String accessKey, - // @QueryParameter("instanceType") String instanceType, - // @QueryParameter("keypairName") String keypairName) { - // if(ec2ImageLaunchWrapper - // if(looksOk(value)) return FormValidation.ok(); - // else return FormValidation.error("There's a problem here"); - // } + public FormValidation doTestConnection(@QueryParameter String accessKey, @QueryParameter String secretKey) { + try { + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + AmazonEC2Client ec2 = new AmazonEC2Client(credentials); + ec2.describeAvailabilityZones(); + return FormValidation.ok("Success"); + } catch (AmazonServiceException e) { + LOGGER.warning("Failed to check EC2 credential: " + e.getMessage()); + return FormValidation.error(e.getMessage()); + } + } + + public FormValidation doValidateAmi(@QueryParameter String accessKey, @QueryParameter String secretKey, + final @QueryParameter String imageId) { + + FormValidation val = doTestConnection(accessKey, secretKey); + if(val.kind == FormValidation.Kind.ERROR) { + return val; + } + + AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); + AmazonEC2Client ec2 = new AmazonEC2Client(credentials); + DescribeImagesResult res = ec2.describeImages(new DescribeImagesRequest().withImageIds(imageId)); + + if (res.getImages().size() > 0 && res.getImages().get(0).getImageId().equals(imageId)) { + Image image = res.getImages().get(0); + return FormValidation.ok("manifest: "+image.getImageLocation() +"\ndescription: " + image.getDescription()); + } else { + return FormValidation.error("No such AMI: " + imageId); + } + } public static List> getComputerConnectorDescriptors() { return Hudson.getInstance().> getDescriptorList( @@ -153,6 +191,14 @@ public String getKeypairName() { return keypairName; } + public String getAvailabilityZone() { + return availabilityZone; + } + + public String getSecurityGroup() { + return securityGroup; + } + public ComputerConnector getComputerConnector() { return computerConnector; } diff --git a/src/main/resources/jenkins/plugins/ec2slave/EC2ImageSlave/configure-entries.jelly b/src/main/resources/jenkins/plugins/ec2slave/EC2ImageSlave/configure-entries.jelly index 0d2976b..ee9abb6 100644 --- a/src/main/resources/jenkins/plugins/ec2slave/EC2ImageSlave/configure-entries.jelly +++ b/src/main/resources/jenkins/plugins/ec2slave/EC2ImageSlave/configure-entries.jelly @@ -29,7 +29,7 @@ THE SOFTWARE. - + @@ -37,10 +37,14 @@ THE SOFTWARE. + + + + @@ -48,11 +52,25 @@ THE SOFTWARE. + + + + + + + + + + + + + + - +