/* * The MIT License * * Copyright (c) 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package hudson.plugins.ec2; import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; import com.cloudbees.jenkins.plugins.awscredentials.AWSCredentialsImpl; import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials; import com.cloudbees.plugins.credentials.Credentials; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.CredentialsScope; import com.cloudbees.plugins.credentials.CredentialsStore; import com.cloudbees.plugins.credentials.SystemCredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardListBoxModel; import com.cloudbees.plugins.credentials.domains.Domain; import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.Nullable; import hudson.ProxyConfiguration; import hudson.model.Computer; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.Label; import hudson.model.Node; import hudson.security.ACL; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner.PlannedNode; import hudson.util.FormValidation; import hudson.util.HttpResponses; import hudson.util.ListBoxModel; import hudson.util.Secret; import hudson.util.StreamTaskListener; import java.io.BufferedReader; import java.io.IOException; import java.io.PrintStream; import java.io.PrintWriter; import java.io.StringReader; import java.io.StringWriter; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Proxy; import java.net.URL; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.HashMap; import java.util.UUID; import java.util.concurrent.Callable; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import java.util.logging.SimpleFormatter; import javax.servlet.ServletException; import hudson.model.TaskListener; import jenkins.model.Jenkins; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import org.kohsuke.stapler.StaplerResponse; import com.amazonaws.AmazonClientException; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AWSCredentials; import com.amazonaws.auth.AWSCredentialsProvider; import com.amazonaws.auth.BasicAWSCredentials; import com.amazonaws.auth.InstanceProfileCredentialsProvider; import com.amazonaws.internal.StaticCredentialsProvider; import com.amazonaws.services.ec2.AmazonEC2; import com.amazonaws.services.ec2.AmazonEC2Client; import com.amazonaws.services.ec2.model.CreateKeyPairRequest; import com.amazonaws.services.ec2.model.DescribeSpotInstanceRequestsRequest; import com.amazonaws.services.ec2.model.Filter; import com.amazonaws.services.ec2.model.Instance; import com.amazonaws.services.ec2.model.InstanceStateName; import com.amazonaws.services.ec2.model.InstanceType; import com.amazonaws.services.ec2.model.KeyPair; import com.amazonaws.services.ec2.model.KeyPairInfo; import com.amazonaws.services.ec2.model.Reservation; import com.amazonaws.services.ec2.model.SpotInstanceRequest; import com.amazonaws.services.ec2.model.Tag; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.GeneratePresignedUrlRequest; import hudson.ProxyConfiguration; import hudson.model.Computer; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.Label; import hudson.model.Node; import hudson.slaves.Cloud; import hudson.slaves.NodeProvisioner.PlannedNode; import hudson.util.FormValidation; import hudson.util.HttpResponses; import hudson.util.Secret; import hudson.util.StreamTaskListener; /** * Hudson's view of EC2. * * @author Kohsuke Kawaguchi */ public abstract class EC2Cloud extends Cloud { private static final Logger LOGGER = Logger.getLogger(EC2Cloud.class.getName()); public static final String DEFAULT_EC2_HOST = "us-east-1"; public static final String AWS_URL_HOST = "amazonaws.com"; public static final String EC2_SLAVE_TYPE_SPOT = "spot"; public static final String EC2_SLAVE_TYPE_DEMAND = "demand"; private static final SimpleFormatter sf = new SimpleFormatter(); private final boolean useInstanceProfileForCredentials; /** * Id of the {@link AmazonWebServicesCredentials} used to connect to Amazon ECS */ @CheckForNull private String credentialsId; @CheckForNull @Deprecated private transient String accessId; @CheckForNull @Deprecated private transient Secret secretKey; protected final EC2PrivateKey privateKey; /** * Upper bound on how many instances we may provision. */ public final int instanceCap; private final List<? extends SlaveTemplate> templates; private transient KeyPair usableKeyPair; protected transient AmazonEC2 connection; private static AWSCredentialsProvider awsCredentialsProvider; protected EC2Cloud(String id, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey, String instanceCapStr, List<? extends SlaveTemplate> templates) { super(id); this.useInstanceProfileForCredentials = useInstanceProfileForCredentials; this.credentialsId = credentialsId; this.privateKey = new EC2PrivateKey(privateKey); if (templates == null) { this.templates = Collections.emptyList(); } else { this.templates = templates; } if (instanceCapStr.isEmpty()) { this.instanceCap = Integer.MAX_VALUE; } else { this.instanceCap = Integer.parseInt(instanceCapStr); } readResolve(); // set parents } public abstract URL getEc2EndpointUrl() throws IOException; public abstract URL getS3EndpointUrl() throws IOException; protected Object readResolve() { for (SlaveTemplate t : templates) t.parent = this; if (this.accessId != null && credentialsId == null) { // REPLACE this.accessId and this.secretId by a credential SystemCredentialsProvider systemCredentialsProvider = SystemCredentialsProvider.getInstance(); // ITERATE ON EXISTING CREDS AND DON'T CREATE IF EXIST for (Credentials credentials: systemCredentialsProvider.getCredentials()) { if (credentials instanceof AmazonWebServicesCredentials) { AmazonWebServicesCredentials awsCreds = (AmazonWebServicesCredentials) credentials; AWSCredentials awsCredentials = awsCreds.getCredentials(); if (accessId.equals(awsCredentials.getAWSAccessKeyId()) && Secret.toString(this.secretKey).equals(awsCredentials.getAWSSecretKey())) { this.credentialsId = awsCreds.getId(); this.accessId = null; this.secretKey = null; return this; } } } // CREATE for (CredentialsStore credentialsStore: CredentialsProvider.lookupStores(Jenkins.getInstance())) { if (credentialsStore instanceof SystemCredentialsProvider.StoreImpl) { try { String credsId = UUID.randomUUID().toString(); credentialsStore.addCredentials(Domain.global(), new AWSCredentialsImpl( CredentialsScope.SYSTEM, credsId, this.accessId, this.secretKey.getEncryptedValue(), "EC2 Cloud - " + getDisplayName())); this.credentialsId = credsId; this.accessId = null; this.secretKey = null; return this; } catch (IOException e) { this.credentialsId = null; LOGGER.log(Level.WARNING, "Exception converting legacy configuration to the new credentials API", e); } } } // PROBLEM, GLOBAL STORE NOT FOUND LOGGER.log(Level.WARNING, "EC2 Plugin could not migrate credentials to the Jenkins Global Credentials Store, EC2 Plugin for cloud {0} must be manually reconfigured", getDisplayName()); } return this; } public boolean isUseInstanceProfileForCredentials() { return useInstanceProfileForCredentials; } public String getCredentialsId() { return credentialsId; } public EC2PrivateKey getPrivateKey() { return privateKey; } public String getInstanceCapStr() { if (instanceCap == Integer.MAX_VALUE) return ""; else return String.valueOf(instanceCap); } public List<SlaveTemplate> getTemplates() { return Collections.unmodifiableList(templates); } public SlaveTemplate getTemplate(String template) { for (SlaveTemplate t : templates) { if (t.description.equals(template)) { return t; } } return null; } /** * Gets {@link SlaveTemplate} that has the matching {@link Label}. */ public SlaveTemplate getTemplate(Label label) { for (SlaveTemplate t : templates) { if (t.getMode() == Node.Mode.NORMAL) { if (label == null || label.matches(t.getLabelSet())) { return t; } } else if (t.getMode() == Node.Mode.EXCLUSIVE) { if (label != null && label.matches(t.getLabelSet())) { return t; } } } return null; } /** * Gets the {@link KeyPairInfo} used for the launch. */ public synchronized KeyPair getKeyPair() throws AmazonClientException, IOException { if (usableKeyPair == null) usableKeyPair = privateKey.find(connect()); return usableKeyPair; } /** * Debug command to attach to a running instance. */ public void doAttach(StaplerRequest req, StaplerResponse rsp, @QueryParameter String id) throws ServletException, IOException, AmazonClientException { checkPermission(PROVISION); SlaveTemplate t = getTemplates().get(0); StringWriter sw = new StringWriter(); StreamTaskListener listener = new StreamTaskListener(sw); EC2AbstractSlave node = t.attach(id, listener); Jenkins.getInstance().addNode(node); rsp.sendRedirect2(req.getContextPath() + "/computer/" + node.getNodeName()); } public HttpResponse doProvision(@QueryParameter String template) throws ServletException, IOException { checkPermission(PROVISION); if (template == null) { throw HttpResponses.error(SC_BAD_REQUEST, "The 'template' query parameter is missing"); } SlaveTemplate t = getTemplate(template); if (t == null) { throw HttpResponses.error(SC_BAD_REQUEST, "No such template: " + template); } try { EC2AbstractSlave node = getNewOrExistingAvailableSlave(t, null, true); if (node == null) throw HttpResponses.error(SC_BAD_REQUEST, "Cloud or AMI instance cap would be exceeded for: " + template); Jenkins.getInstance().addNode(node); return HttpResponses.redirectViaContextPath("/computer/" + node.getNodeName()); } catch (AmazonClientException e) { throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR, e); } } /** * Counts the number of instances in EC2 that can be used with the specified image and a template. Also removes any * nodes associated with canceled requests. * * @param template If left null, then all instances are counted. */ private int countCurrentEC2Slaves(SlaveTemplate template) throws AmazonClientException { LOGGER.log(Level.FINE, "Counting current slaves: " + (template != null ? (" AMI: " + template.getAmi()) : " All AMIS")); int n = 0; Set<String> instanceIds = new HashSet<String>(); String description = template != null ? template.description : null; for (Reservation r : connect().describeInstances().getReservations()) { for (Instance i : r.getInstances()) { if (isEc2ProvisionedAmiSlave(i.getTags(), description) && (template == null || template.getAmi().equals(i.getImageId()))) { InstanceStateName stateName = InstanceStateName.fromValue(i.getState().getName()); if (stateName != InstanceStateName.Terminated && stateName != InstanceStateName.ShuttingDown) { LOGGER.log(Level.FINE, "Existing instance found: " + i.getInstanceId() + " AMI: " + i.getImageId() + " Template: " + description); n++; instanceIds.add(i.getInstanceId()); } } } } List<SpotInstanceRequest> sirs = null; List<Filter> filters = new ArrayList<Filter>(); List<String> values; if (template != null) { values = new ArrayList<String>(); values.add(template.getAmi()); filters.add(new Filter("launch.image-id", values)); } values = new ArrayList<String>(); values.add(EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE); filters.add(new Filter("tag-key", values)); DescribeSpotInstanceRequestsRequest dsir = new DescribeSpotInstanceRequestsRequest().withFilters(filters); try { sirs = connect().describeSpotInstanceRequests(dsir).getSpotInstanceRequests(); } catch (Exception ex) { // Some ec2 implementations don't implement spot requests (Eucalyptus) LOGGER.log(Level.FINEST, "Describe spot instance requests failed", ex); } Set<SpotInstanceRequest> sirSet = new HashSet(); if (sirs != null) { for (SpotInstanceRequest sir : sirs) { sirSet.add(sir); if (sir.getState().equals("open") || sir.getState().equals("active")) { if (instanceIds.contains(sir.getInstanceId())) continue; LOGGER.log(Level.FINE, "Spot instance request found: " + sir.getSpotInstanceRequestId() + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus()); n++; instanceIds.add(sir.getInstanceId()); } else { // Canceled or otherwise dead for (Node node : Jenkins.getInstance().getNodes()) { try { if (!(node instanceof EC2SpotSlave)) continue; EC2SpotSlave ec2Slave = (EC2SpotSlave) node; if (ec2Slave.getSpotInstanceRequestId().equals(sir.getSpotInstanceRequestId())) { LOGGER.log(Level.INFO, "Removing dead request: " + sir.getSpotInstanceRequestId() + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus()); Jenkins.getInstance().removeNode(node); break; } } catch (IOException e) { LOGGER.log(Level.WARNING, "Failed to remove node for dead request: " + sir.getSpotInstanceRequestId() + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus(), e); } } } } } // Count nodes where the spot request does not yet exist (sometimes it takes time for the request to appear // in the EC2 API) for (Node node : Jenkins.getInstance().getNodes()) { if (!(node instanceof EC2SpotSlave)) continue; EC2SpotSlave ec2Slave = (EC2SpotSlave) node; SpotInstanceRequest sir = ec2Slave.getSpotRequest(); if (sir == null) { LOGGER.log(Level.FINE, "Found spot node without request: " + ec2Slave.getSpotInstanceRequestId()); n++; continue; } if (sirSet.contains(sir)) continue; sirSet.add(sir); if (sir.getState().equals("open") || sir.getState().equals("active")) { if (template != null) { List<Tag> instanceTags = sir.getTags(); for (Tag tag : instanceTags) { if (StringUtils.equals(tag.getKey(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE) && StringUtils.equals(tag.getValue(), getSlaveTypeTagValue(EC2_SLAVE_TYPE_SPOT, template.description)) && sir.getLaunchSpecification().getImageId().equals(template.getAmi())) { if (instanceIds.contains(sir.getInstanceId())) continue; LOGGER.log(Level.FINE, "Spot instance request found (from node): " + sir.getSpotInstanceRequestId() + " AMI: " + sir.getInstanceId() + " state: " + sir.getState() + " status: " + sir.getStatus()); n++; instanceIds.add(sir.getInstanceId()); } } } } } return n; } private boolean isEc2ProvisionedAmiSlave(List<Tag> tags, String description) { for (Tag tag : tags) { if (StringUtils.equals(tag.getKey(), EC2Tag.TAG_NAME_JENKINS_SLAVE_TYPE)) { if (description == null) { return true; } else if (StringUtils.equals(tag.getValue(), EC2Cloud.EC2_SLAVE_TYPE_DEMAND) || StringUtils.equals(tag.getValue(), EC2Cloud.EC2_SLAVE_TYPE_SPOT)) { // To handle cases where description is null and also upgrade cases for existing slave nodes. return true; } else if (StringUtils.equals(tag.getValue(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_DEMAND, description)) || StringUtils.equals(tag.getValue(), getSlaveTypeTagValue(EC2Cloud.EC2_SLAVE_TYPE_SPOT, description))) { return true; } else { return false; } } } return false; } /** * Returns the maximum number of possible slaves that can be created. */ private int getPossibleNewSlavesCount(SlaveTemplate template) throws AmazonClientException { int estimatedTotalSlaves = countCurrentEC2Slaves(null); int estimatedAmiSlaves = countCurrentEC2Slaves(template); int availableTotalSlaves = instanceCap - estimatedTotalSlaves; int availableAmiSlaves = template.getInstanceCap() - estimatedAmiSlaves; LOGGER.log(Level.FINE, "Available Total Slaves: " + availableTotalSlaves + " Available AMI slaves: " + availableAmiSlaves + " AMI: " + template.getAmi() + " TemplateDesc: " + template.description); return Math.min(availableAmiSlaves, availableTotalSlaves); } /** * Obtains a slave whose AMI matches the AMI of the given template, and that also has requiredLabel (if requiredLabel is non-null) * forceCreateNew specifies that the creation of a new slave is required. Otherwise, an existing matching slave may be re-used */ private synchronized EC2AbstractSlave getNewOrExistingAvailableSlave(SlaveTemplate template, Label requiredLabel, boolean forceCreateNew) { /* * Note this is synchronized between counting the instances and then allocating the node. Once the node is * allocated, we don't look at that instance as available for provisioning. */ int possibleSlavesCount = getPossibleNewSlavesCount(template); if (possibleSlavesCount < 0) { LOGGER.log(Level.INFO, "Cannot provision - no capacity for instances: " + possibleSlavesCount); return null; } try { EnumSet<SlaveTemplate.ProvisionOptions> provisionOptions = EnumSet.noneOf(SlaveTemplate.ProvisionOptions.class); if (forceCreateNew) provisionOptions = EnumSet.of(SlaveTemplate.ProvisionOptions.FORCE_CREATE); else if (possibleSlavesCount > 0) provisionOptions = EnumSet.of(SlaveTemplate.ProvisionOptions.ALLOW_CREATE); return template.provision(StreamTaskListener.fromStdout(), requiredLabel, provisionOptions); } catch (IOException e) { LOGGER.log(Level.WARNING, "Exception during provisioning", e); return null; } } @Override public Collection<PlannedNode> provision(Label label, int excessWorkload) { try { List<PlannedNode> r = new ArrayList<PlannedNode>(); final SlaveTemplate t = getTemplate(label); LOGGER.log(Level.INFO, "Attempting to provision slave from template " + t + " needed by excess workload of " + excessWorkload + " units of label '" + label + "'"); if (label == null) { LOGGER.log(Level.WARNING, String.format("Label is null - can't calculate how many executors slave will have. Using %s number of executors", t.getNumExecutors())); } while (excessWorkload > 0) { final EC2AbstractSlave slave = getNewOrExistingAvailableSlave(t, label, false); // Returned null if a new node could not be created if (slave == null) break; LOGGER.log(Level.INFO, String.format("We have now %s computers", Jenkins.getInstance().getComputers().length)); Jenkins.getInstance().addNode(slave); LOGGER.log(Level.INFO, String.format("Added node named: %s, We have now %s computers", slave.getNodeName(), Jenkins.getInstance().getComputers().length)); r.add(new PlannedNode(t.getDisplayName(), Computer.threadPoolForRemoting.submit(new Callable<Node>() { public Node call() throws Exception { long startTime = System.currentTimeMillis(); // fetch starting time while ((System.currentTimeMillis() - startTime) < slave.launchTimeout * 1000) { return tryToCallSlave(slave, t); } LOGGER.log(Level.WARNING, "Expected - Instance - failed to connect within launch timeout"); return tryToCallSlave(slave, t); } }), t.getNumExecutors())); excessWorkload -= t.getNumExecutors(); } LOGGER.log(Level.INFO, "Attempting provision - finished, excess workload: " + excessWorkload); return r; } catch (AmazonClientException e) { LOGGER.log(Level.WARNING, "Exception during provisioning", e); return Collections.emptyList(); } catch (IOException e) { LOGGER.log(Level.WARNING, "Exception during provisioning", e); return Collections.emptyList(); } } private EC2AbstractSlave tryToCallSlave(EC2AbstractSlave slave, SlaveTemplate template) { try { slave.toComputer().connect(false).get(); } catch (Exception e) { if (template.spotConfig != null) { if(StringUtils.isNotEmpty(slave.getInstanceId()) && slave.isConnected) { LOGGER.log(Level.INFO, String.format("Instance id: %s for node: %s is connected now.", slave.getInstanceId(), slave.getNodeName())); return slave; } } } return slave; } @Override public boolean canProvision(Label label) { return getTemplate(label) != null; } private AWSCredentialsProvider createCredentialsProvider() { return createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); } public static String getSlaveTypeTagValue(String slaveType, String templateDescription) { return templateDescription != null ? slaveType + "_" + templateDescription : slaveType; } public static AWSCredentialsProvider createCredentialsProvider(final boolean useInstanceProfileForCredentials, final String credentialsId) { if (useInstanceProfileForCredentials) { return new InstanceProfileCredentialsProvider(); } else if (StringUtils.isBlank(credentialsId)) { return new DefaultAWSCredentialsProviderChain(); } else { AmazonWebServicesCredentials credentials = getCredentials(credentialsId); return new StaticCredentialsProvider(credentials.getCredentials()); } } @CheckForNull private static AmazonWebServicesCredentials getCredentials(@Nullable String credentialsId) { if (StringUtils.isBlank(credentialsId)) { return null; } return (AmazonWebServicesCredentials) CredentialsMatchers.firstOrNull( CredentialsProvider.lookupCredentials(AmazonWebServicesCredentials.class, Jenkins.getInstance(), ACL.SYSTEM, Collections.EMPTY_LIST), CredentialsMatchers.withId(credentialsId)); } /** * Connects to EC2 and returns {@link AmazonEC2}, which can then be used to communicate with EC2. */ public synchronized AmazonEC2 connect() throws AmazonClientException { try { if (connection == null) { connection = connect(createCredentialsProvider(), getEc2EndpointUrl()); } return connection; } catch (IOException e) { throw new AmazonClientException("Failed to retrieve the endpoint", e); } } /*** * Connect to an EC2 instance. * * @return {@link AmazonEC2} client */ public synchronized static AmazonEC2 connect(AWSCredentialsProvider credentialsProvider, URL endpoint) { awsCredentialsProvider = credentialsProvider; ClientConfiguration config = new ClientConfiguration(); config.setMaxErrorRetry(16); // Default retry limit (3) is low and often // cause problems. Raise it a bit. // See: https://issues.jenkins-ci.org/browse/JENKINS-26800 config.setSignerOverride("AWS4SignerType"); ProxyConfiguration proxyConfig = Jenkins.getInstance().proxy; Proxy proxy = proxyConfig == null ? Proxy.NO_PROXY : proxyConfig.createProxy(endpoint.getHost()); if (!proxy.equals(Proxy.NO_PROXY) && proxy.address() instanceof InetSocketAddress) { InetSocketAddress address = (InetSocketAddress) proxy.address(); config.setProxyHost(address.getHostName()); config.setProxyPort(address.getPort()); if (null != proxyConfig.getUserName()) { config.setProxyUsername(proxyConfig.getUserName()); config.setProxyPassword(proxyConfig.getPassword()); } } AmazonEC2 client = new AmazonEC2Client(credentialsProvider, config); client.setEndpoint(endpoint.toString()); return client; } /*** * Convert a configured hostname like 'us-east-1' to a FQDN or ip address */ public static String convertHostName(String ec2HostName) { if (ec2HostName == null || ec2HostName.length() == 0) ec2HostName = DEFAULT_EC2_HOST; if (!ec2HostName.contains(".")) ec2HostName = "ec2." + ec2HostName + "." + AWS_URL_HOST; return ec2HostName; } /*** * Convert a user entered string into a port number "" -> -1 to indicate default based on SSL setting */ public static Integer convertPort(String ec2Port) { if (ec2Port == null || ec2Port.length() == 0) return -1; return Integer.parseInt(ec2Port); } /** * Computes the presigned URL for the given S3 resource. * * @param path String like "/bucketName/folder/folder/abc.txt" that represents the resource to request. */ public URL buildPresignedURL(String path) throws AmazonClientException { AWSCredentials credentials = awsCredentialsProvider.getCredentials(); long expires = System.currentTimeMillis() + 60 * 60 * 1000; GeneratePresignedUrlRequest request = new GeneratePresignedUrlRequest(path, credentials.getAWSSecretKey()); request.setExpiration(new Date(expires)); AmazonS3 s3 = new AmazonS3Client(credentials); return s3.generatePresignedUrl(request); } /* Parse a url or return a sensible error */ public static URL checkEndPoint(String url) throws FormValidation { try { return new URL(url); } catch (MalformedURLException ex) { throw FormValidation.error("Endpoint URL is not a valid URL"); } } public static abstract class DescriptorImpl extends Descriptor<Cloud> { public InstanceType[] getInstanceTypes() { return InstanceType.values(); } public FormValidation doCheckUseInstanceProfileForCredentials(@QueryParameter boolean value) { if (value) { try { new InstanceProfileCredentialsProvider().getCredentials(); } catch (AmazonClientException e) { return FormValidation.error(Messages.EC2Cloud_FailedToObtainCredentailsFromEC2(), e.getMessage()); } } return FormValidation.ok(); } public FormValidation doCheckPrivateKey(@QueryParameter String value) throws IOException, ServletException { boolean hasStart = false, hasEnd = false; BufferedReader br = new BufferedReader(new StringReader(value)); String line; while ((line = br.readLine()) != null) { if (line.equals("-----BEGIN RSA PRIVATE KEY-----")) hasStart = true; if (line.equals("-----END RSA PRIVATE KEY-----")) hasEnd = true; } if (!hasStart) return FormValidation.error("This doesn't look like a private key at all"); if (!hasEnd) return FormValidation .error("The private key is missing the trailing 'END RSA PRIVATE KEY' marker. Copy&paste error?"); return FormValidation.ok(); } protected FormValidation doTestConnection(URL ec2endpoint, boolean useInstanceProfileForCredentials, String credentialsId, String privateKey) throws IOException, ServletException { try { AWSCredentialsProvider credentialsProvider = createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); AmazonEC2 ec2 = connect(credentialsProvider, ec2endpoint); ec2.describeInstances(); if (privateKey == null) return FormValidation.error("Private key is not specified. Click 'Generate Key' to generate one."); if (privateKey.trim().length() > 0) { // check if this key exists EC2PrivateKey pk = new EC2PrivateKey(privateKey); if (pk.find(ec2) == null) return FormValidation .error("The EC2 key pair private key isn't registered to this EC2 region (fingerprint is " + pk.getFingerprint() + ")"); } return FormValidation.ok(Messages.EC2Cloud_Success()); } catch (AmazonClientException e) { LOGGER.log(Level.WARNING, "Failed to check EC2 credential", e); return FormValidation.error(e.getMessage()); } } public FormValidation doGenerateKey(StaplerResponse rsp, URL ec2EndpointUrl, boolean useInstanceProfileForCredentials, String credentialsId) throws IOException, ServletException { try { AWSCredentialsProvider credentialsProvider = createCredentialsProvider(useInstanceProfileForCredentials, credentialsId); AmazonEC2 ec2 = connect(credentialsProvider, ec2EndpointUrl); List<KeyPairInfo> existingKeys = ec2.describeKeyPairs().getKeyPairs(); int n = 0; while (true) { boolean found = false; for (KeyPairInfo k : existingKeys) { if (k.getKeyName().equals("hudson-" + n)) found = true; } if (!found) break; n++; } CreateKeyPairRequest request = new CreateKeyPairRequest("hudson-" + n); KeyPair key = ec2.createKeyPair(request).getKeyPair(); rsp.addHeader("script", "findPreviousFormItem(button,'privateKey').value='" + key.getKeyMaterial().replace("\n", "\\n") + "'"); return FormValidation.ok(Messages.EC2Cloud_Success()); } catch (AmazonClientException e) { LOGGER.log(Level.WARNING, "Failed to check EC2 credential", e); return FormValidation.error(e.getMessage()); } } public ListBoxModel doFillCredentialsIdItems() { return new StandardListBoxModel() .withEmptySelection() .withMatching( CredentialsMatchers.always(), CredentialsProvider.lookupCredentials(AmazonWebServicesCredentials.class, Jenkins.getInstance(), ACL.SYSTEM, Collections.EMPTY_LIST)); } } public static void log(Logger logger, Level level, TaskListener listener, String message) { log(logger, level, listener, message, null); } public static void log(Logger logger, Level level, TaskListener listener, String message, Throwable exception) { logger.log(level, message, exception); if (listener != null) { if (exception != null) message += " Exception: " + exception; LogRecord lr = new LogRecord(level, message); PrintStream printStream = listener.getLogger(); printStream.print(sf.format(lr)); } } }