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 extends NodeProperty>> 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 extends NodeProperty>> 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.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+