/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package br.com.ingenieux.jenkins.plugins.awsebdeployment; import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalk; import com.amazonaws.services.elasticbeanstalk.AWSElasticBeanstalkClient; import com.amazonaws.services.elasticbeanstalk.model.ApplicationDescription; import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsRequest; import com.amazonaws.services.elasticbeanstalk.model.DescribeEnvironmentsResult; import com.amazonaws.services.s3.AmazonS3Client; import com.cloudbees.jenkins.plugins.awscredentials.AmazonWebServicesCredentials; import com.cloudbees.plugins.credentials.CredentialsNameProvider; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.AbstractIdCredentialsListBoxModel; import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.google.common.base.Function; import com.google.common.collect.Lists; import edu.umd.cs.findbugs.annotations.NonNull; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.model.*; import hudson.security.ACL; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.BuildStepMonitor; import hudson.tasks.Builder; import hudson.util.FormValidation; import jenkins.tasks.SimpleBuildStep; import lombok.Getter; import org.apache.commons.lang.StringUtils; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.annotation.Nonnull; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import java.util.Arrays; import java.util.Collections; import java.util.List; import static java.lang.String.format; import static org.apache.commons.lang.StringUtils.defaultIfBlank; /** * AWS Elastic Beanstalk Deployment */ @SuppressWarnings({"unchecked", "deprecation"}) public class AWSEBDeploymentBuilder extends Builder implements SimpleBuildStep { private static final Logger LOGGER = LoggerFactory.getLogger(AWSEBDeploymentBuilder.class); @Getter private AWSEBDeploymentConfig config; @Override public boolean perform(AbstractBuild<?, ?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { return super.perform(build, launcher, listener); } @DataBoundConstructor public AWSEBDeploymentBuilder(AWSEBDeploymentConfig config) { if (null == config) { config = new AWSEBDeploymentConfig(); } this.config = config; } /* // FIELDLIST CHECK */ @DataBoundSetter public void setSkipEnvironmentUpdates(boolean skipEnvironmentUpdates) { config.setSkipEnvironmentUpdates(skipEnvironmentUpdates); } @DataBoundSetter public void setMaxAttempts(Integer maxAttempts) { config.setMaxAttempts(maxAttempts); } @DataBoundSetter public void setCheckHealth(boolean checkHealth) { config.setCheckHealth(checkHealth); } @DataBoundSetter public void setSleepTime(Integer sleepTime) { config.setSleepTime(sleepTime); } @DataBoundSetter public void setZeroDowntime(boolean zeroDowntime) { config.setZeroDowntime(zeroDowntime); } @DataBoundSetter public void setExcludes(String excludes) { config.setExcludes(excludes); } @DataBoundSetter public void setIncludes(String includes) { config.setIncludes(includes); } @DataBoundSetter public void setRootObject(String rootObject) { config.setRootObject(rootObject); } @DataBoundSetter public void setVersionDescriptionFormat(String versionDescriptionFormat) { config.setVersionDescriptionFormat(versionDescriptionFormat); } @DataBoundSetter public void setVersionLabelFormat(String versionLabelFormat) { config.setVersionLabelFormat(versionLabelFormat); } @DataBoundSetter public void setKeyPrefix(String keyPrefix) { config.setKeyPrefix(keyPrefix); } @DataBoundSetter public void setBucketName(String bucketName) { config.setBucketName(bucketName); } @DataBoundSetter public void setEnvironmentName(String environmentName) { config.setEnvironmentName(environmentName); } @DataBoundSetter public void setApplicationName(String applicationName) { config.setApplicationName(applicationName); } @DataBoundSetter public void setAwsRegion(String awsRegion) { config.setAwsRegion(awsRegion); } @DataBoundSetter public void setCredentialId(String credentialId) { config.setCredentialId(credentialId); } public boolean isSkipEnvironmentUpdates() { return config.isSkipEnvironmentUpdates(); } public Integer getMaxAttempts() { return config.getMaxAttempts(); } public boolean isCheckHealth() { return config.isCheckHealth(); } public Integer getSleepTime() { return config.getSleepTime(); } public boolean isZeroDowntime() { return config.isZeroDowntime(); } public String getExcludes() { return config.getExcludes(); } public String getIncludes() { return config.getIncludes(); } public String getRootObject() { return config.getRootObject(); } public String getVersionDescriptionFormat() { return config.getVersionDescriptionFormat(); } public String getVersionLabelFormat() { return config.getVersionLabelFormat(); } public String getKeyPrefix() { return config.getKeyPrefix(); } public String getBucketName() { return config.getBucketName(); } public String getEnvironmentName() { return config.getEnvironmentName(); } public String getApplicationName() { return config.getApplicationName(); } public String getAwsRegion() { return config.getAwsRegion(); } public String getCredentialId() { return config.getCredentialId(); } @Override public void perform(@Nonnull Run<?, ?> build, @Nonnull FilePath ws, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws IOException { try { new DeployerRunner(build, ws, launcher, listener, this).perform(); } catch (Exception exc) { throw new IOException("Deployment Failure", exc); } } public BuildStepMonitor getRequiredMonitorService() { return BuildStepMonitor.NONE; } // Overridden for better type safety. // If your plugin doesn't really define any property on Descriptor, // you don't have to do this. @SuppressWarnings("rawtypes") @Override public DescriptorImpl getDescriptor() { return (DescriptorImpl) super.getDescriptor(); } @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { public DescriptorImpl() { load(); } public boolean isApplicable(Class<? extends AbstractProject> aClass) { return true; } public String getDisplayName() { return "AWS Elastic Beanstalk"; } public AbstractIdCredentialsListBoxModel<?, ?> doFillCredentialIdItems( @AncestorInPath Item owner) { if (owner == null || !owner.hasPermission(Item.CONFIGURE)) { return new AWSCredentialsListBoxModel(); } List<AmazonWebServicesCredentials> creds = CredentialsProvider .lookupCredentials(AmazonWebServicesCredentials.class, owner, ACL.SYSTEM, Collections.<DomainRequirement>emptyList()); return new AWSCredentialsListBoxModel() .withEmptySelection() .withAll(creds); } public FormValidation doCheckAwsRegion(@QueryParameter String value) { if (value.contains("$")) { return FormValidation.warning("Validation skipped due to parameter usage ('$')"); } if (!value.matches("^\\p{Alpha}{2}-(?:gov-)?\\p{Alpha}{4,}-\\d$")) { return FormValidation.error("Doesn't look like a region, like {place}-{cardinal}-{number}"); } return FormValidation.ok(); } public FormValidation doCheckApplicationName(@QueryParameter String value) { if (value.contains("$")) { return FormValidation.warning("Validation skipped due to parameter usage ('$')"); } int valueLen = value.length(); if (valueLen == 0 || valueLen > 100) { return FormValidation.error("Application Names must have between 1-100 characters"); } return FormValidation.ok(); } public FormValidation doCheckEnvironmentName(@QueryParameter String value, @QueryParameter boolean skipEnvironmentUpdates) { if (skipEnvironmentUpdates) { return FormValidation.ok("Environment Updates Skipped"); } if (value.contains("$")) { return FormValidation.warning("Validation skipped due to parameter usage ('$')"); } if (value.contains(",")) { if (!value.matches("^[\\p{Alpha}[\\p{Alnum}\\-]{0,39}]+(,\\p{Space}*[\\p{Alpha}[\\p{Alnum}\\-]{0,39}]+)*$") || value.endsWith("-")) { return FormValidation.error( "Doesn't look like properly comma separated environment names. Each must be from 4 to 40 characters in length. The name can contain only letters, numbers, and hyphens. It cannot start or end with a hyphen"); } } else { if (!value.matches("^\\p{Alpha}[\\p{Alnum}\\-]{0,39}$") || value.endsWith("-")) { return FormValidation.error( "Doesn't look like an environment name. Must be from 4 to 40 characters in length. The name can contain only letters, numbers, and hyphens. It cannot start or end with a hyphen"); } } return FormValidation.ok(); } public FormValidation doValidateCredentials( @QueryParameter("credentialId") final String credentialId, @QueryParameter final String awsRegion) { for (String value : Arrays.asList(credentialId, awsRegion)) { if (value.contains("$")) { return FormValidation.warning("Validation skipped due to parameter usage ('$')"); } } StringWriter stringWriter = new StringWriter(); PrintWriter w = new PrintWriter(stringWriter, true); try { w.printf("<ul>%n"); w.printf("<li>Building Client (credentialId: '%s', region: '%s')</li>%n", Util.escape(credentialId), Util.escape(awsRegion)); AWSClientFactory factory = AWSClientFactory.getClientFactory(credentialId, awsRegion); AmazonS3Client amazonS3 = factory.getService(AmazonS3Client.class); String s3Endpoint = factory.getEndpointFor(amazonS3); w.printf("<li>Testing Amazon S3 Service (endpoint: %s)</li>%n", Util.escape(s3Endpoint)); w.printf("<li>Buckets Found: %d</li>%n", amazonS3.listBuckets().size()); AWSElasticBeanstalkClient awsElasticBeanstalk = factory.getService(AWSElasticBeanstalkClient.class); String awsEBEndpoint = factory.getEndpointFor(awsElasticBeanstalk); w.printf("<li>Testing AWS Elastic Beanstalk Service (endpoint: %s)</li>%n", Util.escape(awsEBEndpoint)); List<String> applicationList = Lists.transform(awsElasticBeanstalk.describeApplications().getApplications(), new Function<ApplicationDescription, String>() { @Override public String apply(ApplicationDescription input) { return input.getApplicationName(); } }); w.printf("<li>Applications Found: %d (%s)</li>%n", applicationList.size(), Util.escape(StringUtils.join(applicationList, ", "))); w.printf("</ul>%n"); return FormValidation.okWithMarkup(stringWriter.toString()); } catch (Exception exc) { return FormValidation.error(exc, "Failure"); } } public FormValidation doValidateCoordinates(@QueryParameter("credentialId") String credentialId, @QueryParameter("awsRegion") String awsRegion, @QueryParameter("applicationName") String applicationName, @QueryParameter("environmentName") String environmentName, @QueryParameter("skipEnvironmentUpdates") boolean skipEnvironmentUpdates ) throws Exception { if (skipEnvironmentUpdates) { return FormValidation.ok("Skipping Environment Updates"); } for (String value : Arrays .asList(credentialId, awsRegion, applicationName, environmentName)) { if (value.contains("$")) { return FormValidation.warning("Validation skipped due to parameter usage ('$')"); } } List<String> environmentNames = Lists.newArrayList(environmentName.replaceAll("\\s", "").split(",")); AWSClientFactory clientFactory = AWSClientFactory.getClientFactory(credentialId, awsRegion); AWSElasticBeanstalk awsElasticBeanstalk = clientFactory.getService(AWSElasticBeanstalkClient.class); DescribeEnvironmentsResult describeEnvironmentsResult = awsElasticBeanstalk.describeEnvironments( new DescribeEnvironmentsRequest().withApplicationName(applicationName) .withIncludeDeleted(false) .withEnvironmentNames(environmentNames)); // Validate multiple environments & display IDs if (describeEnvironmentsResult.getEnvironments().size() > 1) { List<String> environmentIds = Lists.newArrayList(); Integer size = describeEnvironmentsResult.getEnvironments().size(); for (Integer i = 0; i < size; i++) { environmentIds.add(describeEnvironmentsResult.getEnvironments().get(i).getEnvironmentId()); } return FormValidation.ok("Multiple environments found (environmentIDs: %s)", StringUtils.join(environmentIds, ", ")); } // Validate single environment & display ID if (describeEnvironmentsResult.getEnvironments().size() == 1) { String environmentId = describeEnvironmentsResult.getEnvironments().get(0).getEnvironmentId(); return FormValidation.ok("Environment found (environmentId: %s)", environmentId); } return FormValidation.error("Environment not found"); } public FormValidation doValidateUpload(@QueryParameter("applicationName") String applicationName, @QueryParameter("bucketName") String bucketName, @QueryParameter("keyPrefix") String keyPrefix, @QueryParameter("versionLabelFormat") String versionLabelFormat) { String objectKey = Utils.formatPath("%s/%s-%s.zip", defaultIfBlank(keyPrefix, "<ERROR: MISSING KEY PREFIX>"), defaultIfBlank(applicationName, "<ERROR: MISSING APPLICATION NAME>"), defaultIfBlank(versionLabelFormat, "<ERROR: MISSING VERSION LABEL FORMAT>")); String targetPath = Util.escape(String.format("s3://%s/%s", defaultIfBlank(bucketName, "[default account bucket for region]"), objectKey)); final String resultingMessage = format("Your object will be uploaded to S3 as: <code>%s</code> (<i>note replacements will apply</i>)", targetPath); return FormValidation.okWithMarkup(resultingMessage); } } public static class AWSCredentialsListBoxModel extends AbstractIdCredentialsListBoxModel<AWSCredentialsListBoxModel, AmazonWebServicesCredentials> { /** * {@inheritDoc} */ @NonNull protected String describe(@NonNull AmazonWebServicesCredentials c) { return CredentialsNameProvider.name(c); } } }