Skip to content

Commit

Permalink
added fields for Availability Zone and Security Group as well as an A…
Browse files Browse the repository at this point in the history
…MI validation button and little more help doc
  • Loading branch information
adphillips committed Apr 19, 2011
1 parent bb4a1f6 commit 90802e9
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 31 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Expand Up @@ -26,6 +26,13 @@
<type>jar</type> <type>jar</type>
<scope>compile</scope> <scope>compile</scope>
</dependency> </dependency>
<dependency>
<groupId>commons-lang</groupId>
<artifactId>commons-lang</artifactId>
<version>2.6</version>
<type>jar</type>
<scope>compile</scope>
</dependency>
</dependencies> </dependencies>


<developers> <developers>
Expand Down
50 changes: 47 additions & 3 deletions src/main/java/jenkins/plugins/ec2slave/EC2ImageLaunchWrapper.java
Expand Up @@ -37,19 +37,28 @@
import java.io.IOException; import java.io.IOException;
import java.io.PrintStream; import java.io.PrintStream;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger; import java.util.logging.Logger;


import org.apache.commons.lang.StringUtils;

import com.amazonaws.AmazonClientException; import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException; import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2Client; 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.DescribeInstancesRequest;
import com.amazonaws.services.ec2.model.DescribeSecurityGroupsResult;
import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.InstanceStateName; 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.Reservation;
import com.amazonaws.services.ec2.model.RunInstancesRequest; import com.amazonaws.services.ec2.model.RunInstancesRequest;
import com.amazonaws.services.ec2.model.RunInstancesResult; import com.amazonaws.services.ec2.model.RunInstancesResult;
import com.amazonaws.services.ec2.model.SecurityGroup;
import com.amazonaws.services.ec2.model.TerminateInstancesRequest; import com.amazonaws.services.ec2.model.TerminateInstancesRequest;


