diff --git a/src/main/java/com/amazonaws/codedeploy/AWSCodeDeployPublisher.java b/src/main/java/com/amazonaws/codedeploy/AWSCodeDeployPublisher.java index 931dcc3..b89153f 100644 --- a/src/main/java/com/amazonaws/codedeploy/AWSCodeDeployPublisher.java +++ b/src/main/java/com/amazonaws/codedeploy/AWSCodeDeployPublisher.java @@ -1,12 +1,12 @@ /* * Copyright 2014 Amazon.com, Inc. or its affiliates. All Rights Reserved. - * + * * Licensed under the Apache License, Version 2.0 (the "License"). * You may not use this file except in compliance with the License. * A copy of the License is located at - * + * * http://aws.amazon.com/apache2.0 - * + * * or in the "license" file accompanying this file. This file 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 @@ -76,7 +76,7 @@ public class AWSCodeDeployPublisher extends Publisher { public static final long DEFAULT_TIMEOUT_SECONDS = 900; public static final long DEFAULT_POLLING_FREQUENCY_SECONDS = 15; public static final String ROLE_SESSION_NAME = "jenkins-codedeploy-plugin"; - public static final Regions[] AVAILABLE_REGIONS = {Regions.US_EAST_1, Regions.US_WEST_2}; + public static final Regions[] AVAILABLE_REGIONS = {Regions.AP_SOUTHEAST_2, Regions.EU_WEST_1, Regions.US_EAST_1, Regions.US_WEST_2}; private final String s3bucket; private final String s3prefix; @@ -91,6 +91,7 @@ public class AWSCodeDeployPublisher extends Publisher { private final String region; private final String includes; private final String excludes; + private final String subdirectory; private final String proxyHost; private final int proxyPort; @@ -118,7 +119,8 @@ public AWSCodeDeployPublisher( String includes, String proxyHost, int proxyPort, - String excludes) { + String excludes, + String subdirectory) { this.externalId = externalId; this.applicationName = applicationName; @@ -127,6 +129,7 @@ public AWSCodeDeployPublisher( this.region = region; this.includes = includes; this.excludes = excludes; + this.subdirectory = subdirectory; this.proxyHost = proxyHost; this.proxyPort = proxyPort; this.credentials = credentials; @@ -178,8 +181,8 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis if ("awsAccessKey".equals(credentials)) { if (StringUtils.isEmpty(this.awsAccessKey) && StringUtils.isEmpty(this.awsSecretKey)) { aws = AWSClients.fromDefaultCredentialChain( - this.region, - this.proxyHost, + this.region, + this.proxyHost, this.proxyPort); } else { aws = AWSClients.fromBasicCredentials( @@ -205,7 +208,7 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis verifyCodeDeployApplication(aws); String projectName = build.getProject().getName(); - RevisionLocation revisionLocation = zipAndUpload(aws, projectName, build.getWorkspace()); + RevisionLocation revisionLocation = zipAndUpload(aws, projectName, getSourceDirectory(build.getWorkspace())); registerRevision(aws, revisionLocation); String deploymentId = createDeployment(aws, revisionLocation); @@ -224,6 +227,30 @@ public boolean perform(AbstractBuild build, Launcher launcher, BuildListener lis return success; } + private FilePath getSourceDirectory(FilePath basePath) throws IOException, InterruptedException { + String subdirectory = this.subdirectory.trim().length() > 0 ? this.subdirectory.trim() : ""; + if (!subdirectory.isEmpty() && !subdirectory.startsWith("/")) { + subdirectory = "/" + subdirectory; + } + FilePath sourcePath = basePath.withSuffix(subdirectory).absolutize(); + File sourceDirectory = new File(sourcePath.getRemote()); + if (!sourceDirectory.isDirectory() || !isSubDirectory(basePath, sourcePath)) { + throw new IllegalArgumentException("Provided path is not a subdirectory of the workspace: " + sourcePath ); + } + return sourcePath; + } + + private boolean isSubDirectory(FilePath parent, FilePath child) { + FilePath parentFolder = child; + while (parentFolder!=null) { + if (parent.equals(parentFolder)) { + return true; + } + parentFolder = child.getParent(); + } + return false; + } + private void verifyCodeDeployApplication(AWSClients aws) throws IllegalArgumentException { // Check that the application exists ListApplicationsResult applications = aws.codedeploy.listApplications(); @@ -243,43 +270,48 @@ private void verifyCodeDeployApplication(AWSClients aws) throws IllegalArgumentE } } - private RevisionLocation zipAndUpload(AWSClients aws, String projectName, FilePath workspace) throws IOException, InterruptedException { + private RevisionLocation zipAndUpload(AWSClients aws, String projectName, FilePath sourceDirectory) throws IOException, InterruptedException { File zipFile = File.createTempFile(projectName + "-", ".zip"); - this.logger.println("Zipping workspace into " + zipFile.getAbsolutePath()); + String key; - workspace.zip( - new FileOutputStream(zipFile), - new DirScanner.Glob(this.includes, this.excludes) - ); + try { + this.logger.println("Zipping files into " + zipFile.getAbsolutePath()); - String key; - if (this.s3prefix.isEmpty()) { - key = zipFile.getName(); - } else { - key = this.s3prefix; - if (this.s3prefix.endsWith("/")) { - key += zipFile.getName(); + sourceDirectory.zip( + new FileOutputStream(zipFile), + new DirScanner.Glob(this.includes, this.excludes) + ); + + if (this.s3prefix.isEmpty()) { + key = zipFile.getName(); } else { - key += "/" + zipFile.getName(); + key = this.s3prefix; + if (this.s3prefix.endsWith("/")) { + key += zipFile.getName(); + } else { + key += "/" + zipFile.getName(); + } } - } - logger.println("Uploading zip to s3://" + this.s3bucket + "/" + key); - PutObjectResult s3result = aws.s3.putObject(this.s3bucket, key, zipFile); + logger.println("Uploading zip to s3://" + this.s3bucket + "/" + key); + PutObjectResult s3result = aws.s3.putObject(this.s3bucket, key, zipFile); - S3Location s3Location = new S3Location(); - s3Location.setBucket(this.s3bucket); - s3Location.setKey(key); - s3Location.setBundleType(BundleType.Zip); - s3Location.setETag(s3result.getETag()); + S3Location s3Location = new S3Location(); + s3Location.setBucket(this.s3bucket); + s3Location.setKey(key); + s3Location.setBundleType(BundleType.Zip); + s3Location.setETag(s3result.getETag()); - RevisionLocation revisionLocation = new RevisionLocation(); - revisionLocation.setRevisionType(RevisionLocationType.S3); - revisionLocation.setS3Location(s3Location); + RevisionLocation revisionLocation = new RevisionLocation(); + revisionLocation.setRevisionType(RevisionLocationType.S3); + revisionLocation.setS3Location(s3Location); - return revisionLocation; + return revisionLocation; + } finally { + zipFile.delete(); + } } private void registerRevision(AWSClients aws, RevisionLocation revisionLocation) { @@ -413,7 +445,7 @@ public FormValidation doCheckName(@QueryParameter String value) } public boolean isApplicable(Class aClass) { - // Indicates that this builder can be used with all kinds of project types + // Indicates that this builder can be used with all kinds of project types return true; } @@ -583,6 +615,10 @@ public String getExcludes() { return excludes; } + public String getSubdirectory() { + return subdirectory; + } + public String getRegion() { return region; } diff --git a/src/main/resources/com/amazonaws/codedeploy/AWSCodeDeployPublisher/config.jelly b/src/main/resources/com/amazonaws/codedeploy/AWSCodeDeployPublisher/config.jelly index 04c860a..0bdd542 100644 --- a/src/main/resources/com/amazonaws/codedeploy/AWSCodeDeployPublisher/config.jelly +++ b/src/main/resources/com/amazonaws/codedeploy/AWSCodeDeployPublisher/config.jelly @@ -15,7 +15,10 @@ - + + + + diff --git a/src/main/resources/com/amazonaws/codedeploy/AWSCodeDeployPublisher/help-subdirectory.html b/src/main/resources/com/amazonaws/codedeploy/AWSCodeDeployPublisher/help-subdirectory.html new file mode 100644 index 0000000..c148bed --- /dev/null +++ b/src/main/resources/com/amazonaws/codedeploy/AWSCodeDeployPublisher/help-subdirectory.html @@ -0,0 +1,5 @@ +
+ A subdirectory inside the workspace to be packed instead of the whole workspace. Remember that the + appspec.yml must be placed at the top of the .zip archive. The excludes and includes will + be applied based on this path. +