/** /**
Expand All @@ -73,7 +82,11 @@ public class EC2ImageLaunchWrapper extends ComputerLauncher {
private String ami; private String ami;


private String instanceType; private String instanceType;


private String securityGroup;

private String availabilityZone;

private String keypairName; private String keypairName;


private int retryIntervalSeconds = 10; private int retryIntervalSeconds = 10;
Expand All @@ -93,12 +106,14 @@ public class EC2ImageLaunchWrapper extends ComputerLauncher {
private transient boolean preLaunchOk = false; private transient boolean preLaunchOk = false;


public EC2ImageLaunchWrapper(ComputerConnector computerConnector, String secretKey, String accessKey, String ami, 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.ami = ami;
this.computerConnector = computerConnector; this.computerConnector = computerConnector;
// TODO: make a combobox for instance type in the slave config // TODO: make a combobox for instance type in the slave config
this.instanceType = instanceType; this.instanceType = instanceType;
this.keypairName = keypairName; this.keypairName = keypairName;
this.securityGroup = securityGroup;
this.availabilityZone = availabilityZone;


AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey); AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
ec2 = new AmazonEC2Client(credentials); ec2 = new AmazonEC2Client(credentials);
Expand All @@ -109,7 +124,18 @@ public EC2ImageLaunchWrapper(ComputerConnector computerConnector, String secretK
// //
protected String launchInstanceFromImage() { protected String launchInstanceFromImage() {
RunInstancesRequest req = new RunInstancesRequest().withImageId(ami).withInstanceType(instanceType) 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); RunInstancesResult res = ec2.runInstances(req);
Reservation rvn = res.getReservation(); Reservation rvn = res.getReservation();


Expand All @@ -136,6 +162,24 @@ public void terminateInstance(PrintStream logger) {


ec2.terminateInstances(new TerminateInstancesRequest().withInstanceIds(curInstanceId)); ec2.terminateInstances(new TerminateInstancesRequest().withInstanceIds(curInstanceId));
} }

public List<String> getAvailabilityZones() {
DescribeAvailabilityZonesResult res = ec2.describeAvailabilityZones();
ArrayList<String> ret = new ArrayList<String>();
for(AvailabilityZone z : res.getAvailabilityZones()) {
ret.add(z.getZoneName());
}
return ret;
}

public List<String> getSecurityGroups() {
DescribeSecurityGroupsResult res = ec2.describeSecurityGroups();
ArrayList<String> ret = new ArrayList<String>();
for(SecurityGroup s : res.getSecurityGroups()) {
ret.add(s.getGroupName());
}
return ret;
}


// //
//// ////
Expand Down
74 changes: 60 additions & 14 deletions src/main/java/jenkins/plugins/ec2slave/EC2ImageSlave.java
Expand Up @@ -34,11 +34,22 @@
import hudson.slaves.ComputerLauncher; import hudson.slaves.ComputerLauncher;
import hudson.slaves.DumbSlave; import hudson.slaves.DumbSlave;
import hudson.slaves.RetentionStrategy; import hudson.slaves.RetentionStrategy;
import hudson.util.FormValidation;


import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.logging.Logger;


import org.kohsuke.stapler.DataBoundConstructor; 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. * The {@link EC2ImageSlave} is a slave in the same way {@link DumbSlave} is, i.e.
Expand All @@ -56,7 +67,9 @@
public final class EC2ImageSlave extends Slave { public final class EC2ImageSlave extends Slave {
private static final long serialVersionUID = -3392496004371742586L; 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; private transient EC2ImageLaunchWrapper ec2ImageLaunchWrapper;


Expand All @@ -68,9 +81,10 @@ public final class EC2ImageSlave extends Slave {


@DataBoundConstructor @DataBoundConstructor
public EC2ImageSlave(String secretKey, String accessKey, String imageId, String instanceType, String keypairName, public EC2ImageSlave(String secretKey, String accessKey, String imageId, String instanceType, String keypairName,
String name, String nodeDescription, String remoteFS, String numExecutors, Mode mode, String labelString, String securityGroup, String availabilityZone, String name, String nodeDescription, String remoteFS,
ComputerConnector computerConnector, RetentionStrategy retentionStrategy, String numExecutors, Mode mode, String labelString, ComputerConnector computerConnector,
List<? extends NodeProperty<?>> nodeProperties) throws FormException, IOException { RetentionStrategy retentionStrategy, List<? extends NodeProperty<?>> nodeProperties) throws FormException,
IOException {
super(name, nodeDescription, remoteFS, numExecutors, mode, labelString, super(name, nodeDescription, remoteFS, numExecutors, mode, labelString,
null /* null launcher because we create it dynamically in getLauncher */, retentionStrategy, nodeProperties); null /* null launcher because we create it dynamically in getLauncher */, retentionStrategy, nodeProperties);


Expand All @@ -95,6 +109,8 @@ public EC2ImageSlave(String secretKey, String accessKey, String imageId, String
this.imageId = imageId; this.imageId = imageId;
this.instanceType = instanceType; this.instanceType = instanceType;
this.keypairName = keypairName; this.keypairName = keypairName;
this.securityGroup = securityGroup;
this.availabilityZone = availabilityZone;
} }


@Override @Override
Expand All @@ -104,7 +120,7 @@ public ComputerLauncher getLauncher() {
//hostname already set. This implies that the EC2ImageSlave config will be displaying //hostname already set. This implies that the EC2ImageSlave config will be displaying
//Computer *Connector* descriptor stuff rather than *Launcher* //Computer *Connector* descriptor stuff rather than *Launcher*
ec2ImageLaunchWrapper = new EC2ImageLaunchWrapper(computerConnector, secretKey, accessKey, imageId, instanceType, ec2ImageLaunchWrapper = new EC2ImageLaunchWrapper(computerConnector, secretKey, accessKey, imageId, instanceType,
keypairName); keypairName, securityGroup, availabilityZone);


setLauncher(ec2ImageLaunchWrapper); setLauncher(ec2ImageLaunchWrapper);


Expand All @@ -117,15 +133,37 @@ public String getDisplayName() {
return "EC2 Image Slave"; return "EC2 Image Slave";
} }


// public FormValidation doCheckImageId(@QueryParameter String value, public FormValidation doTestConnection(@QueryParameter String accessKey, @QueryParameter String secretKey) {
// @QueryParameter("secretKey") String secretKey, try {
// @QueryParameter("accessKey") String accessKey, AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
// @QueryParameter("instanceType") String instanceType, AmazonEC2Client ec2 = new AmazonEC2Client(credentials);
// @QueryParameter("keypairName") String keypairName) { ec2.describeAvailabilityZones();
// if(ec2ImageLaunchWrapper return FormValidation.ok("Success");
// if(looksOk(value)) return FormValidation.ok(); } catch (AmazonServiceException e) {
// else return FormValidation.error("There's a problem here"); 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<Descriptor<ComputerConnector>> getComputerConnectorDescriptors() { public static List<Descriptor<ComputerConnector>> getComputerConnectorDescriptors() {
return Hudson.getInstance().<ComputerConnector, Descriptor<ComputerConnector>> getDescriptorList( return Hudson.getInstance().<ComputerConnector, Descriptor<ComputerConnector>> getDescriptorList(
Expand Down Expand Up @@ -153,6 +191,14 @@ public String getKeypairName() {
return keypairName; return keypairName;
} }


public String getAvailabilityZone() {
return availabilityZone;
}

public String getSecurityGroup() {
return securityGroup;
}

public ComputerConnector getComputerConnector() { public ComputerConnector getComputerConnector() {
return computerConnector; return computerConnector;
} }
Expand Down
Expand Up @@ -29,30 +29,48 @@ THE SOFTWARE.


<f:section title="${%EC2 Configuration}"> <f:section title="${%EC2 Configuration}">


<f:entry title="${%Access Key}" field="accessKey"> <f:entry title="${%Access Key ID}" field="accessKey">
<f:textbox /> <f:textbox />
</f:entry> </f:entry>


<f:entry title="${%Secret Key}" field="secretKey"> <f:entry title="${%Secret Key}" field="secretKey">
<f:password /> <f:password />
</f:entry> </f:entry>


<!-- f:validateButton title="${%Test Connection}" progress="${%Testing...}" method="testConnection" with="secretKey,accessKey" /-->

<f:entry title="${%AMI}" field="imageId"> <f:entry title="${%AMI}" field="imageId">
<f:textbox /> <f:textbox />
</f:entry> </f:entry>


<f:validateButton title="${%Check AMI}" progress="${%Checking...}" method="validateAmi" with="secretKey,accessKey,imageId" />

<f:entry title="${%Instance Type}" field="instanceType"> <f:entry title="${%Instance Type}" field="instanceType">
<f:textbox /> <f:textbox />
</f:entry> </f:entry>


<f:entry title="${%Keypair Name}" field="keypairName"> <f:entry title="${%Keypair Name}" field="keypairName">
<f:textbox /> <f:textbox />
</f:entry> </f:entry>

<f:advanced>

<f:entry title="${%Security Group}" field="securityGroup">
<f:textbox />
</f:entry>

<f:entry title="${%Availability Zone}" field="availabilityZone">
<f:textbox />
</f:entry>

</f:advanced>

<!-- f:validateButton title="${%Check AMI}" progress="${%Checking...}" method="validateAmi" with="secretKey,accessId,region,ami" /-->
</f:section> </f:section>


<!-- In order to display ComputerConnector choices, we might not be able to simply import dumbSlave's description --> <!-- In order to display ComputerConnector choices, we might not be able to simply import dumbSlave's description -->


<f:section title="${%Slave Process Configuration}"> <f:section title="${%Jenkins Slave Configuration}">


<!-- below we include config that looks just like dumbslave's config-entries.jelly <!-- below we include config that looks just like dumbslave's config-entries.jelly
file but with computerConnector in place of computerLauncher file but with computerConnector in place of computerLauncher
Expand Down
@@ -0,0 +1,6 @@
<div>
Jenkins uses this access key ID and secret access key to interface with Amazon EC2.
If you have already signed up on EC2, you can obtain this from
<a href="http://aws-portal.amazon.com/gp/aws/developer/account/index.html?action=access-key">here</a>.
Otherwise, you need to sign up to Amazon Web Services to get one.
</div>
@@ -0,0 +1,6 @@
<div>
The type of EC2 image to launch. Possible values are t1.micro, m1.small, m1.large, m1.xlarge,
m2.xlarge, m2.2xlarge, m2.4xlarge, c1.medium, c1.xlarge, cc1.4xlarge, or cg1.4xlarge.
Note: not all values apply to a given AMI. For a description of image types see
<a href="http://aws.amazon.com/ec2/instance-types/">here</a>.
</div>
Expand Up @@ -26,15 +26,42 @@
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
import java.io.IOException; import java.io.IOException;
import java.util.List;
import java.util.Properties; import java.util.Properties;


import org.junit.Before;
import org.junit.Ignore; import org.junit.Ignore;
import org.junit.Test; import org.junit.Test;


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.DescribeImagesRequest;
import com.amazonaws.services.ec2.model.DescribeImagesResult;
import com.amazonaws.services.ec2.model.Image;
import com.amazonaws.services.ec2.model.SecurityGroup;

public class EC2ImageLaunchWrapperTest { public class EC2ImageLaunchWrapperTest {


private String credsFile = "../../aws-test/src/AwsCredentials.properties"; private String credsFile = "../aws-test/src/AwsCredentials.properties";
EC2ImageLaunchWrapper launcher;
Properties props = new Properties();

@Before
public void setup() throws FileNotFoundException, IOException {

props.load(new FileReader(credsFile));

String ami = "ami-xxxx";
String instanceType = "t1.micro";
String keypairName = "mykeypair";


launcher = new EC2ImageLaunchWrapper(null,
props.getProperty("secretKey"), props.getProperty("accessKey"),
ami, instanceType, keypairName, null, null);
}

/** /**
* For obvious reasons this test doesn't work out of the box. It is really only practical for * For obvious reasons this test doesn't work out of the box. It is really only practical for
* developer (not CI) testing. To run it, point to your AwsCredentials.properties and set ami, * developer (not CI) testing. To run it, point to your AwsCredentials.properties and set ami,
Expand All @@ -43,19 +70,30 @@ public class EC2ImageLaunchWrapperTest {
@Ignore @Ignore
@Test @Test
public void testImageLauncher() throws FileNotFoundException, IOException, InterruptedException { public void testImageLauncher() throws FileNotFoundException, IOException, InterruptedException {
Properties props = new Properties();
props.load(new FileReader(credsFile));

String ami = "ami-xxxx";
String instanceType = "t1.micro";
String keypairName = "mykeypair";

EC2ImageLaunchWrapper launcher = new EC2ImageLaunchWrapper(null,
props.getProperty("secretKey"), props.getProperty("accessKey"),
ami, instanceType, keypairName);

launcher.preLaunch(System.out); launcher.preLaunch(System.out);
launcher.terminateInstance(System.out); launcher.terminateInstance(System.out);
} }

@Test
public void testGetAvailabilityZones() {
List<String> azs = launcher.getAvailabilityZones();
System.out.println(azs);
}

@Test
public void testGetSecurityGroups() {
List<String> sec = launcher.getSecurityGroups();
System.out.println(sec);
}

@Test
public void testCheckAMI() {
AWSCredentials credentials = new BasicAWSCredentials(props.getProperty("accessKey"), props.getProperty("secretKey"));
AmazonEC2Client ec2 = new AmazonEC2Client(credentials);
DescribeImagesResult res = ec2.describeImages(new DescribeImagesRequest().withImageIds("ami-xxxxxx"));
System.out.println(res.getImages());
Image image = res.getImages().get(0);
System.out.println(image.getImageLocation());
}


} }

0 comments on commit 90802e9

Please sign in to comment.