├── Dojofile ├── e2e └── terraform │ ├── Dojofile │ └── s3.tf ├── Dojofile.compose ├── images ├── artifact_store.png ├── gocd_s3_fetch_options.png └── build_and_publish_image_artifacts.png ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── test │ └── java │ │ └── diogomrol │ │ └── gocd │ │ └── s3 │ │ └── artifact │ │ └── plugin │ │ ├── IntegrationTests.java │ │ ├── model │ │ ├── ArtifactPlanConfigTypeAdapterTest.java │ │ ├── PublishArtifactRequestTest.java │ │ ├── AntDirectoryScannerTest.java │ │ └── EnvironmentVariableResolverTest.java │ │ ├── executors │ │ ├── ValidateFetchArtifactConfigExecutorTest.java │ │ ├── GetFetchArtifactMetadataExecutorTest.java │ │ ├── GetFetchArtifactViewExecutorTest.java │ │ ├── GetPluginIconExecutorTest.java │ │ ├── GetPublishArtifactRequestMetadataExecutorTest.java │ │ ├── GetArtifactStoreViewExecutorTest.java │ │ ├── GetPublishArtifactViewExecutorTest.java │ │ ├── GetArtifactStoreConfigMetadataExecutorTest.java │ │ ├── ViewTest.java │ │ ├── ValidatePublishArtifactConfigExecutorTest.java │ │ ├── ValidateArtifactStoreConfigExecutorExecutorTest.java │ │ ├── PublishArtifactExecutorIntegrationTest.java │ │ └── PublishAndFetchIntegrationTest.java │ │ └── ConsoleLoggerTest.java └── main │ ├── java │ └── diogomrol │ │ └── gocd │ │ └── s3 │ │ └── artifact │ │ └── plugin │ │ ├── model │ │ ├── UnresolvedPropertyException.java │ │ ├── Capabilities.java │ │ ├── FetchArtifact.java │ │ ├── ArtifactPlanConfig.java │ │ ├── EnvironmentVariableResolver.java │ │ ├── AntDirectoryScanner.java │ │ ├── PublishArtifactResponse.java │ │ ├── FetchArtifactRequest.java │ │ ├── S3FileArtifactPlanConfig.java │ │ ├── FetchArtifactConfig.java │ │ ├── ArtifactStore.java │ │ ├── ArtifactPlanConfigTypeAdapter.java │ │ ├── ArtifactPlan.java │ │ ├── ArtifactInfo.java │ │ ├── PublishArtifactRequest.java │ │ └── ArtifactStoreConfig.java │ │ ├── annotation │ │ ├── FieldMetadataTypeAdapter.java │ │ ├── FieldMetadata.java │ │ ├── ConfigMetadata.java │ │ ├── MetadataHelper.java │ │ ├── FieldType.java │ │ ├── ValidationError.java │ │ ├── ValidationResult.java │ │ └── Validatable.java │ │ ├── executors │ │ ├── RequestExecutor.java │ │ ├── GetFetchArtifactMetadataExecutor.java │ │ ├── ValidateFetchArtifactConfigExecutor.java │ │ ├── GetCapabilitiesExecutor.java │ │ ├── GetArtifactStoreViewExecutor.java │ │ ├── GetFetchArtifactViewExecutor.java │ │ ├── GetPublishArtifactViewExecutor.java │ │ ├── GetPublishArtifactConfigMetadataExecutor.java │ │ ├── GetArtifactStoreConfigMetadataExecutor.java │ │ ├── ValidateArtifactStoreConfigExecutor.java │ │ ├── GetPluginIconExecutor.java │ │ ├── ValidatePublishArtifactConfigExecutor.java │ │ ├── PublishArtifactExecutor.java │ │ └── FetchArtifactExecutor.java │ │ ├── Constants.java │ │ ├── S3ClientFactory.java │ │ ├── Request.java │ │ ├── ConsoleLogger.java │ │ ├── utils │ │ └── Util.java │ │ └── S3ArtifactPlugin.java │ ├── resource-templates │ ├── plugin.properties │ └── plugin.xml │ └── resources │ ├── plugin-icon.svg │ ├── publish-artifact.template.html │ ├── fetch-artifact.template.html │ ├── plugin-settings.template.html │ └── artifact-store.template.html ├── .travis.yml ├── .gitignore ├── docker-compose.yml ├── settings.gradle ├── CHANGELOG.md ├── ci.gocd.yaml ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── gradlew ├── tasks └── LICENSE /Dojofile: -------------------------------------------------------------------------------- 1 | DOJO_DOCKER_IMAGE=kudulab/openjdk-dojo:1.2.0 2 | -------------------------------------------------------------------------------- /e2e/terraform/Dojofile: -------------------------------------------------------------------------------- 1 | DOJO_DOCKER_IMAGE="kudulab/terraform-dojo:1.2.1" 2 | -------------------------------------------------------------------------------- /Dojofile.compose: -------------------------------------------------------------------------------- 1 | DOJO_DRIVER=docker-compose 2 | DOJO_DOCKER_IMAGE=kudulab/openjdk-dojo:1.2.0 3 | -------------------------------------------------------------------------------- /images/artifact_store.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Diogomrol/gocd-s3-artifact-plugin/HEAD/images/artifact_store.png -------------------------------------------------------------------------------- /images/gocd_s3_fetch_options.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Diogomrol/gocd-s3-artifact-plugin/HEAD/images/gocd_s3_fetch_options.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Diogomrol/gocd-s3-artifact-plugin/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /images/build_and_publish_image_artifacts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Diogomrol/gocd-s3-artifact-plugin/HEAD/images/build_and_publish_image_artifacts.png -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/IntegrationTests.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin; 2 | 3 | //category marker interface 4 | public interface IntegrationTests { 5 | } 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: minimal 3 | services: 4 | - docker 5 | 6 | env: 7 | - DOJO_VERSION=0.5.0 8 | 9 | before_install: 10 | - sudo rm -f /usr/bin/dojo 11 | - wget -O dojo https://github.com/ai-traders/dojo/releases/download/${DOJO_VERSION}/dojo_linux_amd64 12 | - sudo mv dojo /usr/local/bin 13 | - sudo chmod +x /usr/local/bin/dojo 14 | 15 | script: 16 | - dojo "./gradlew test assemble" 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij files 2 | .idea/ 3 | 4 | # gradle's output dir 5 | build/ 6 | 7 | # the gradle temp dir 8 | .gradle 9 | 10 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 11 | !gradle-wrapper.jar 12 | out 13 | docker-registry-artifact-plugin.iml 14 | src/main/resources-generated/ 15 | 16 | /e2e/terraform/tf-out.json 17 | .terraform 18 | /docker-compose.yml.dojo 19 | /e2e/terraform/kudu_deployment.tfplan 20 | /ops/ 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2.2' 2 | services: 3 | default: 4 | links: 5 | - gocd:gocd 6 | depends_on: 7 | - agent 8 | gocd: 9 | image: gocd/gocd-server:v19.3.0 10 | ports: 11 | - 8153:8153 12 | volumes: 13 | - ${DOJO_WORK_OUTER}/build/libs:/godata/plugins/external 14 | agent: 15 | image: gocd/gocd-agent-alpine-3.9:v19.3.0 16 | links: 17 | - gocd:gocd 18 | environment: 19 | - GO_SERVER_URL=https://gocd:8154/go 20 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/UnresolvedPropertyException.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | public class UnresolvedPropertyException extends Exception { 4 | 5 | private final String partiallyResolvedTag; 6 | 7 | public UnresolvedPropertyException(String partiallyResolvedTag, String propertyName) { 8 | super(String.format("Failed to resolve one or more variables in %s: %s", propertyName, partiallyResolvedTag)); 9 | this.partiallyResolvedTag = partiallyResolvedTag; 10 | } 11 | 12 | public String getPartiallyResolvedTag() { 13 | return partiallyResolvedTag; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/FieldMetadataTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.annotation; 2 | 3 | import com.google.gson.*; 4 | 5 | import java.lang.reflect.Type; 6 | 7 | public class FieldMetadataTypeAdapter implements JsonSerializer { 8 | 9 | @Override 10 | public JsonElement serialize(FieldMetadata src, Type typeOfSrc, JsonSerializationContext context) { 11 | JsonObject jsonObject = new JsonObject(); 12 | jsonObject.add("required", new JsonPrimitive(src.required())); 13 | jsonObject.add("secure", new JsonPrimitive(src.secure())); 14 | return jsonObject; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/resource-templates/plugin.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2017 ThoughtWorks, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | name=${name} 17 | pluginId=${id} 18 | version=${version} 19 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Diogo Oliveira 3 | * Copyright 2017 ThoughtWorks, Inc. 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | rootProject.name = 's3-artifact-plugin' 19 | -------------------------------------------------------------------------------- /src/main/resources/plugin-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | Created by potrace 1.15, written by Peter Selinger 2001-2017 9 | 10 | 12 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/RequestExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 20 | 21 | public interface RequestExecutor { 22 | 23 | GoPluginApiResponse execute() throws Exception; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/resources/publish-artifact.template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {{GOINPUTNAME[Source].$error.server}} 5 |
6 | 7 |
8 | 9 | 10 | {{GOINPUTNAME[Destination].$error.server}} 11 |
12 | -------------------------------------------------------------------------------- /src/main/resource-templates/plugin.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | ${name} 20 | ${version} 21 | ${goCdVersion} 22 | ${description} 23 | 24 | ${vendorName} 25 | ${vendorUrl} 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 2.1.1 - Unreleased 2 | 3 | ### 2.1.0 (2019-May-28) 4 | 5 | Added support for authentication using AWS instance profiles. 6 | 7 | ### 2.0.1 (2019-May-27) 8 | 9 | Fixed single file upload when at subdirectory. 10 | 11 | ### 2.0.0 (2019-May-19) 12 | 13 | Features and improvements: 14 | - Added numerous tests at unit and integration level 15 | - Implemented pattern maching and directory uploads 16 | - Implemented fetch of multiple files or directories 17 | - Implemented environment variable expansion in the destination prefix of the S3 bucket 18 | - Added eu-north-1 region 19 | 20 | Build system and release cycle: 21 | - Updated build system to use gradle 5.x 22 | - Added builds in docker using dojo. 23 | - Automated testing and releases 24 | - Added integration tests with AWS S3 bucket, publish and fetch cycle 25 | 26 | ### 1.0.0 (2018-08-31) 27 | 28 | - Support the creation of an artifact store for AWS S3 using AWS Credentials (AWS Access Key and AWS Secret Access Key) 29 | - Support for publishing file artifacts to S3 (not folders or wildcard paths) 30 | - Support for fetching files from S3 31 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/Capabilities.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import static diogomrol.gocd.s3.artifact.plugin.utils.Util.GSON; 20 | 21 | public class Capabilities { 22 | public Capabilities() { 23 | } 24 | 25 | public static Capabilities fromJSON(String json) { 26 | return GSON.fromJson(json, Capabilities.class); 27 | } 28 | 29 | public String toJSON() { 30 | return GSON.toJson(this); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/model/ArtifactPlanConfigTypeAdapterTest.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | import org.junit.Test; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | 10 | import static org.assertj.core.api.Assertions.assertThat; 11 | import static org.hamcrest.CoreMatchers.nullValue; 12 | import static org.junit.Assert.fail; 13 | 14 | 15 | public class ArtifactPlanConfigTypeAdapterTest { 16 | 17 | @Test 18 | public void shouldDeserializeToBuildFilePlanConfig() throws JSONException { 19 | List inputs = Arrays.asList( 20 | new JSONObject().put("Source", "info.json").toString(), 21 | new JSONObject().put("Source", "info.json").toString()); 22 | 23 | for (String json : inputs) { 24 | ArtifactPlanConfig artifactPlanConfig = ArtifactPlanConfig.fromJSON(json); 25 | 26 | assertThat(artifactPlanConfig).isInstanceOf(S3FileArtifactPlanConfig.class); 27 | assertThat(((S3FileArtifactPlanConfig) artifactPlanConfig).getSource()).isEqualTo("info.json"); 28 | } 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/FetchArtifact.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | 22 | public class FetchArtifact { 23 | @Expose 24 | @SerializedName("artifact_id") 25 | private String artifactId; 26 | 27 | public FetchArtifact() { 28 | } 29 | 30 | public FetchArtifact(String artifactId) { 31 | this.artifactId = artifactId; 32 | } 33 | 34 | public String getArtifactId() { 35 | return artifactId; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/FieldMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.annotation; 18 | 19 | import java.lang.annotation.ElementType; 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.RetentionPolicy; 22 | import java.lang.annotation.Target; 23 | 24 | @Target(ElementType.FIELD) 25 | @Retention(RetentionPolicy.RUNTIME) 26 | public @interface FieldMetadata { 27 | 28 | String key(); 29 | 30 | boolean required() default false; 31 | 32 | boolean secure() default false; 33 | 34 | FieldType type() default FieldType.STRING; 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetFetchArtifactMetadataExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 21 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 22 | 23 | import java.util.ArrayList; 24 | 25 | public class GetFetchArtifactMetadataExecutor implements RequestExecutor { 26 | public GoPluginApiResponse execute() { 27 | return DefaultGoPluginApiResponse.success(Util.GSON.toJson(new ArrayList<>())); 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/ArtifactPlanConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.Validatable; 20 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 21 | 22 | public abstract class ArtifactPlanConfig implements Validatable { 23 | 24 | abstract public String getSource(); 25 | abstract public String getDestination(); 26 | 27 | @Override 28 | public String toString() { 29 | return toJSON(); 30 | } 31 | 32 | public static ArtifactPlanConfig fromJSON(String json) { 33 | return Util.GSON.fromJson(json, ArtifactPlanConfig.class); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/ValidateFetchArtifactConfigExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationResult; 20 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 21 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 22 | 23 | public class ValidateFetchArtifactConfigExecutor implements RequestExecutor { 24 | 25 | @Override 26 | public GoPluginApiResponse execute() { 27 | final ValidationResult validationResult = new ValidationResult(); 28 | return DefaultGoPluginApiResponse.success(validationResult.toJSON()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/EnvironmentVariableResolver.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import org.apache.commons.lang3.text.StrSubstitutor; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.regex.Pattern; 8 | 9 | public class EnvironmentVariableResolver { 10 | 11 | private static final Pattern ENVIRONMENT_VARIABLE_PATTERN = Pattern.compile("\\$\\{(.*)\\}"); 12 | private String property; 13 | private String propertyName; 14 | 15 | public EnvironmentVariableResolver(String property, String propertyName) { 16 | this.property = property; 17 | this.propertyName = propertyName; 18 | Map map = new HashMap<>(); 19 | map.put("GO_ARTIFACT_LOCATOR", "${GO_PIPELINE_NAME}/${GO_PIPELINE_COUNTER}/${GO_STAGE_NAME}/${GO_STAGE_COUNTER}/${GO_JOB_NAME}"); 20 | this.property = StrSubstitutor.replace(property, map); 21 | } 22 | 23 | public String resolve(Map environmentVariables) throws UnresolvedPropertyException { 24 | String evaluatedProperty = StrSubstitutor.replace(property, environmentVariables); 25 | if(ENVIRONMENT_VARIABLE_PATTERN.matcher(evaluatedProperty).find()) { 26 | throw new UnresolvedPropertyException(evaluatedProperty, propertyName); 27 | } 28 | return evaluatedProperty; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/ValidateFetchArtifactConfigExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 20 | import org.junit.Test; 21 | import org.skyscreamer.jsonassert.JSONAssert; 22 | import org.skyscreamer.jsonassert.JSONCompareMode; 23 | 24 | public class ValidateFetchArtifactConfigExecutorTest { 25 | @Test 26 | public void shouldValidateMandatoryKeys() throws Exception { 27 | 28 | final GoPluginApiResponse response = new ValidateFetchArtifactConfigExecutor().execute(); 29 | 30 | String expectedJSON = "[]"; 31 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 32 | } 33 | } -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/ConfigMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.annotation; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | 22 | public class ConfigMetadata { 23 | 24 | @Expose 25 | @SerializedName("key") 26 | private String key; 27 | 28 | @Expose 29 | @SerializedName("metadata") 30 | private FieldMetadata metadata; 31 | 32 | public ConfigMetadata(String key, FieldMetadata metadata) { 33 | this.key = key; 34 | this.metadata = metadata; 35 | } 36 | 37 | public String getKey() { 38 | return key; 39 | } 40 | 41 | public boolean isRequired() { 42 | return metadata.required(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/AntDirectoryScanner.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import org.apache.tools.ant.DirectoryScanner; 4 | import java.io.File; 5 | import java.util.ArrayList; 6 | import java.util.Arrays; 7 | import java.util.List; 8 | import java.util.stream.Collectors; 9 | 10 | public class AntDirectoryScanner { 11 | 12 | public List getFilesMatchingPattern(File baseDir, String pattern) { 13 | DirectoryScanner scanner = new DirectoryScanner(); 14 | scanner.setBasedir(baseDir); 15 | scanner.setIncludes(pattern.trim().split(" *, *")); 16 | scanner.scan(); 17 | 18 | String[] allPaths = scanner.getIncludedFiles(); 19 | List allFiles = new ArrayList<>(); 20 | String[] directories = scanner.getIncludedDirectories(); 21 | for (String directory : directories) { 22 | File[] files = new File(baseDir, directory).listFiles(); 23 | if(files != null) { 24 | for(File f : files) { 25 | allFiles.add(new File(directory, f.getName())); 26 | } 27 | } 28 | } 29 | 30 | for (int i = 0; i < allPaths.length; i++) { 31 | File file = new File(allPaths[i]); 32 | if (!allFiles.contains(file)) { 33 | allFiles.add(new File(allPaths[i])); 34 | } 35 | } 36 | return allFiles; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetCapabilitiesExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.model.Capabilities; 20 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 21 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 22 | 23 | import static com.thoughtworks.go.plugin.api.response.DefaultGoApiResponse.SUCCESS_RESPONSE_CODE; 24 | 25 | public class GetCapabilitiesExecutor { 26 | 27 | public GoPluginApiResponse execute() { 28 | Capabilities capabilities = getCapabilities(); 29 | return new DefaultGoPluginApiResponse(SUCCESS_RESPONSE_CODE, capabilities.toJSON()); 30 | } 31 | 32 | Capabilities getCapabilities() { 33 | return new Capabilities(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/GetFetchArtifactMetadataExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 20 | import org.json.JSONException; 21 | import org.junit.Test; 22 | import org.skyscreamer.jsonassert.JSONAssert; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class GetFetchArtifactMetadataExecutorTest { 27 | @Test 28 | public void shouldReturnFetchArtifactMetadata() throws JSONException { 29 | final GoPluginApiResponse response = new GetFetchArtifactMetadataExecutor().execute(); 30 | 31 | final String expectedJSON = "[]"; 32 | 33 | assertThat(response.responseCode()).isEqualTo(200); 34 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), true); 35 | } 36 | } -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetArtifactStoreViewExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.JsonObject; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | public class GetArtifactStoreViewExecutor implements RequestExecutor { 26 | private static final Gson GSON = new Gson(); 27 | 28 | @Override 29 | public GoPluginApiResponse execute() { 30 | JsonObject jsonObject = new JsonObject(); 31 | jsonObject.addProperty("template", Util.readResource("/artifact-store.template.html")); 32 | return DefaultGoPluginApiResponse.success( GSON.toJson(jsonObject)); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetFetchArtifactViewExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.JsonObject; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | public class GetFetchArtifactViewExecutor implements RequestExecutor { 26 | private static final Gson GSON = new Gson(); 27 | 28 | @Override 29 | public GoPluginApiResponse execute() { 30 | JsonObject jsonObject = new JsonObject(); 31 | jsonObject.addProperty("template", Util.readResource("/fetch-artifact.template.html")); 32 | return DefaultGoPluginApiResponse.success(GSON.toJson(jsonObject)); 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetPublishArtifactViewExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.JsonObject; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | public class GetPublishArtifactViewExecutor implements RequestExecutor { 26 | private static final Gson GSON = new Gson(); 27 | 28 | @Override 29 | public GoPluginApiResponse execute() { 30 | JsonObject jsonObject = new JsonObject(); 31 | jsonObject.addProperty("template", Util.readResource("/publish-artifact.template.html")); 32 | return DefaultGoPluginApiResponse.success( GSON.toJson(jsonObject)); 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /src/main/resources/fetch-artifact.template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {{GOINPUTNAME[SubPath].$error.server}} 5 |
6 | 7 |
8 | 9 | 10 | {{GOINPUTNAME[Destination].$error.server}} 11 |
12 | 13 |
14 | 16 | 17 | {{GOINPUTNAME[IsFile].$error.server}} 18 |
-------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin; 18 | 19 | import com.thoughtworks.go.plugin.api.GoPluginIdentifier; 20 | 21 | import java.util.Collections; 22 | 23 | public interface Constants { 24 | // The type of this extension 25 | String EXTENSION_TYPE = "artifact"; 26 | 27 | // The extension point API version that this plugin understands 28 | String API_VERSION = "1.0"; 29 | 30 | // the identifier of this plugin 31 | GoPluginIdentifier PLUGIN_IDENTIFIER = new GoPluginIdentifier(EXTENSION_TYPE, Collections.singletonList(API_VERSION)); 32 | 33 | // requests that the plugin makes to the server 34 | String REQUEST_SERVER_PREFIX = "go.processor"; 35 | String REQUEST_SERVER_GET_PLUGIN_SETTINGS = REQUEST_SERVER_PREFIX + ".plugin-settings.get"; 36 | String SEND_CONSOLE_LOG = "go.processor.artifact.console-log"; 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetPublishArtifactConfigMetadataExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.ConfigMetadata; 20 | import diogomrol.gocd.s3.artifact.plugin.annotation.MetadataHelper; 21 | import diogomrol.gocd.s3.artifact.plugin.model.S3FileArtifactPlanConfig; 22 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 23 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 24 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 25 | 26 | import java.util.List; 27 | 28 | public class GetPublishArtifactConfigMetadataExecutor implements RequestExecutor { 29 | 30 | public GoPluginApiResponse execute() { 31 | final List metadata = MetadataHelper.getMetadata(S3FileArtifactPlanConfig.class); 32 | return DefaultGoPluginApiResponse.success(Util.GSON.toJson(metadata)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetArtifactStoreConfigMetadataExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.ConfigMetadata; 20 | import diogomrol.gocd.s3.artifact.plugin.annotation.MetadataHelper; 21 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactStoreConfig; 22 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 23 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 24 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 25 | 26 | import java.util.List; 27 | 28 | public class GetArtifactStoreConfigMetadataExecutor implements RequestExecutor { 29 | 30 | public GoPluginApiResponse execute() { 31 | final List storeConfigMetadata = MetadataHelper.getMetadata(ArtifactStoreConfig.class); 32 | return DefaultGoPluginApiResponse.success( Util.GSON.toJson(storeConfigMetadata)); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/MetadataHelper.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.annotation; 18 | 19 | 20 | import java.lang.reflect.Field; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | public class MetadataHelper { 25 | 26 | public static List getMetadata(Class clazz) { 27 | return buildMetadata(clazz); 28 | } 29 | 30 | private static List buildMetadata(Class clazz) { 31 | Field[] fields = clazz.getDeclaredFields(); 32 | List metadata = new ArrayList<>(); 33 | for (Field field : fields) { 34 | FieldMetadata profileField = field.getAnnotation(FieldMetadata.class); 35 | if (profileField != null) { 36 | final ConfigMetadata configMetadata = new ConfigMetadata(profileField.key(), profileField); 37 | metadata.add(configMetadata); 38 | } 39 | } 40 | return metadata; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/PublishArtifactResponse.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import com.google.gson.Gson; 20 | import com.google.gson.GsonBuilder; 21 | import com.google.gson.annotations.Expose; 22 | import com.google.gson.annotations.SerializedName; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | public class PublishArtifactResponse { 28 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().serializeNulls().create(); 29 | 30 | public PublishArtifactResponse() { 31 | } 32 | 33 | @Expose 34 | @SerializedName("metadata") 35 | private Map metadata = new HashMap<>(); 36 | 37 | public Map getMetadata() { 38 | return metadata; 39 | } 40 | 41 | public void addMetadata(String key, Object value) { 42 | this.metadata.put(key, value); 43 | } 44 | 45 | public String toJSON() { 46 | return GSON.toJson(this); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/GetFetchArtifactViewExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.reflect.TypeToken; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.junit.Test; 24 | 25 | import java.util.Map; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | 29 | public class GetFetchArtifactViewExecutorTest { 30 | 31 | @Test 32 | public void shouldRenderTheTemplateInJSON() { 33 | GoPluginApiResponse response = new GetFetchArtifactViewExecutor().execute(); 34 | 35 | Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 36 | 37 | assertThat(response.responseCode()).isEqualTo(200); 38 | assertThat(responseHash).containsEntry("template", Util.readResource("/fetch-artifact.template.html")); 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/GetPluginIconExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.reflect.TypeToken; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.junit.Test; 24 | 25 | import java.util.Base64; 26 | import java.util.Map; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | 30 | public class GetPluginIconExecutorTest { 31 | @Test 32 | public void rendersIconInBase64() { 33 | GoPluginApiResponse response = new GetPluginIconExecutor().execute(); 34 | Map hashMap = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 35 | assertThat(hashMap).hasSize(2); 36 | assertThat(hashMap.get("content_type")).isEqualTo("image/svg+xml"); 37 | assertThat(Util.readResourceBytes("/plugin-icon.svg")).isEqualTo(Base64.getDecoder().decode(hashMap.get("data"))); 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/ValidateArtifactStoreConfigExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationResult; 20 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactStoreConfig; 21 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | public class ValidateArtifactStoreConfigExecutor implements RequestExecutor { 26 | private final ArtifactStoreConfig artifactStoreConfig; 27 | 28 | public ValidateArtifactStoreConfigExecutor(GoPluginApiRequest request) { 29 | artifactStoreConfig = ArtifactStoreConfig.fromJSON(request.requestBody()); 30 | } 31 | 32 | @Override 33 | public GoPluginApiResponse execute() { 34 | final ValidationResult validationResult = artifactStoreConfig.validate(); 35 | return DefaultGoPluginApiResponse.success(validationResult.toJSON()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/GetPublishArtifactRequestMetadataExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 20 | import org.json.JSONException; 21 | import org.junit.Test; 22 | import org.skyscreamer.jsonassert.JSONAssert; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class GetPublishArtifactRequestMetadataExecutorTest { 27 | @Test 28 | public void shouldReturnFetchArtifactMetadata() throws JSONException { 29 | final GoPluginApiResponse response = new GetPublishArtifactConfigMetadataExecutor().execute(); 30 | 31 | final String expectedJSON = "[" + 32 | "{\"key\":\"Source\",\"metadata\":{\"required\":false,\"secure\":false}}," + 33 | "{\"key\":\"Destination\",\"metadata\":{\"required\":false,\"secure\":false}}" + 34 | "]"; 35 | 36 | assertThat(response.responseCode()).isEqualTo(200); 37 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), true); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/GetPluginIconExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | 20 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 21 | import com.google.gson.Gson; 22 | import com.google.gson.JsonObject; 23 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 24 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 25 | 26 | import java.util.Base64; 27 | 28 | public class GetPluginIconExecutor implements RequestExecutor { 29 | private static final Gson GSON = new Gson(); 30 | 31 | @Override 32 | public GoPluginApiResponse execute() { 33 | JsonObject jsonObject = new JsonObject(); 34 | jsonObject.addProperty("content_type", getContentType()); 35 | jsonObject.addProperty("data", Base64.getEncoder().encodeToString(Util.readResourceBytes(getIcon()))); 36 | return DefaultGoPluginApiResponse.success(GSON.toJson(jsonObject)); 37 | } 38 | 39 | private String getContentType() { 40 | return "image/svg+xml"; 41 | } 42 | 43 | private String getIcon() { 44 | return "/plugin-icon.svg"; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/S3ClientFactory.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin; 2 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactStoreConfig; 3 | import com.amazonaws.SdkClientException; 4 | import com.amazonaws.auth.AWSStaticCredentialsProvider; 5 | import com.amazonaws.auth.BasicAWSCredentials; 6 | import com.amazonaws.regions.Regions; 7 | import com.amazonaws.services.s3.AmazonS3; 8 | import com.amazonaws.services.s3.AmazonS3ClientBuilder; 9 | import org.apache.commons.lang3.StringUtils; 10 | 11 | public class S3ClientFactory { 12 | private static final S3ClientFactory S3_CLIENT_FACTORY = new S3ClientFactory(); 13 | 14 | public AmazonS3 s3(ArtifactStoreConfig artifactStoreConfig) throws SdkClientException { 15 | return createClient(artifactStoreConfig); 16 | } 17 | 18 | public static S3ClientFactory instance() { 19 | return S3_CLIENT_FACTORY; 20 | } 21 | 22 | private static AmazonS3 createClient(ArtifactStoreConfig artifactStoreConfig) throws SdkClientException { 23 | AmazonS3ClientBuilder s3ClientBuilder = AmazonS3ClientBuilder.standard(); 24 | 25 | if (StringUtils.isNotBlank(artifactStoreConfig.getRegion())) { 26 | s3ClientBuilder = s3ClientBuilder.withRegion(Regions.fromName(artifactStoreConfig.getRegion())); 27 | } 28 | 29 | if (StringUtils.isNotBlank(artifactStoreConfig.getAwsaccesskey()) && StringUtils.isNotBlank(artifactStoreConfig.getAwssecretaccesskey())) { 30 | BasicAWSCredentials awsCredentials = new BasicAWSCredentials(artifactStoreConfig.getAwsaccesskey(), artifactStoreConfig.getAwssecretaccesskey()); 31 | s3ClientBuilder = s3ClientBuilder.withCredentials(new AWSStaticCredentialsProvider(awsCredentials)); 32 | } 33 | 34 | return s3ClientBuilder.build(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/FieldType.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.annotation; 18 | 19 | public enum FieldType { 20 | STRING { 21 | @Override 22 | public String validate(String value) { 23 | return null; 24 | } 25 | }, 26 | 27 | POSITIVE_DECIMAL { 28 | @Override 29 | public String validate(String value) { 30 | try { 31 | if (Long.parseLong(value) < 0) { 32 | return "must be positive decimal"; 33 | } 34 | } catch (Exception e) { 35 | return "must be positive decimal"; 36 | } 37 | return null; 38 | } 39 | }, 40 | 41 | NUMBER { 42 | @Override 43 | public String validate(String value) { 44 | try { 45 | if (Double.parseDouble(value) < 0) { 46 | return "must be number"; 47 | } 48 | } catch (Exception e) { 49 | return "must be number"; 50 | } 51 | 52 | return null; 53 | } 54 | }; 55 | 56 | public abstract String validate(String value); 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/FetchArtifactRequest.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 6 | 7 | import java.util.Map; 8 | 9 | public class FetchArtifactRequest { 10 | @Expose 11 | @SerializedName("fetch_artifact_configuration") 12 | private FetchArtifactConfig fetchArtifactConfig; 13 | @Expose 14 | @SerializedName("store_configuration") 15 | private ArtifactStoreConfig artifactStoreConfig; 16 | @Expose 17 | @SerializedName("artifact_metadata") 18 | private Map metadata; 19 | 20 | @Expose 21 | @SerializedName("agent_working_directory") 22 | private String agentWorkingDir; 23 | 24 | public FetchArtifactRequest() { 25 | } 26 | 27 | public FetchArtifactRequest(ArtifactStoreConfig artifactStoreConfig, Map metadata, FetchArtifactConfig fetchArtifactConfig, String agentWorkingDir) { 28 | this.artifactStoreConfig = artifactStoreConfig; 29 | this.metadata = metadata; 30 | this.agentWorkingDir = agentWorkingDir; 31 | this.fetchArtifactConfig = fetchArtifactConfig; 32 | } 33 | 34 | public ArtifactStoreConfig getArtifactStoreConfig() { 35 | return artifactStoreConfig; 36 | } 37 | 38 | public String getAgentWorkingDir() { 39 | return agentWorkingDir; 40 | } 41 | 42 | public Map getMetadata() { 43 | return metadata; 44 | } 45 | 46 | public static FetchArtifactRequest fromJSON(String json) { 47 | return Util.GSON.fromJson(json, FetchArtifactRequest.class); 48 | } 49 | 50 | public FetchArtifactConfig getFetchArtifactConfig() { 51 | return fetchArtifactConfig; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/S3FileArtifactPlanConfig.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import com.google.common.collect.ImmutableSet; 4 | import diogomrol.gocd.s3.artifact.plugin.annotation.FieldMetadata; 5 | import com.google.gson.annotations.Expose; 6 | import com.google.gson.annotations.SerializedName; 7 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationResult; 8 | 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | 12 | public class S3FileArtifactPlanConfig extends ArtifactPlanConfig { 13 | 14 | private static final ImmutableSet OPTIONAL_PROPERTIES = ImmutableSet.of("Destination"); 15 | 16 | @Expose 17 | @SerializedName("Source") 18 | @FieldMetadata(key = "Source") 19 | private String source; 20 | 21 | @Expose 22 | @SerializedName("Destination") 23 | @FieldMetadata(key = "Destination") 24 | private String destination; 25 | 26 | public S3FileArtifactPlanConfig(String source, Optional destination) { 27 | this.source = source; 28 | this.destination = destination.orElse(""); 29 | } 30 | 31 | @Override 32 | public String getSource() { return source; } 33 | 34 | @Override 35 | public String getDestination() { 36 | return destination; 37 | } 38 | 39 | @Override 40 | public boolean equals(Object o) { 41 | if (this == o) return true; 42 | if (o == null || getClass() != o.getClass()) return false; 43 | S3FileArtifactPlanConfig that = (S3FileArtifactPlanConfig) o; 44 | return Objects.equals(source, that.source) && Objects.equals(destination, that.destination); 45 | } 46 | 47 | @Override 48 | public int hashCode() { 49 | return Objects.hash(source, destination); 50 | } 51 | 52 | @Override 53 | public ValidationResult validate() { 54 | return new ValidationResult(validateAllFieldsAsRequired(OPTIONAL_PROPERTIES)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/ValidationError.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.annotation; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | 22 | public class ValidationError { 23 | @Expose 24 | @SerializedName("key") 25 | private final String key; 26 | @Expose 27 | @SerializedName("message") 28 | private final String message; 29 | 30 | public ValidationError(String key, String message) { 31 | this.key = key; 32 | this.message = message; 33 | } 34 | 35 | public String key() { 36 | return key; 37 | } 38 | 39 | public String message() { 40 | return message; 41 | } 42 | 43 | @Override 44 | public boolean equals(Object o) { 45 | if (this == o) return true; 46 | if (o == null || getClass() != o.getClass()) return false; 47 | 48 | ValidationError that = (ValidationError) o; 49 | 50 | if (key != null ? !key.equals(that.key) : that.key != null) return false; 51 | return message != null ? message.equals(that.message) : that.message == null; 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | int result = key != null ? key.hashCode() : 0; 57 | result = 31 * result + (message != null ? message.hashCode() : 0); 58 | return result; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/FetchArtifactConfig.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | import diogomrol.gocd.s3.artifact.plugin.annotation.FieldMetadata; 6 | import diogomrol.gocd.s3.artifact.plugin.annotation.Validatable; 7 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationResult; 8 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 9 | 10 | public class FetchArtifactConfig implements Validatable { 11 | @Expose 12 | @SerializedName("SubPath") 13 | @FieldMetadata(key = "SubPath", required = false) 14 | private String subPath; 15 | 16 | @Expose 17 | @SerializedName("IsFile") 18 | @FieldMetadata(key = "IsFile", required = false) 19 | private boolean isFile; 20 | 21 | @Expose 22 | @SerializedName("Destination") 23 | @FieldMetadata(key = "Destination", required = false) 24 | private String destination; 25 | 26 | public FetchArtifactConfig() { 27 | } 28 | 29 | public FetchArtifactConfig(String subPath, String destination, boolean isFile) { 30 | this.isFile = isFile; 31 | this.destination = destination; 32 | this.subPath = subPath; 33 | } 34 | 35 | public static FetchArtifactConfig fromJSON(String json) { 36 | return Util.GSON.fromJson(json, FetchArtifactConfig.class); 37 | } 38 | 39 | public String getSubPath() { 40 | return subPath; 41 | } 42 | 43 | @Override 44 | public ValidationResult validate() { 45 | ValidationResult validationResult = new ValidationResult(); 46 | //TODO: tomzo check if subdirectory is a valid path for S3 path 47 | return validationResult; 48 | } 49 | 50 | public boolean getIsFile() { 51 | return isFile; 52 | } 53 | 54 | public String getDestination() { 55 | return destination; 56 | } 57 | 58 | public void setDestination(String destination) { 59 | this.destination = destination; 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/ValidationResult.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.annotation; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | 21 | import java.util.*; 22 | 23 | public class ValidationResult { 24 | private final Set errors = new HashSet<>(); 25 | 26 | public ValidationResult() { 27 | } 28 | 29 | public ValidationResult(Collection errors) { 30 | this.errors.addAll(errors); 31 | } 32 | 33 | public ValidationResult(ValidationError... errors) { 34 | this.errors.addAll(Arrays.asList(errors)); 35 | } 36 | 37 | public void addError(String key, String message) { 38 | errors.add(new ValidationError(key, message)); 39 | } 40 | 41 | public void addError(ValidationError validationError) { 42 | if (validationError == null) { 43 | return; 44 | } 45 | errors.add(validationError); 46 | } 47 | 48 | public boolean hasErrors() { 49 | return !errors.isEmpty(); 50 | } 51 | 52 | public String toJSON() { 53 | return Util.GSON.toJson(errors); 54 | } 55 | 56 | public boolean hasKey(String key) { 57 | return errors.stream().anyMatch(validationError -> key.equals(validationError.key())); 58 | } 59 | 60 | public List errors() { 61 | return Collections.unmodifiableList(new ArrayList<>(errors)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/resources/plugin-settings.template.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 |
23 | 24 | 25 | {{GOINPUTNAME[go_server_url].$error.server}} 26 |
27 | 28 |
29 | 30 | 31 | {{GOINPUTNAME[api_url].$error.server}} 32 |
33 | 34 |
35 | 36 | 37 | {{GOINPUTNAME[api_user].$error.server}} 38 |
39 | 40 |
41 | 42 | 43 | {{GOINPUTNAME[api_key].$error.server}} 44 |
45 | -------------------------------------------------------------------------------- /e2e/terraform/s3.tf: -------------------------------------------------------------------------------- 1 | 2 | variable "suffix" { 3 | default = "dev" 4 | } 5 | 6 | # Setup our aws provider 7 | provider "aws" { 8 | region = "eu-west-1" 9 | } 10 | 11 | # For local testing outside of kudulab, this should be commented out 12 | terraform { 13 | backend "s3" { 14 | bucket = "kudu-terraform-infra" 15 | region = "eu-west-1" 16 | dynamodb_table = "kudu-terraform-locks" 17 | } 18 | } 19 | 20 | resource "aws_s3_bucket" "gocd_artifact_test" { 21 | bucket = "gocd-s3-artifact-test-${var.suffix}" 22 | acl = "private" 23 | force_destroy = true 24 | 25 | tags = { 26 | Name = "GoCD S3 plugin test bucket ${var.suffix}" 27 | createdBy = "gocd-s3-artifact-plugin" 28 | } 29 | } 30 | 31 | resource "aws_iam_user" "test_user" { 32 | name = "gocd-s3-artifact-test-${var.suffix}" 33 | path = "/" 34 | permissions_boundary = "arn:aws:iam::976184668068:policy/gocd-s3-artifact-boundry" 35 | tags = { 36 | Name = "GoCD S3 plugin test user ${var.suffix}" 37 | createdBy = "gocd-s3-artifact-plugin" 38 | } 39 | } 40 | 41 | resource "aws_iam_access_key" "test_user" { 42 | user = "${aws_iam_user.test_user.name}" 43 | } 44 | 45 | resource "aws_iam_user_policy" "test_user_policy" { 46 | name = "gocd-s3-artifact-test-${var.suffix}" 47 | user = "${aws_iam_user.test_user.name}" 48 | 49 | policy = < responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 39 | 40 | assertThat(response.responseCode()).isEqualTo(200); 41 | assertThat(responseHash).containsEntry("template", Util.readResource("/artifact-store.template.html")); 42 | } 43 | 44 | @Override 45 | protected List getMetadataList() { 46 | return MetadataHelper.getMetadata(ArtifactStoreConfig.class); 47 | } 48 | 49 | @Override 50 | protected RequestExecutor getRequestExecutor() { 51 | return new GetArtifactStoreViewExecutor(); 52 | } 53 | } -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/ValidatePublishArtifactConfigExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationError; 20 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationResult; 21 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactPlanConfig; 22 | import com.google.gson.JsonParseException; 23 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 24 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 25 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 26 | 27 | public class ValidatePublishArtifactConfigExecutor implements RequestExecutor { 28 | private final String artifactPlanConfigJSON; 29 | 30 | public ValidatePublishArtifactConfigExecutor(GoPluginApiRequest request) { 31 | artifactPlanConfigJSON = request.requestBody(); 32 | } 33 | 34 | @Override 35 | public GoPluginApiResponse execute() { 36 | try { 37 | ArtifactPlanConfig artifactPlanConfig = ArtifactPlanConfig.fromJSON(artifactPlanConfigJSON); 38 | ValidationResult validationResult = artifactPlanConfig.validate(); 39 | return DefaultGoPluginApiResponse.success(validationResult.toJSON()); 40 | } catch (JsonParseException e) { 41 | ValidationResult validationResult = new ValidationResult( 42 | new ValidationError("Source", "Source file must be specified.")); 43 | return DefaultGoPluginApiResponse.success(validationResult.toJSON()); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/ArtifactPlanConfigTypeAdapter.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import com.google.gson.*; 4 | import org.apache.commons.lang3.StringUtils; 5 | 6 | import java.lang.reflect.Type; 7 | import java.util.Optional; 8 | 9 | public class ArtifactPlanConfigTypeAdapter implements JsonDeserializer, JsonSerializer { 10 | 11 | @Override 12 | public ArtifactPlanConfig deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 13 | JsonObject jsonObject = json.getAsJsonObject(); 14 | if (isBuildFileConfig(jsonObject)) { 15 | return new S3FileArtifactPlanConfig(jsonObject.get("Source").getAsString(), parseDestination(jsonObject)); 16 | } else { 17 | throw new JsonParseException("Ambiguous or unknown json. `Source` property must be specified."); 18 | } 19 | } 20 | 21 | private Optional parseDestination(JsonObject jsonObject) { 22 | JsonElement destination = jsonObject.get("Destination"); 23 | if (destination != null && StringUtils.isNotBlank(destination.getAsString())) { 24 | return Optional.of(destination.getAsString()); 25 | } 26 | return Optional.empty(); 27 | } 28 | 29 | @Override 30 | public JsonElement serialize(ArtifactPlanConfig src, Type typeOfSrc, JsonSerializationContext context) { 31 | if (src instanceof S3FileArtifactPlanConfig) { 32 | return context.serialize(src, S3FileArtifactPlanConfig.class); 33 | } 34 | throw new JsonIOException("Unknown type of ArtifactPlanConfig"); 35 | } 36 | 37 | private boolean isBuildFileConfig(JsonObject jsonObject) { 38 | return containsSourceFileProperty(jsonObject); 39 | } 40 | 41 | private boolean containsSourceFileProperty(JsonObject jsonObject) { 42 | return jsonObject.has("Source") && isPropertyNotBlank(jsonObject); 43 | } 44 | 45 | private boolean isPropertyNotBlank(JsonObject jsonObject) { 46 | try { 47 | JsonElement jsonElement = jsonObject.get("Source"); 48 | return StringUtils.isNotBlank(jsonElement.getAsString()); 49 | } catch (UnsupportedOperationException e) { 50 | return false; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/GetPublishArtifactViewExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.ConfigMetadata; 20 | import diogomrol.gocd.s3.artifact.plugin.annotation.MetadataHelper; 21 | import diogomrol.gocd.s3.artifact.plugin.model.S3FileArtifactPlanConfig; 22 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 23 | import com.google.gson.Gson; 24 | import com.google.gson.reflect.TypeToken; 25 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 26 | import org.junit.Test; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | import java.util.Map; 31 | 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | 34 | public class GetPublishArtifactViewExecutorTest extends ViewTest { 35 | @Test 36 | public void shouldRenderTheTemplateInJSON() throws Exception { 37 | GoPluginApiResponse response = getRequestExecutor().execute(); 38 | 39 | Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 40 | 41 | assertThat(response.responseCode()).isEqualTo(200); 42 | assertThat(responseHash).containsEntry("template", Util.readResource("/publish-artifact.template.html")); 43 | } 44 | 45 | 46 | @Override 47 | protected List getMetadataList() { 48 | List configMetadata = new ArrayList<>(); 49 | configMetadata.addAll(MetadataHelper.getMetadata(S3FileArtifactPlanConfig.class)); 50 | return configMetadata; 51 | } 52 | 53 | @Override 54 | protected RequestExecutor getRequestExecutor() { 55 | return new GetPublishArtifactViewExecutor(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin; 18 | 19 | /** 20 | * Enumerable that represents one of the messages that the server sends to the plugin 21 | */ 22 | public enum Request { 23 | REQUEST_GET_PLUGIN_ICON("cd.go.artifact.get-icon"), 24 | REQUEST_GET_PLUGIN_CAPABILITIES("cd.go.artifact.get-capabilities"), 25 | 26 | REQUEST_STORE_CONFIG_METADATA("cd.go.artifact.store.get-metadata"), 27 | REQUEST_STORE_CONFIG_VIEW("cd.go.artifact.store.get-view"), 28 | REQUEST_STORE_CONFIG_VALIDATE("cd.go.artifact.store.validate"), 29 | 30 | REQUEST_PUBLISH_ARTIFACT_METADATA("cd.go.artifact.publish.get-metadata"), 31 | REQUEST_PUBLISH_ARTIFACT_VIEW("cd.go.artifact.publish.get-view"), 32 | REQUEST_PUBLISH_ARTIFACT_VALIDATE("cd.go.artifact.publish.validate"), 33 | 34 | REQUEST_FETCH_ARTIFACT_METADATA("cd.go.artifact.fetch.get-metadata"), 35 | REQUEST_FETCH_ARTIFACT_VIEW("cd.go.artifact.fetch.get-view"), 36 | REQUEST_FETCH_ARTIFACT_VALIDATE("cd.go.artifact.fetch.validate"), 37 | 38 | REQUEST_PUBLISH_ARTIFACT("cd.go.artifact.publish-artifact"), 39 | REQUEST_FETCH_ARTIFACT("cd.go.artifact.fetch-artifact"); 40 | 41 | private final String requestName; 42 | 43 | Request(String requestName) { 44 | this.requestName = requestName; 45 | } 46 | 47 | public String requestName() { 48 | return requestName; 49 | } 50 | 51 | public static Request fromString(String requestName) { 52 | if (requestName != null) { 53 | for (Request request : Request.values()) { 54 | if (requestName.equalsIgnoreCase(request.requestName)) { 55 | return request; 56 | } 57 | } 58 | } 59 | 60 | return null; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /ci.gocd.yaml: -------------------------------------------------------------------------------- 1 | format_version: 3 2 | pipelines: 3 | "gocd-s3-artifact-plugin": 4 | group: gocd 5 | label_template: "${git[:8]}" 6 | locking: off 7 | materials: 8 | git: 9 | type: configrepo 10 | blacklist: 11 | - "*.md" 12 | - "build.gradle" 13 | secure_variables: 14 | VAULT_TOKEN: "AES:am1B7EuHGEV67+WKsAqzRQ==:AU2HecWwVR2T2OqyNN4D7+9DYWZQ3udAXXp8fnR8jjk=" 15 | stages: 16 | - build: 17 | clean_workspace: true 18 | resources: 19 | - docker 20 | artifacts: 21 | - build: 22 | source: build/libs/*.jar* 23 | destination: build/libs 24 | tasks: 25 | - exec: 26 | command: bash 27 | arguments: 28 | - -c 29 | - ./tasks tf_apply create 30 | - exec: 31 | command: bash 32 | arguments: 33 | - -c 34 | - ./tasks build_test 35 | - exec: 36 | run_if: any 37 | command: bash 38 | arguments: 39 | - -c 40 | - ./tasks tf_apply destroy 41 | - release: 42 | approval: manual 43 | clean_workspace: true 44 | jobs: 45 | github: 46 | resources: 47 | - docker 48 | tasks: 49 | - fetch: 50 | stage: build 51 | job: build 52 | source: build/libs 53 | destination: build 54 | - exec: 55 | command: /bin/bash 56 | arguments: 57 | - ./tasks 58 | - github_release 59 | - bump: 60 | clean_workspace: true 61 | jobs: 62 | patch: 63 | resources: 64 | - docker 65 | tasks: 66 | - exec: 67 | command: /bin/bash 68 | arguments: 69 | - -c 70 | - ./tasks bump 71 | - exec: 72 | command: /bin/bash 73 | arguments: 74 | - -c 75 | - ./tasks commit 76 | - exec: 77 | command: git 78 | arguments: 79 | - push 80 | - origin 81 | - master 82 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/model/PublishArtifactRequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import org.junit.Test; 20 | 21 | import java.util.Optional; 22 | 23 | import static org.assertj.core.api.Assertions.assertThat; 24 | 25 | public class PublishArtifactRequestTest { 26 | 27 | @Test 28 | public void shouldDeserializeRequestBody() { 29 | final String json = "{\n" + 30 | " \"artifact_plan\": {\n" + 31 | " \"configuration\": {\n" + 32 | " \"Source\": \"alpine-build.json\"\n" + 33 | " },\n" + 34 | " \"id\": \"installers\",\n" + 35 | " \"storeId\": \"s3-store\"\n" + 36 | " },\n" + 37 | " \"artifact_store\": {\n" + 38 | " \"configuration\": {\n" + 39 | " \"S3Bucket\": \"s3-url\",\n" + 40 | " \"Region\": \"us-west-1\",\n" + 41 | " \"AWSAccessKey\": \"aws-access-key\",\n" + 42 | " \"AWSSecretAccessKey\": \"aws-secret-access-key\"\n" + 43 | " },\n" + 44 | " \"id\": \"s3-store\"\n" + 45 | " },\n" + 46 | " \"agent_working_directory\": \"/temp\"\n" + 47 | "}"; 48 | 49 | final PublishArtifactRequest publishArtifactRequest = PublishArtifactRequest.fromJSON(json); 50 | 51 | assertThat(publishArtifactRequest.getAgentWorkingDir()).isEqualTo("/temp"); 52 | 53 | assertThat(publishArtifactRequest.getArtifactStore().getId()).isEqualTo("s3-store"); 54 | assertThat(publishArtifactRequest.getArtifactStore().getArtifactStoreConfig()) 55 | .isEqualTo(new ArtifactStoreConfig("s3-url", "us-west-1", "aws-access-key", "aws-secret-access-key")); 56 | 57 | assertThat(publishArtifactRequest.getArtifactPlan()) 58 | .isEqualTo(new ArtifactPlan("installers", "s3-store", "alpine-build.json", Optional.empty())); 59 | } 60 | } -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/GetArtifactStoreConfigMetadataExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 20 | import org.json.JSONException; 21 | import org.junit.Test; 22 | import org.skyscreamer.jsonassert.JSONAssert; 23 | 24 | import static org.assertj.core.api.Assertions.assertThat; 25 | 26 | public class GetArtifactStoreConfigMetadataExecutorTest { 27 | @Test 28 | public void shouldReturnFetchArtifactMetadata() throws JSONException { 29 | final GoPluginApiResponse response = new GetArtifactStoreConfigMetadataExecutor().execute(); 30 | 31 | final String expectedJSON = "[\n" + 32 | " {\n" + 33 | " \"key\": \"S3Bucket\",\n" + 34 | " \"metadata\": {\n" + 35 | " \"required\": true,\n" + 36 | " \"secure\": false\n" + 37 | " }\n" + 38 | " },\n" + 39 | " {\n" + 40 | " \"key\": \"Region\",\n" + 41 | " \"metadata\": {\n" + 42 | " \"required\": false,\n" + 43 | " \"secure\": false\n" + 44 | " }\n" + 45 | " },\n" + 46 | " {\n" + 47 | " \"key\": \"AWSAccessKey\",\n" + 48 | " \"metadata\": {\n" + 49 | " \"required\": false,\n" + 50 | " \"secure\": false\n" + 51 | " }\n" + 52 | " },\n" + 53 | " {\n" + 54 | " \"key\": \"AWSSecretAccessKey\",\n" + 55 | " \"metadata\": {\n" + 56 | " \"required\": false,\n" + 57 | " \"secure\": true\n" + 58 | " }\n" + 59 | " }\n" + 60 | "]"; 61 | 62 | assertThat(response.responseCode()).isEqualTo(200); 63 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), true); 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/ArtifactPlan.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | import java.util.Optional; 22 | 23 | public class ArtifactPlan { 24 | @Expose 25 | @SerializedName("id") 26 | private String id; 27 | 28 | @Expose 29 | @SerializedName("storeId") 30 | private String storeId; 31 | 32 | @Expose 33 | @SerializedName("configuration") 34 | private ArtifactPlanConfig artifactPlanConfig; 35 | 36 | public ArtifactPlan() { 37 | } 38 | 39 | public ArtifactPlan(String id, String storeId, String sourcePattern, Optional destination) { 40 | this.id = id; 41 | this.storeId = storeId; 42 | this.artifactPlanConfig = new S3FileArtifactPlanConfig(sourcePattern, destination); 43 | } 44 | 45 | public String getId() { 46 | return id; 47 | } 48 | 49 | public String getStoreId() { 50 | return storeId; 51 | } 52 | 53 | public ArtifactPlanConfig getArtifactPlanConfig() { 54 | return artifactPlanConfig; 55 | } 56 | 57 | @Override 58 | public String toString() { 59 | return String.format("Artifact[id=%s, storeId=%s, artifactPlanConfig=%s]", getId(), getStoreId(), artifactPlanConfig.toString()); 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (!(o instanceof ArtifactPlan)) return false; 66 | 67 | ArtifactPlan that = (ArtifactPlan) o; 68 | 69 | if (id != null ? !id.equals(that.id) : that.id != null) return false; 70 | if (storeId != null ? !storeId.equals(that.storeId) : that.storeId != null) return false; 71 | return artifactPlanConfig != null ? artifactPlanConfig.equals(that.artifactPlanConfig) : that.artifactPlanConfig == null; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | int result = id != null ? id.hashCode() : 0; 77 | result = 31 * result + (storeId != null ? storeId.hashCode() : 0); 78 | result = 31 * result + (artifactPlanConfig != null ? artifactPlanConfig.hashCode() : 0); 79 | return result; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/model/AntDirectoryScannerTest.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import com.amazonaws.SdkClientException; 4 | import com.amazonaws.services.s3.AmazonS3; 5 | import com.amazonaws.services.s3.model.PutObjectRequest; 6 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 7 | import diogomrol.gocd.s3.artifact.plugin.ConsoleLogger; 8 | import diogomrol.gocd.s3.artifact.plugin.S3ClientFactory; 9 | import org.json.JSONException; 10 | import org.junit.Before; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.junit.rules.ExpectedException; 14 | import org.junit.rules.TemporaryFolder; 15 | import org.mockito.ArgumentCaptor; 16 | import org.mockito.Captor; 17 | import org.mockito.Mock; 18 | 19 | import java.io.File; 20 | import java.io.IOException; 21 | import java.nio.file.Files; 22 | import java.nio.file.Path; 23 | import java.nio.file.Paths; 24 | import java.util.List; 25 | import java.util.Optional; 26 | 27 | import static org.assertj.core.api.Assertions.assertThat; 28 | import static org.mockito.ArgumentMatchers.any; 29 | import static org.mockito.Mockito.when; 30 | import static org.mockito.MockitoAnnotations.initMocks; 31 | 32 | public class AntDirectoryScannerTest { 33 | @Rule 34 | public TemporaryFolder tmpFolder = new TemporaryFolder(); 35 | private File workingDir; 36 | private AntDirectoryScanner scanner; 37 | 38 | @Before 39 | public void setUp() throws IOException, SdkClientException { 40 | initMocks(this); 41 | workingDir = tmpFolder.newFolder("test-tmp"); 42 | scanner = new AntDirectoryScanner(); 43 | } 44 | 45 | @Test 46 | public void shouldMatchFileByName() throws IOException { 47 | File test = createFile("test.bin"); 48 | List files = scanner.getFilesMatchingPattern(workingDir, "test.bin"); 49 | assertThat(files) 50 | .hasSize(1) 51 | .contains(test); 52 | } 53 | @Test 54 | public void shouldMatchFileInSubdirectory() throws IOException { 55 | File test = createFile("out/test.bin"); 56 | List files = scanner.getFilesMatchingPattern(workingDir, "out"); 57 | assertThat(files) 58 | .hasSize(1) 59 | .contains(test); 60 | } 61 | @Test 62 | public void shouldNotIncludeSameFileTwiceWhenMatches2Rules() throws IOException { 63 | File test = createFile("out/test.bin"); 64 | List files = scanner.getFilesMatchingPattern(workingDir, "out,out/*.bin"); 65 | assertThat(files) 66 | .hasSize(1) 67 | .contains(test); 68 | } 69 | 70 | 71 | private File createFile(String path) throws IOException { 72 | Path filepath = Paths.get(workingDir.toPath().toAbsolutePath().toString(), path); 73 | filepath.getParent().toFile().mkdir(); 74 | Files.write(filepath, "".getBytes()); 75 | return new File(path); 76 | } 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/ArtifactInfo.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 22 | 23 | import java.util.Arrays; 24 | import java.util.List; 25 | 26 | public class ArtifactInfo { 27 | @Expose 28 | @SerializedName("id") 29 | private String id; 30 | 31 | @Expose 32 | @SerializedName("configuration") 33 | private ArtifactStoreConfig artifactStoreConfig; 34 | 35 | @Expose 36 | @SerializedName("artifact_plans") 37 | private List artifactPlans; 38 | 39 | public ArtifactInfo() { 40 | } 41 | 42 | public ArtifactInfo(String id, ArtifactStoreConfig artifactStoreConfig, ArtifactPlan... artifactPlans) { 43 | this.id = id; 44 | this.artifactStoreConfig = artifactStoreConfig; 45 | this.artifactPlans = Arrays.asList(artifactPlans); 46 | } 47 | 48 | public static ArtifactInfo fromJSON(String json) { 49 | return Util.GSON.fromJson(json, ArtifactInfo.class); 50 | } 51 | 52 | public String getId() { 53 | return id; 54 | } 55 | 56 | public ArtifactStoreConfig getArtifactStoreConfig() { 57 | return artifactStoreConfig; 58 | } 59 | 60 | public List getArtifactPlans() { 61 | return artifactPlans; 62 | } 63 | 64 | @Override 65 | public boolean equals(Object o) { 66 | if (this == o) return true; 67 | if (!(o instanceof ArtifactInfo)) return false; 68 | 69 | ArtifactInfo that = (ArtifactInfo) o; 70 | 71 | if (id != null ? !id.equals(that.id) : that.id != null) return false; 72 | if (artifactStoreConfig != null ? !artifactStoreConfig.equals(that.artifactStoreConfig) : that.artifactStoreConfig != null) 73 | return false; 74 | return artifactPlans != null ? artifactPlans.equals(that.artifactPlans) : that.artifactPlans == null; 75 | } 76 | 77 | @Override 78 | public int hashCode() { 79 | int result = id != null ? id.hashCode() : 0; 80 | result = 31 * result + (artifactStoreConfig != null ? artifactStoreConfig.hashCode() : 0); 81 | result = 31 * result + (artifactPlans != null ? artifactPlans.hashCode() : 0); 82 | return result; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/ConsoleLogger.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin; 18 | 19 | import com.google.gson.Gson; 20 | import com.thoughtworks.go.plugin.api.GoApplicationAccessor; 21 | import com.thoughtworks.go.plugin.api.request.DefaultGoApiRequest; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoApiResponse; 24 | 25 | import static diogomrol.gocd.s3.artifact.plugin.S3ArtifactPlugin.LOG; 26 | 27 | public class ConsoleLogger { 28 | private static ConsoleLogger consoleLogger; 29 | private final GoApplicationAccessor accessor; 30 | 31 | private ConsoleLogger(GoApplicationAccessor accessor) { 32 | this.accessor = accessor; 33 | } 34 | 35 | public void info(String message) { 36 | sendLog(new ConsoleLogMessage(ConsoleLogMessage.LogLevel.INFO, message)); 37 | } 38 | 39 | public void error(String message) { 40 | sendLog(new ConsoleLogMessage(ConsoleLogMessage.LogLevel.ERROR, message)); 41 | } 42 | 43 | private void sendLog(ConsoleLogMessage consoleLogMessage) { 44 | DefaultGoApiRequest request = new DefaultGoApiRequest(Constants.SEND_CONSOLE_LOG, Constants.API_VERSION, Constants.PLUGIN_IDENTIFIER); 45 | request.setRequestBody(consoleLogMessage.toJSON()); 46 | 47 | GoApiResponse response = accessor.submit(request); 48 | if (response.responseCode() != DefaultGoApiResponse.SUCCESS_RESPONSE_CODE) { 49 | LOG.error(String.format("Failed to submit console log: %s", response.responseBody())); 50 | } 51 | } 52 | 53 | public static ConsoleLogger getLogger(GoApplicationAccessor accessor) { 54 | if (consoleLogger == null) { 55 | synchronized (ConsoleLogger.class) { 56 | if (consoleLogger == null) { 57 | consoleLogger = new ConsoleLogger(accessor); 58 | } 59 | } 60 | } 61 | 62 | return consoleLogger; 63 | } 64 | 65 | static class ConsoleLogMessage { 66 | private LogLevel logLevel; 67 | private String message; 68 | 69 | public ConsoleLogMessage(LogLevel logLevel, String message) { 70 | this.message = message; 71 | this.logLevel = logLevel; 72 | } 73 | 74 | public String toJSON() { 75 | return new Gson().toJson(this); 76 | } 77 | 78 | enum LogLevel { 79 | INFO, ERROR 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/annotation/Validatable.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.annotation; 18 | 19 | import com.google.common.collect.Lists; 20 | import com.google.gson.reflect.TypeToken; 21 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 22 | import org.apache.commons.lang3.StringUtils; 23 | 24 | import java.util.Collections; 25 | import java.util.List; 26 | import java.util.Map; 27 | import java.util.Set; 28 | import java.util.stream.Collectors; 29 | 30 | public interface Validatable { 31 | 32 | ValidationResult validate(); 33 | 34 | default String toJSON() { 35 | return Util.GSON.toJson(this); 36 | } 37 | 38 | default Map toProperties() { 39 | return Util.GSON.fromJson(toJSON(), new TypeToken>() { 40 | }.getType()); 41 | } 42 | 43 | default List validateAllFieldsAsRequired() { 44 | return validateAllFieldsAsRequired(Collections.emptySet()); 45 | } 46 | 47 | default List validateAllFieldsAsRequired(Set excluding) { 48 | return toProperties().entrySet().stream() 49 | .filter(entry -> !excluding.contains(entry.getKey())) 50 | .filter(entry -> StringUtils.isBlank(entry.getValue())) 51 | .map(entry -> new ValidationError(entry.getKey(), entry.getKey() + " must not be blank.")) 52 | .collect(Collectors.toList()); 53 | } 54 | 55 | default List validateAllOrNoneRequired(Set including) { 56 | 57 | boolean allBlank = true, noneBlank = true; 58 | 59 | for (String propertyName : including) { 60 | String value = toProperties().get(propertyName); 61 | 62 | allBlank &= StringUtils.isBlank(value); 63 | noneBlank &= StringUtils.isNotBlank(value); 64 | } 65 | 66 | if (allBlank || noneBlank) { 67 | return Collections.emptyList(); 68 | } else { 69 | List fieldsList = Lists.newArrayList(including); 70 | String fields = String.join(" and ", String.join(", ", fieldsList.subList(0, fieldsList.size() - 1)), fieldsList.get(fieldsList.size() - 1)); 71 | 72 | String errorMessage = fields + " must be filled altogether, if required."; 73 | 74 | return including.stream() 75 | .map(s -> new ValidationError(s, errorMessage)) 76 | .collect(Collectors.toList()); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/model/EnvironmentVariableResolverTest.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.model; 2 | 3 | import com.google.common.collect.ImmutableMap; 4 | import org.junit.Test; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | 9 | import static org.assertj.core.api.Assertions.assertThat; 10 | 11 | 12 | public class EnvironmentVariableResolverTest { 13 | 14 | @Test 15 | public void shouldResolveTagPatternWithSingleEnvironmentVariable() throws UnresolvedPropertyException { 16 | EnvironmentVariableResolver environmentVariableResolver = new EnvironmentVariableResolver("v${GO_PIPELINE_COUNTER}", "tag"); 17 | Map environmentVariables = ImmutableMap.of("GO_PIPELINE_COUNTER", "112"); 18 | 19 | String tag = environmentVariableResolver.resolve(environmentVariables); 20 | 21 | assertThat(tag).isEqualTo("v112"); 22 | } 23 | 24 | @Test 25 | public void shouldResolveTagPatternWithMultipleEnvironmentVariables() throws UnresolvedPropertyException { 26 | EnvironmentVariableResolver environmentVariableResolver = new EnvironmentVariableResolver("v${GO_PIPELINE_COUNTER}-${GO_STAGE_COUNTER}", "tag"); 27 | Map environmentVariables = ImmutableMap.of("GO_PIPELINE_COUNTER", "112", 28 | "GO_STAGE_COUNTER", "1"); 29 | 30 | String tag = environmentVariableResolver.resolve(environmentVariables); 31 | 32 | assertThat(tag).isEqualTo("v112-1"); 33 | } 34 | 35 | @Test 36 | public void shouldResolveTagPatternWithSpecialGocdArtifactLocatorVariable() throws UnresolvedPropertyException { 37 | EnvironmentVariableResolver environmentVariableResolver = new EnvironmentVariableResolver("v${GO_ARTIFACT_LOCATOR}/x", "tag"); 38 | //${GO_PIPELINE_NAME}/${GO_PIPELINE_COUNTER}/${GO_STAGE_NAME}/${GO_STAGE_COUNTER}/${GO_JOB_NAME} 39 | Map environmentVariables = new HashMap<>(); 40 | environmentVariables.put("GO_PIPELINE_NAME", "test"); 41 | environmentVariables.put("GO_PIPELINE_COUNTER", "112"); 42 | environmentVariables.put("GO_STAGE_NAME", "build"); 43 | environmentVariables.put("GO_STAGE_COUNTER", "21"); 44 | environmentVariables.put("GO_JOB_NAME", "job"); 45 | 46 | String tag = environmentVariableResolver.resolve(environmentVariables); 47 | 48 | assertThat(tag).isEqualTo("vtest/112/build/21/job/x"); 49 | } 50 | 51 | @Test 52 | public void shouldThrowExceptionIfTagIsUnresolved() { 53 | EnvironmentVariableResolver environmentVariableResolver = new EnvironmentVariableResolver("v${GO_PIPELINE_COUNTER}-${GO_STAGE_COUNTER}", "tag"); 54 | Map environmentVariables = ImmutableMap.of("GO_PIPELINE_COUNTER", "112"); 55 | 56 | boolean exceptionCaught = false; 57 | try { 58 | environmentVariableResolver.resolve(environmentVariables); 59 | } catch (UnresolvedPropertyException e) { 60 | assertThat(e.getPartiallyResolvedTag()).isEqualTo("v112-${GO_STAGE_COUNTER}"); 61 | exceptionCaught = true; 62 | } 63 | 64 | assertThat(exceptionCaught).isTrue(); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/ConsoleLoggerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin; 18 | 19 | import com.thoughtworks.go.plugin.api.GoApplicationAccessor; 20 | import com.thoughtworks.go.plugin.api.request.GoApiRequest; 21 | import com.thoughtworks.go.plugin.api.response.DefaultGoApiResponse; 22 | import org.json.JSONException; 23 | import org.junit.BeforeClass; 24 | import org.junit.Test; 25 | import org.mockito.ArgumentCaptor; 26 | import org.skyscreamer.jsonassert.JSONAssert; 27 | 28 | import static org.assertj.core.api.Assertions.assertThat; 29 | import static org.mockito.Mockito.mock; 30 | import static org.mockito.Mockito.when; 31 | 32 | public class ConsoleLoggerTest { 33 | private static GoApplicationAccessor accessor; 34 | private static ConsoleLogger consoleLogger; 35 | private static ArgumentCaptor argumentCaptor; 36 | 37 | @BeforeClass 38 | public static void setUp() { 39 | accessor = mock(GoApplicationAccessor.class); 40 | argumentCaptor = ArgumentCaptor.forClass(GoApiRequest.class); 41 | 42 | consoleLogger = ConsoleLogger.getLogger(accessor); 43 | 44 | when(accessor.submit(argumentCaptor.capture())).thenReturn(DefaultGoApiResponse.success(null)); 45 | } 46 | 47 | @Test 48 | public void shouldLogInfoMessageToConsoleLog() throws JSONException { 49 | consoleLogger.info("This is info message."); 50 | 51 | final GoApiRequest request = argumentCaptor.getValue(); 52 | assertThat(request.api()).isEqualTo(Constants.SEND_CONSOLE_LOG); 53 | assertThat(request.apiVersion()).isEqualTo(Constants.API_VERSION); 54 | 55 | final String expectedJSON = "{\n" + 56 | " \"logLevel\": \"INFO\",\n" + 57 | " \"message\": \"This is info message.\"\n" + 58 | "}"; 59 | 60 | JSONAssert.assertEquals(expectedJSON, request.requestBody(), true); 61 | } 62 | 63 | @Test 64 | public void shouldLogErrorMessageToConsoleLog() throws JSONException { 65 | consoleLogger.error("This is error."); 66 | 67 | final GoApiRequest request = argumentCaptor.getValue(); 68 | assertThat(request.api()).isEqualTo(Constants.SEND_CONSOLE_LOG); 69 | assertThat(request.apiVersion()).isEqualTo(Constants.API_VERSION); 70 | 71 | final String expectedJSON = "{\n" + 72 | " \"logLevel\": \"ERROR\",\n" + 73 | " \"message\": \"This is error.\"\n" + 74 | "}"; 75 | 76 | JSONAssert.assertEquals(expectedJSON, request.requestBody(), true); 77 | } 78 | } -------------------------------------------------------------------------------- /src/main/resources/artifact-store.template.html: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 | {{GOINPUTNAME[S3Bucket].$error.server}} 5 |
6 | 7 |
8 | 9 | 30 | {{GOINPUTNAME[Region].$error.server}} 31 |
32 | 33 |
34 | 35 | 36 | {{GOINPUTNAME[AWSAccessKey].$error.server}} 37 |
38 | 39 |
40 | 41 | 42 | {{GOINPUTNAME[AWSSecretAccessKey].$error.server}} 43 |
44 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/ViewTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.ConfigMetadata; 20 | import com.google.gson.Gson; 21 | import com.google.gson.reflect.TypeToken; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.jsoup.Jsoup; 24 | import org.jsoup.nodes.Document; 25 | import org.jsoup.select.Elements; 26 | import org.junit.Test; 27 | 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | import static java.text.MessageFormat.format; 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | 34 | public abstract class ViewTest { 35 | @Test 36 | public void allFieldsShouldBePresentInView() throws Exception { 37 | final Document document = getDocument(); 38 | final List metadataList = getMetadataList(); 39 | for (ConfigMetadata field : metadataList) { 40 | final String name = field.getKey(); 41 | final Elements inputFieldForName = document.getElementsByAttributeValue("ng-model", name); 42 | assertThat(inputFieldForName).describedAs(format("No input/textarea defined for {0}", name)).hasSize(1); 43 | assertThat(inputFieldForName.get(0).attr("ng-class")) 44 | .describedAs(format("ng-class attribute is not defined on {0}", inputFieldForName)) 45 | .isEqualTo(format("'{'''is-invalid-input'': GOINPUTNAME[{0}].$error.server'}'", name)); 46 | 47 | final Elements spanToShowError = document.getElementsByAttributeValue("ng-class", format("'{'''is-visible'': GOINPUTNAME[{0}].$error.server'}'", name)); 48 | assertThat(spanToShowError).hasSize(1); 49 | assertThat(spanToShowError.attr("ng-show")).isEqualTo(format("GOINPUTNAME[{0}].$error.server", name)); 50 | assertThat(spanToShowError.text()).isEqualTo(format("'{{'GOINPUTNAME[{0}].$error.server'}}'", name)); 51 | } 52 | 53 | final Elements inputs = document.select("textarea, input, select"); 54 | assertThat(inputs).describedAs("should contains only inputs that defined in ElasticProfile.java").hasSize(metadataList.size()); 55 | } 56 | 57 | 58 | private Document getDocument() throws Exception { 59 | final GoPluginApiResponse response = getRequestExecutor().execute(); 60 | final Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 61 | 62 | assertThat(response.responseCode()).isEqualTo(200); 63 | assertThat(responseHash).containsKey("template"); 64 | 65 | return Jsoup.parse(responseHash.get("template")); 66 | } 67 | 68 | protected abstract List getMetadataList(); 69 | 70 | protected abstract RequestExecutor getRequestExecutor(); 71 | } 72 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at diogo.mrol@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/ValidatePublishArtifactConfigExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 20 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 21 | import org.json.JSONArray; 22 | import org.json.JSONException; 23 | import org.json.JSONObject; 24 | import org.junit.Before; 25 | import org.junit.Test; 26 | import org.mockito.Mock; 27 | import org.skyscreamer.jsonassert.JSONAssert; 28 | import org.skyscreamer.jsonassert.JSONCompareMode; 29 | 30 | import static org.mockito.Mockito.when; 31 | import static org.mockito.MockitoAnnotations.initMocks; 32 | 33 | public class ValidatePublishArtifactConfigExecutorTest { 34 | @Mock 35 | private GoPluginApiRequest request; 36 | 37 | @Before 38 | public void setUp() { 39 | initMocks(this); 40 | } 41 | 42 | @Test 43 | public void shouldValidateRequestWithSourceFile() throws Exception { 44 | String requestBody = new JSONObject().put("Source", "").toString(); 45 | when(request.requestBody()).thenReturn(requestBody); 46 | 47 | final GoPluginApiResponse response = new ValidatePublishArtifactConfigExecutor(request).execute(); 48 | 49 | String expectedJSON = "[" + 50 | " {" + 51 | " 'key': 'Source'," + 52 | " 'message': 'Source file must be specified.'" + 53 | " }" + 54 | "]"; 55 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 56 | } 57 | 58 | @Test 59 | public void shouldValidateInvalidRequest() throws JSONException { 60 | when(request.requestBody()).thenReturn("{}"); 61 | 62 | final GoPluginApiResponse response = new ValidatePublishArtifactConfigExecutor(request).execute(); 63 | 64 | String expectedJSON = "[" + 65 | " {" + 66 | " 'key': 'Source'," + 67 | " 'message': 'Source file must be specified.'" + 68 | " }" + 69 | "]"; 70 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 71 | } 72 | 73 | @Test 74 | public void shouldValidateRequestContainingAllFields() throws JSONException { 75 | String requestBody = new JSONObject() 76 | .put("DummyProp", "build.json") 77 | .toString(); 78 | when(request.requestBody()).thenReturn(requestBody); 79 | 80 | GoPluginApiResponse response = new ValidatePublishArtifactConfigExecutor(request).execute(); 81 | 82 | String expectedResponse = new JSONArray().put( 83 | new JSONObject() 84 | .put("key", "Source") 85 | .put("message", "Source file must be specified.") 86 | ).toString(); 87 | 88 | JSONAssert.assertEquals(expectedResponse, response.responseBody(), true); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/PublishArtifactRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 20 | import com.google.gson.annotations.Expose; 21 | import com.google.gson.annotations.SerializedName; 22 | 23 | import java.util.Map; 24 | 25 | public class PublishArtifactRequest { 26 | @Expose 27 | @SerializedName("agent_working_directory") 28 | private String agentWorkingDir; 29 | 30 | @Expose 31 | @SerializedName("artifact_store") 32 | private ArtifactStore artifactStore; 33 | 34 | @Expose 35 | @SerializedName("artifact_plan") 36 | private ArtifactPlan artifactPlan; 37 | 38 | @Expose 39 | @SerializedName("environment_variables") 40 | private Map environmentVariables; 41 | 42 | public PublishArtifactRequest() { 43 | } 44 | 45 | public PublishArtifactRequest(ArtifactStore artifactStore, ArtifactPlan artifactPlan, String agentWorkingDir) { 46 | this.agentWorkingDir = agentWorkingDir; 47 | this.artifactStore = artifactStore; 48 | this.artifactPlan = artifactPlan; 49 | } 50 | 51 | public String getAgentWorkingDir() { 52 | return agentWorkingDir; 53 | } 54 | 55 | public ArtifactStore getArtifactStore() { 56 | return artifactStore; 57 | } 58 | 59 | public ArtifactPlan getArtifactPlan() { 60 | return artifactPlan; 61 | } 62 | 63 | public Map getEnvironmentVariables() { 64 | return environmentVariables; 65 | } 66 | 67 | public void setEnvironmentVariables(Map environmentVariables) { 68 | this.environmentVariables = environmentVariables; 69 | } 70 | 71 | public static PublishArtifactRequest fromJSON(String json) { 72 | return Util.GSON.fromJson(json, PublishArtifactRequest.class); 73 | } 74 | 75 | public String toJSON() { 76 | return Util.GSON.toJson(this); 77 | } 78 | 79 | @Override 80 | public boolean equals(Object o) { 81 | if (this == o) return true; 82 | if (!(o instanceof PublishArtifactRequest)) return false; 83 | 84 | PublishArtifactRequest that = (PublishArtifactRequest) o; 85 | 86 | if (agentWorkingDir != null ? !agentWorkingDir.equals(that.agentWorkingDir) : that.agentWorkingDir != null) 87 | return false; 88 | if (artifactStore != null ? !artifactStore.equals(that.artifactStore) : that.artifactStore != null) 89 | return false; 90 | return artifactPlan != null ? artifactPlan.equals(that.artifactPlan) : that.artifactPlan == null; 91 | } 92 | 93 | @Override 94 | public int hashCode() { 95 | int result = agentWorkingDir != null ? agentWorkingDir.hashCode() : 0; 96 | result = 31 * result + (artifactStore != null ? artifactStore.hashCode() : 0); 97 | result = 31 * result + (artifactPlan != null ? artifactPlan.hashCode() : 0); 98 | return result; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/utils/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.utils; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.annotation.FieldMetadata; 20 | import diogomrol.gocd.s3.artifact.plugin.annotation.FieldMetadataTypeAdapter; 21 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactPlanConfig; 22 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactPlanConfigTypeAdapter; 23 | import com.google.gson.Gson; 24 | import com.google.gson.GsonBuilder; 25 | 26 | import java.io.ByteArrayOutputStream; 27 | import java.io.IOException; 28 | import java.io.InputStream; 29 | import java.io.StringReader; 30 | import java.nio.charset.StandardCharsets; 31 | import java.nio.file.Path; 32 | import java.util.Properties; 33 | 34 | public class Util { 35 | public static String normalizePath(Path path) { 36 | return path.toString().replace("\\", "/"); 37 | } 38 | 39 | public static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() 40 | .serializeNulls() 41 | .registerTypeAdapter(ArtifactPlanConfig.class, new ArtifactPlanConfigTypeAdapter()) 42 | .registerTypeAdapter(FieldMetadata.class, new FieldMetadataTypeAdapter()) 43 | .create(); 44 | 45 | public static String readResource(String resourceFile) { 46 | return new String(readResourceBytes(resourceFile), StandardCharsets.UTF_8); 47 | } 48 | 49 | public static byte[] readResourceBytes(String resourceFile) { 50 | try (InputStream is = Util.class.getResourceAsStream(resourceFile)) { 51 | return readFully(is); 52 | } catch (IOException e) { 53 | throw new RuntimeException("Could not find resource " + resourceFile, e); 54 | } 55 | } 56 | 57 | private static byte[] readFully(InputStream input) throws IOException { 58 | byte[] buffer = new byte[8192]; 59 | int bytesRead; 60 | ByteArrayOutputStream output = new ByteArrayOutputStream(); 61 | while ((bytesRead = input.read(buffer)) != -1) { 62 | output.write(buffer, 0, bytesRead); 63 | } 64 | return output.toByteArray(); 65 | } 66 | 67 | public static String pluginId() { 68 | return getPluginProperties().getProperty("pluginId"); 69 | } 70 | 71 | public static Properties getPluginProperties() { 72 | String propertiesAsAString = readResource("/plugin.properties"); 73 | try { 74 | Properties properties = new Properties(); 75 | properties.load(new StringReader(propertiesAsAString)); 76 | return properties; 77 | } catch (IOException e) { 78 | throw new RuntimeException(e); 79 | } 80 | } 81 | 82 | public static boolean isNotBlank(final CharSequence cs) { 83 | return !isBlank(cs); 84 | } 85 | 86 | public static boolean isBlank(final CharSequence cs) { 87 | int strLen; 88 | if (cs == null || (strLen = cs.length()) == 0) { 89 | return true; 90 | } 91 | for (int i = 0; i < strLen; i++) { 92 | if (Character.isWhitespace(cs.charAt(i)) == false) { 93 | return false; 94 | } 95 | } 96 | return true; 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/S3ArtifactPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin; 18 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 19 | import com.thoughtworks.go.plugin.api.GoApplicationAccessor; 20 | import com.thoughtworks.go.plugin.api.GoPlugin; 21 | import com.thoughtworks.go.plugin.api.GoPluginIdentifier; 22 | import com.thoughtworks.go.plugin.api.annotation.Extension; 23 | import com.thoughtworks.go.plugin.api.annotation.Load; 24 | import com.thoughtworks.go.plugin.api.exceptions.UnhandledRequestTypeException; 25 | import com.thoughtworks.go.plugin.api.info.PluginContext; 26 | import com.thoughtworks.go.plugin.api.logging.Logger; 27 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 28 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 29 | import diogomrol.gocd.s3.artifact.plugin.executors.*; 30 | 31 | import java.util.Properties; 32 | 33 | @Extension 34 | public class S3ArtifactPlugin implements GoPlugin { 35 | public static final Logger LOG = Logger.getLoggerFor(S3ArtifactPlugin.class); 36 | private ConsoleLogger consoleLogger; 37 | 38 | @Load 39 | public void onLoad(PluginContext ctx) { 40 | final Properties properties = Util.getPluginProperties(); 41 | LOG.info(String.format("Loading plugin %s[%s].", properties.getProperty("name"), properties.getProperty("pluginId"))); 42 | } 43 | 44 | @Override 45 | public void initializeGoApplicationAccessor(GoApplicationAccessor accessor) { 46 | consoleLogger = ConsoleLogger.getLogger(accessor); 47 | } 48 | 49 | @Override 50 | public GoPluginApiResponse handle(GoPluginApiRequest request) { 51 | try { 52 | switch (Request.fromString(request.requestName())) { 53 | case REQUEST_GET_PLUGIN_ICON: 54 | return new GetPluginIconExecutor().execute(); 55 | case REQUEST_GET_PLUGIN_CAPABILITIES: 56 | return new GetCapabilitiesExecutor().execute(); 57 | case REQUEST_STORE_CONFIG_METADATA: 58 | return new GetArtifactStoreConfigMetadataExecutor().execute(); 59 | case REQUEST_STORE_CONFIG_VIEW: 60 | return new GetArtifactStoreViewExecutor().execute(); 61 | case REQUEST_STORE_CONFIG_VALIDATE: 62 | return new ValidateArtifactStoreConfigExecutor(request).execute(); 63 | case REQUEST_PUBLISH_ARTIFACT_METADATA: 64 | return new GetPublishArtifactConfigMetadataExecutor().execute(); 65 | case REQUEST_PUBLISH_ARTIFACT_VIEW: 66 | return new GetPublishArtifactViewExecutor().execute(); 67 | case REQUEST_PUBLISH_ARTIFACT_VALIDATE: 68 | return new ValidatePublishArtifactConfigExecutor(request).execute(); 69 | case REQUEST_FETCH_ARTIFACT_METADATA: 70 | return new GetFetchArtifactMetadataExecutor().execute(); 71 | case REQUEST_FETCH_ARTIFACT_VIEW: 72 | return new GetFetchArtifactViewExecutor().execute(); 73 | case REQUEST_FETCH_ARTIFACT_VALIDATE: 74 | return new ValidateFetchArtifactConfigExecutor().execute(); 75 | case REQUEST_PUBLISH_ARTIFACT: 76 | return new PublishArtifactExecutor(request, consoleLogger).execute(); 77 | case REQUEST_FETCH_ARTIFACT: 78 | return new FetchArtifactExecutor(request, consoleLogger).execute(); 79 | default: 80 | throw new UnhandledRequestTypeException(request.requestName()); 81 | } 82 | } catch (Exception e) { 83 | LOG.error("Error while executing request " + request.requestName(), e); 84 | throw new RuntimeException(e); 85 | } 86 | } 87 | 88 | @Override 89 | public GoPluginIdentifier pluginIdentifier() { 90 | return Constants.PLUGIN_IDENTIFIER; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/model/ArtifactStoreConfig.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.model; 18 | 19 | import com.google.common.collect.ImmutableSet; 20 | import com.google.common.collect.Lists; 21 | import com.google.gson.annotations.Expose; 22 | import com.google.gson.annotations.SerializedName; 23 | import diogomrol.gocd.s3.artifact.plugin.annotation.FieldMetadata; 24 | import diogomrol.gocd.s3.artifact.plugin.annotation.Validatable; 25 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationError; 26 | import diogomrol.gocd.s3.artifact.plugin.annotation.ValidationResult; 27 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 28 | 29 | import java.util.List; 30 | 31 | public class ArtifactStoreConfig implements Validatable { 32 | 33 | private static final ImmutableSet OPTIONAL_PROPERTIES = ImmutableSet.of("Region", "AWSAccessKey", "AWSSecretAccessKey"); 34 | private static final ImmutableSet AWS_ACCESS_PROPERTIES = ImmutableSet.of("AWSAccessKey", "AWSSecretAccessKey"); 35 | 36 | @Expose 37 | @SerializedName("S3Bucket") 38 | @FieldMetadata(key = "S3Bucket", required = true) 39 | private String s3bucket; 40 | 41 | @Expose 42 | @SerializedName("Region") 43 | @FieldMetadata(key = "Region", required = false, secure = false) 44 | private String region; 45 | 46 | @Expose 47 | @SerializedName("AWSAccessKey") 48 | @FieldMetadata(key = "AWSAccessKey", required = false) 49 | private String awsaccesskey; 50 | 51 | @Expose 52 | @SerializedName("AWSSecretAccessKey") 53 | @FieldMetadata(key = "AWSSecretAccessKey", required = false, secure = true) 54 | private String awssecretaccesskey; 55 | 56 | 57 | public ArtifactStoreConfig() { 58 | } 59 | 60 | public ArtifactStoreConfig(String s3bucket, String region, String awsaccesskey, String awssecretaccesskey) { 61 | this.s3bucket = s3bucket; 62 | this.region = region; 63 | this.awsaccesskey = awsaccesskey; 64 | this.awssecretaccesskey = awssecretaccesskey; 65 | } 66 | 67 | public String getS3bucket() { 68 | return s3bucket; 69 | } 70 | 71 | public String getRegion () { return region; } 72 | 73 | public String getAwsaccesskey() { 74 | return awsaccesskey; 75 | } 76 | 77 | public String getAwssecretaccesskey() { 78 | return awssecretaccesskey; 79 | } 80 | 81 | @Override 82 | public boolean equals(Object o) { 83 | if (this == o) return true; 84 | if (!(o instanceof ArtifactStoreConfig)) return false; 85 | 86 | ArtifactStoreConfig that = (ArtifactStoreConfig) o; 87 | 88 | if (s3bucket != null ? !s3bucket.equals(that.s3bucket) : that.s3bucket != null) return false; 89 | if (region != null ? !region.equals(that.region) : that.region != null) return false; 90 | if (awsaccesskey != null ? !awsaccesskey.equals(that.awsaccesskey) : that.awsaccesskey != null) return false; 91 | return awssecretaccesskey != null ? awssecretaccesskey.equals(that.awssecretaccesskey) : that.awssecretaccesskey == null; 92 | } 93 | 94 | @Override 95 | public int hashCode() { 96 | int result = s3bucket != null ? s3bucket.hashCode() : 0; 97 | result = 31 * result + (region != null ? region.hashCode() : 0); 98 | result = 31 * result + (awsaccesskey != null ? awsaccesskey.hashCode() : 0); 99 | result = 31 * result + (awssecretaccesskey != null ? awssecretaccesskey.hashCode() : 0); 100 | return result; 101 | } 102 | 103 | public static ArtifactStoreConfig fromJSON(String json) { 104 | return Util.GSON.fromJson(json, ArtifactStoreConfig.class); 105 | } 106 | 107 | @Override 108 | public ValidationResult validate() { 109 | List validationErrors = Lists.newArrayList(); 110 | validationErrors.addAll(validateAllFieldsAsRequired(OPTIONAL_PROPERTIES)); 111 | validationErrors.addAll(validateAllOrNoneRequired(AWS_ACCESS_PROPERTIES)); 112 | 113 | return new ValidationResult(validationErrors); 114 | 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/ValidateArtifactStoreConfigExecutorExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 20 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 21 | import org.json.JSONException; 22 | import org.json.JSONObject; 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.mockito.Mock; 26 | import org.skyscreamer.jsonassert.JSONAssert; 27 | import org.skyscreamer.jsonassert.JSONCompareMode; 28 | 29 | import static org.mockito.Mockito.when; 30 | import static org.mockito.MockitoAnnotations.initMocks; 31 | 32 | public class ValidateArtifactStoreConfigExecutorExecutorTest { 33 | @Mock 34 | private GoPluginApiRequest request; 35 | 36 | @Before 37 | public void setUp() { 38 | initMocks(this); 39 | } 40 | 41 | @Test 42 | public void shouldRejectMissingFields() throws Exception { 43 | when(request.requestBody()).thenReturn("{}"); 44 | 45 | final GoPluginApiResponse response = new ValidateArtifactStoreConfigExecutor(request).execute(); 46 | 47 | String expectedJSON = "[\n" + 48 | " {\n" + 49 | " \"key\": \"S3Bucket\",\n" + 50 | " \"message\": \"S3Bucket must not be blank.\"\n" + 51 | " }\n" + 52 | "]"; 53 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 54 | } 55 | 56 | @Test 57 | public void shouldRejectAWSAccessKeyAlone() throws Exception { 58 | String requestBody = new JSONObject() 59 | .put("S3Bucket", "http://localhost/index") 60 | .put("Region", "us-west-1") 61 | .put("AWSAccessKey", "chuck-norris") 62 | .toString(); 63 | when(request.requestBody()).thenReturn(requestBody); 64 | 65 | final GoPluginApiResponse response = new ValidateArtifactStoreConfigExecutor(request).execute(); 66 | 67 | String expectedJSON = "[\n" + 68 | " {\n" + 69 | " \"key\": \"AWSAccessKey\",\n" + 70 | " \"message\": \"AWSAccessKey and AWSSecretAccessKey must be filled altogether, if required.\"\n" + 71 | " },\n" + 72 | " {\n" + 73 | " \"key\": \"AWSSecretAccessKey\",\n" + 74 | " \"message\": \"AWSAccessKey and AWSSecretAccessKey must be filled altogether, if required.\"\n" + 75 | " }\n" + 76 | "]"; 77 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 78 | } 79 | 80 | @Test 81 | public void shouldRejectAWSSecretAccessKeyAlone() throws Exception { 82 | String requestBody = new JSONObject() 83 | .put("S3Bucket", "http://localhost/index") 84 | .put("Region", "us-west-1") 85 | .put("AWSSecretAccessKey", "chuck-norris-doesnt-need-passwords") 86 | .toString(); 87 | when(request.requestBody()).thenReturn(requestBody); 88 | 89 | final GoPluginApiResponse response = new ValidateArtifactStoreConfigExecutor(request).execute(); 90 | 91 | String expectedJSON = "[\n" + 92 | " {\n" + 93 | " \"key\": \"AWSAccessKey\",\n" + 94 | " \"message\": \"AWSAccessKey and AWSSecretAccessKey must be filled altogether, if required.\"\n" + 95 | " },\n" + 96 | " {\n" + 97 | " \"key\": \"AWSSecretAccessKey\",\n" + 98 | " \"message\": \"AWSAccessKey and AWSSecretAccessKey must be filled altogether, if required.\"\n" + 99 | " }\n" + 100 | "]"; 101 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 102 | } 103 | 104 | @Test 105 | public void shouldAcceptMinimalFields() throws Exception { 106 | when(request.requestBody()).thenReturn("{}"); 107 | 108 | final GoPluginApiResponse response = new ValidateArtifactStoreConfigExecutor(request).execute(); 109 | 110 | String expectedJSON = "[\n" + 111 | " {\n" + 112 | " \"key\": \"S3Bucket\",\n" + 113 | " \"message\": \"S3Bucket must not be blank.\"\n" + 114 | " }\n" + 115 | "]"; 116 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 117 | } 118 | 119 | @Test 120 | public void shouldAcceptAllFields() throws JSONException { 121 | String requestBody = new JSONObject() 122 | .put("S3Bucket", "http://localhost/index") 123 | .put("Region", "us-west-1") 124 | .put("AWSAccessKey", "chuck-norris") 125 | .put("AWSSecretAccessKey", "chuck-norris-doesnt-need-passwords") 126 | .toString(); 127 | when(request.requestBody()).thenReturn(requestBody); 128 | 129 | final GoPluginApiResponse response = new ValidateArtifactStoreConfigExecutor(request).execute(); 130 | String expectedJSON = "[]"; 131 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), JSONCompareMode.NON_EXTENSIBLE); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS='"-Xmx64m"' 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /tasks: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -Eeuo pipefail 4 | 5 | RELEASER_VERSION="2.1.0" 6 | SECRET_OPS_VERSION="0.6.2" 7 | 8 | SECRET_OPS_FILE="ops/secret-ops" 9 | SECRET_OPS_TAR_FILE="ops/secret-ops-${SECRET_OPS_VERSION}.tar.gz" 10 | RELEASER_FILE="ops/releaser-${RELEASER_VERSION}" 11 | 12 | VAULT_ADDR="https://vault.kudulab.io:8200" 13 | 14 | mkdir -p ops 15 | if [[ ! -f $RELEASER_FILE ]];then 16 | wget --quiet -O $RELEASER_FILE https://github.com/kudulab/releaser/releases/download/${RELEASER_VERSION}/releaser 17 | fi 18 | source $RELEASER_FILE 19 | if [[ ! -f $SECRET_OPS_TAR_FILE ]];then 20 | wget --quiet -O $SECRET_OPS_TAR_FILE https://github.com/kudulab/secret-ops/releases/download/${SECRET_OPS_VERSION}/secret-ops.tar.gz 21 | tar -xf $SECRET_OPS_TAR_FILE -C ops 22 | fi 23 | source $SECRET_OPS_FILE 24 | 25 | GOCD_ENDPOINT="http://gocd:8153/go" 26 | KUDU_SERVICE=gocd-s3-artifact-plugin-test 27 | 28 | function tf_ops { 29 | operation=$1 30 | 31 | cd e2e/terraform/ 32 | terraform init -backend-config key=kudu-${KUDU_SERVICE}/terraform.tfstate 33 | terraform get # modules 34 | if [[ "${operation}" == "create" ]]; then 35 | terraform plan -out="kudu_deployment.tfplan" 36 | elif [[ "${operation}" == "destroy" ]]; then 37 | terraform plan -out="kudu_deployment.tfplan" -destroy 38 | else 39 | echo "Unknown operation: ${operation}" 40 | exit 1 41 | fi 42 | terraform apply kudu_deployment.tfplan 43 | 44 | terraform output -json > tf-out.json 45 | } 46 | 47 | function source_tf { 48 | AWS_BUCKET=$(cat e2e/terraform/tf-out.json | jq -r '.s3_bucket.value') 49 | AWS_ACCESS_KEY=$(cat e2e/terraform/tf-out.json | jq -r '.s3_key_id.value') 50 | AWS_SECRET_ACCESS_KEY=$(cat e2e/terraform/tf-out.json | jq -r '.s3_key.value') 51 | export AWS_BUCKET 52 | export AWS_ACCESS_KEY 53 | export AWS_SECRET_ACCESS_KEY 54 | } 55 | 56 | command="$1" 57 | case "${command}" in 58 | _tf_apply) 59 | tf_ops "$2" 60 | ;; 61 | tf_apply) 62 | set +u 63 | if [ -z "${AWS_ACCESS_KEY_ID}" ] || [ -z "${AWS_SECRET_ACCESS_KEY}" ]; then 64 | echo "AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY must be set." 65 | if [[ -n "${VAULT_TOKEN}" ]]; then 66 | echo "Trying to get secrets from vault" 67 | AWS_ACCESS_KEY_ID=$(vault read -field=key_id secret/gocd-s3-plugin/aws) 68 | AWS_SECRET_ACCESS_KEY=$(vault read -field=secret_key secret/gocd-s3-plugin/aws) 69 | export AWS_ACCESS_KEY_ID 70 | export AWS_SECRET_ACCESS_KEY 71 | else 72 | exit 1 73 | fi 74 | fi 75 | dojo -c e2e/terraform/Dojofile "./tasks _tf_apply $2" 76 | ;; 77 | wait_online) 78 | sleep 10 79 | for i in {1..200}; do 80 | HTTP_RESPONSE=$(curl --silent --write-out "HTTPSTATUS:%{http_code}" \ 81 | "${GOCD_ENDPOINT}/api/v1/health" --insecure \ 82 | -H 'Accept: application/vnd.go.cd.v1+json' \ 83 | -H 'Content-Type: application/json' \ 84 | ) 85 | HTTP_STATUS=$(echo $HTTP_RESPONSE | tr -d '\n' | sed -e 's/.*HTTPSTATUS://') 86 | if [ ! $HTTP_STATUS -eq 200 ]; then 87 | echo "GoCD is not up yet" >&2 88 | sleep 1 89 | continue 90 | fi 91 | HTTP_BODY=$(echo $HTTP_RESPONSE | sed -e 's/HTTPSTATUS\:.*//g') 92 | echo $HTTP_BODY 93 | exit 0 94 | done 95 | ;; 96 | _setup_gocd) 97 | ./tasks wait_online 98 | bash 99 | ;; 100 | setup_gocd) 101 | export GOCDCLI_SERVER_URL=$GOCD_ENDPOINT 102 | dojo "./tasks _setup_gocd" 103 | ;; 104 | _build_test) 105 | gradle check assemble 106 | ;; 107 | build_test) 108 | source_tf 109 | dojo "./tasks _build_test" 110 | ;; 111 | set_version) 112 | set +u 113 | releaser::bump_changelog_version "$2" "$3" 114 | next_version=$(releaser::get_last_version_from_changelog "${changelog_file}") 115 | releaser::set_version_in_file "pluginVersion = " "build.gradle" "${next_version}" 116 | ;; 117 | bump) 118 | set +u 119 | releaser::bump_changelog_version "" true 120 | next_version=$(releaser::get_last_version_from_changelog "${changelog_file}") 121 | releaser::set_version_in_file "pluginVersion = " "build.gradle" "${next_version}" 122 | ;; 123 | verify_version) 124 | releaser::verify_release_ready 125 | ;; 126 | prepare_release) 127 | next_version=$(releaser::get_last_version_from_changelog "${changelog_file}") 128 | releaser::set_version_in_changelog "${changelog_file}" "${next_version}" false 129 | releaser::set_version_in_file "pluginVersion = " "build.gradle" "${next_version}" 130 | ;; 131 | commit) 132 | git add "${changelog_file}" 133 | git add "build.gradle" 134 | git commit --author "Tomasz Setkowski " -m "Version bump" 135 | ;; 136 | github_release) 137 | set +u 138 | if [[ -z "${GITHUB_TOKEN}" ]]; then 139 | echo "GITHUB_TOKEN must be set for release" 140 | if [[ -n "${VAULT_TOKEN}" ]]; then 141 | echo "Trying to get GITHUB_TOKEN from vault" 142 | GITHUB_TOKEN=$(vault read -field=token secret/gocd-s3-plugin/github) 143 | export GITHUB_TOKEN 144 | else 145 | exit 1 146 | fi 147 | fi 148 | releaser::prepare_github_release_bin 149 | 150 | VERSION=$(ls build/libs/s3-artifact-plugin-*.jar | grep -Eo '[0-9]+\.[0-9]+\.[0-9]+') 151 | 152 | changelog_version=$(releaser::get_last_version_from_changelog "${changelog_file}") 153 | if [ $changelog_version != $VERSION ]; then 154 | echo "changelog version $changelog_version does not match file version $VERSION" 155 | exit 2 156 | fi 157 | 158 | $GHRELEASE_BIN release \ 159 | --user Diogomrol \ 160 | --repo gocd-s3-artifact-plugin \ 161 | --tag $VERSION \ 162 | --name $VERSION \ 163 | --pre-release 164 | 165 | cd build/libs/ 166 | for file in s3-artifact-plugin-*.jar*; do 167 | $GHRELEASE_BIN upload \ 168 | --user Diogomrol \ 169 | --repo gocd-s3-artifact-plugin \ 170 | --tag $VERSION \ 171 | --name "$file" \ 172 | --file "$file" 173 | done 174 | ;; 175 | generate_vault_token) 176 | vault_token=$(vault token create -orphan -ttl=168h -policy=gocd-s3-plugin -field token -metadata gocd_renew=true) 177 | secured_token_gocd=$(secret_ops::encrypt_with_gocd_top "${vault_token}") 178 | echo "Generated token: ${vault_token} and encrypted by GoCD server" 179 | secret_ops::insert_vault_token_gocd_yaml "${secured_token_gocd}" 180 | ;; 181 | *) 182 | echo "Invalid command: '${command}'" 183 | exit 1 184 | ;; 185 | esac 186 | set +e 187 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/PublishArtifactExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import diogomrol.gocd.s3.artifact.plugin.ConsoleLogger; 20 | import diogomrol.gocd.s3.artifact.plugin.S3ClientFactory; 21 | import com.amazonaws.services.s3.AmazonS3; 22 | import com.amazonaws.services.s3.model.ObjectMetadata; 23 | import com.amazonaws.services.s3.model.PutObjectRequest; 24 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 25 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 26 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 27 | import diogomrol.gocd.s3.artifact.plugin.model.*; 28 | 29 | import java.io.File; 30 | import java.nio.file.Path; 31 | import java.nio.file.Paths; 32 | import java.util.List; 33 | 34 | import static diogomrol.gocd.s3.artifact.plugin.S3ArtifactPlugin.LOG; 35 | import static diogomrol.gocd.s3.artifact.plugin.utils.Util.normalizePath; 36 | 37 | public class PublishArtifactExecutor implements RequestExecutor { 38 | private final PublishArtifactRequest publishArtifactRequest; 39 | private final PublishArtifactResponse publishArtifactResponse; 40 | private final ConsoleLogger consoleLogger; 41 | private final S3ClientFactory clientFactory; 42 | private AntDirectoryScanner scanner; 43 | 44 | public PublishArtifactExecutor(GoPluginApiRequest request, ConsoleLogger consoleLogger) { 45 | this(request, consoleLogger, S3ClientFactory.instance()); 46 | } 47 | 48 | PublishArtifactExecutor(GoPluginApiRequest request, ConsoleLogger consoleLogger, S3ClientFactory clientFactory) { 49 | this.publishArtifactRequest = PublishArtifactRequest.fromJSON(request.requestBody()); 50 | this.consoleLogger = consoleLogger; 51 | this.clientFactory = clientFactory; 52 | scanner = new AntDirectoryScanner(); 53 | publishArtifactResponse = new PublishArtifactResponse(); 54 | } 55 | 56 | @Override 57 | public GoPluginApiResponse execute() { 58 | ArtifactPlan artifactPlan = publishArtifactRequest.getArtifactPlan(); 59 | final ArtifactStoreConfig artifactStoreConfig = publishArtifactRequest.getArtifactStore().getArtifactStoreConfig(); 60 | try { 61 | final AmazonS3 s3 = clientFactory.s3(artifactStoreConfig); 62 | final String sourcePattern = artifactPlan.getArtifactPlanConfig().getSource(); 63 | String destinationFolder = artifactPlan.getArtifactPlanConfig().getDestination(); 64 | EnvironmentVariableResolver envResolver = new EnvironmentVariableResolver(destinationFolder, "Destination"); 65 | destinationFolder = envResolver.resolve(publishArtifactRequest.getEnvironmentVariables()); 66 | final String s3bucket = artifactStoreConfig.getS3bucket(); 67 | final String workingDir = publishArtifactRequest.getAgentWorkingDir(); 68 | String s3InbucketPath; 69 | if(!destinationFolder.isEmpty()) { 70 | s3InbucketPath = normalizePath(Paths.get(destinationFolder)); 71 | } 72 | else { 73 | s3InbucketPath = ""; 74 | } 75 | 76 | List matchingFiles = scanner.getFilesMatchingPattern(new File(workingDir), sourcePattern); 77 | if(matchingFiles.size() == 0) { 78 | String noFilesMsg = String.format("No files are matching pattern: %s", sourcePattern); 79 | consoleLogger.error(noFilesMsg); 80 | LOG.warn(noFilesMsg); 81 | //TODO: tomzo consider handling no artifacts failure in GoCD core 82 | return DefaultGoPluginApiResponse.badRequest(noFilesMsg); 83 | } 84 | else if(matchingFiles.size() == 1) { 85 | File sourceFile = matchingFiles.get(0); 86 | String s3Key = normalizePath(Paths.get(s3InbucketPath, sourceFile.toPath().toString())); 87 | PutObjectRequest request = new PutObjectRequest(s3bucket, s3Key, new File(Paths.get(workingDir, sourceFile.toString()).toString())); 88 | ObjectMetadata metadata = new ObjectMetadata(); 89 | request.setMetadata(metadata); 90 | s3.putObject(request); 91 | publishArtifactResponse.addMetadata("Source", sourceFile.toString()); 92 | publishArtifactResponse.addMetadata("IsFile", true); 93 | consoleLogger.info(String.format("Source file `%s` successfully pushed to S3 bucket `%s`.", sourceFile, artifactStoreConfig.getS3bucket())); 94 | } 95 | else { 96 | // upload many files 97 | for(File sourceFile : matchingFiles) { 98 | String s3Key = normalizePath(Paths.get(s3InbucketPath, sourceFile.getPath())); 99 | PutObjectRequest request = new PutObjectRequest(s3bucket, s3Key, new File(Paths.get(workingDir, sourceFile.toString()).toString())); 100 | ObjectMetadata metadata = new ObjectMetadata(); 101 | request.setMetadata(metadata); 102 | s3.putObject(request); 103 | consoleLogger.info(String.format("Source file `%s` successfully pushed to S3 bucket `%s`.", sourceFile, artifactStoreConfig.getS3bucket())); 104 | } 105 | publishArtifactResponse.addMetadata("Source", sourcePattern); 106 | publishArtifactResponse.addMetadata("IsFile", false); 107 | } 108 | publishArtifactResponse.addMetadata("Destination", s3InbucketPath); 109 | 110 | return DefaultGoPluginApiResponse.success(publishArtifactResponse.toJSON()); 111 | } catch (Exception e) { 112 | consoleLogger.error(String.format("Failed to publish %s: %s", artifactPlan, e)); 113 | LOG.error(String.format("Failed to publish %s: %s", artifactPlan, e.getMessage()), e); 114 | return DefaultGoPluginApiResponse.error(String.format("Failed to publish %s: %s", artifactPlan, e.getMessage())); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/PublishArtifactExecutorIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.executors; 2 | 3 | import com.amazonaws.SdkClientException; 4 | import com.amazonaws.services.s3.AmazonS3; 5 | import com.amazonaws.services.s3.model.ObjectListing; 6 | import com.amazonaws.services.s3.model.PutObjectRequest; 7 | import com.amazonaws.services.s3.model.S3ObjectSummary; 8 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 9 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 10 | import diogomrol.gocd.s3.artifact.plugin.ConsoleLogger; 11 | import diogomrol.gocd.s3.artifact.plugin.IntegrationTests; 12 | import diogomrol.gocd.s3.artifact.plugin.S3ClientFactory; 13 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactPlan; 14 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactStore; 15 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactStoreConfig; 16 | import diogomrol.gocd.s3.artifact.plugin.model.PublishArtifactRequest; 17 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 18 | import org.json.JSONException; 19 | import org.junit.Before; 20 | import org.junit.Rule; 21 | import org.junit.Test; 22 | import org.junit.experimental.categories.Category; 23 | import org.junit.rules.ExpectedException; 24 | import org.junit.rules.TemporaryFolder; 25 | import org.mockito.ArgumentCaptor; 26 | import org.mockito.Captor; 27 | import org.mockito.Mock; 28 | import org.skyscreamer.jsonassert.JSONAssert; 29 | import org.skyscreamer.jsonassert.JSONCompareMode; 30 | 31 | import java.io.File; 32 | import java.io.IOException; 33 | import java.nio.file.Files; 34 | import java.nio.file.Path; 35 | import java.nio.file.Paths; 36 | import java.util.List; 37 | import java.util.Optional; 38 | 39 | import static org.assertj.core.api.Assertions.assertThat; 40 | import static org.mockito.ArgumentMatchers.any; 41 | import static org.mockito.Mockito.times; 42 | import static org.mockito.Mockito.verify; 43 | import static org.mockito.Mockito.when; 44 | import static org.mockito.MockitoAnnotations.initMocks; 45 | 46 | @Category(IntegrationTests.class) 47 | public class PublishArtifactExecutorIntegrationTest { 48 | @Rule 49 | public TemporaryFolder tmpFolder = new TemporaryFolder(); 50 | 51 | @Mock 52 | private GoPluginApiRequest request; 53 | @Mock 54 | private ConsoleLogger consoleLogger; 55 | private S3ClientFactory s3ClientFactory; 56 | 57 | private File agentWorkingDir; 58 | private AmazonS3 s3Client; 59 | 60 | ArtifactStoreConfig storeConfig; 61 | private String bucketName; 62 | 63 | @Before 64 | public void setUp() throws IOException, SdkClientException { 65 | initMocks(this); 66 | s3ClientFactory = new S3ClientFactory(); 67 | agentWorkingDir = tmpFolder.newFolder("go-agent"); 68 | bucketName = System.getenv("AWS_BUCKET"); 69 | if(Util.isBlank(bucketName)) 70 | throw new RuntimeException("Must set AWS_BUCKET env var"); 71 | storeConfig = new ArtifactStoreConfig(bucketName, "eu-west-1", System.getenv("AWS_ACCESS_KEY"), System.getenv("AWS_SECRET_ACCESS_KEY")); 72 | s3Client = s3ClientFactory.s3(storeConfig); 73 | ObjectListing listing = s3Client.listObjects( bucketName); 74 | List summaries = listObjects(listing); 75 | for(S3ObjectSummary o : summaries) { 76 | s3Client.deleteObject(bucketName, o.getKey()); 77 | } 78 | } 79 | 80 | private List listObjects(ObjectListing listing) { 81 | List summaries = listing.getObjectSummaries(); 82 | while (listing.isTruncated()) { 83 | listing = s3Client.listNextBatchOfObjects (listing); 84 | summaries.addAll (listing.getObjectSummaries()); 85 | } 86 | return summaries; 87 | } 88 | 89 | @Test 90 | public void shouldPublishArtifactFileWhenDestinationFolder() throws IOException { 91 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "build.json", Optional.of("DestinationFolder")); 92 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 93 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, agentWorkingDir.getAbsolutePath()); 94 | 95 | Path path = Paths.get(agentWorkingDir.getAbsolutePath(), "build.json"); 96 | Files.write(path, "{\"content\":\"example artifact file\"}".getBytes()); 97 | 98 | when(request.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 99 | 100 | final GoPluginApiResponse response = new PublishArtifactExecutor(request, consoleLogger, s3ClientFactory).execute(); 101 | assertThat(response.responseCode()).isEqualTo(200); 102 | 103 | ObjectListing listing = s3Client.listObjects( bucketName, "DestinationFolder" ); 104 | List summaries = listObjects(listing); 105 | assertThat(summaries).hasOnlyOneElementSatisfying(s -> assertThat(s.getKey()).isEqualTo("DestinationFolder/build.json")); 106 | } 107 | 108 | @Test 109 | public void shouldPublishArtifactFilesMatchingPatternWhenNoDestinationFolder() throws IOException { 110 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "*.json", Optional.empty()); 111 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 112 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, agentWorkingDir.getAbsolutePath()); 113 | 114 | Path buildJsonPath = Paths.get(agentWorkingDir.getAbsolutePath(), "build.json"); 115 | Files.write(buildJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 116 | Path testJsonPath = Paths.get(agentWorkingDir.getAbsolutePath(), "test.json"); 117 | Files.write(testJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 118 | Path binPath = Paths.get(agentWorkingDir.getAbsolutePath(), "test.bin"); 119 | Files.write(binPath, "example binary artifact".getBytes()); 120 | 121 | when(request.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 122 | 123 | final GoPluginApiResponse response = new PublishArtifactExecutor(request, consoleLogger, s3ClientFactory).execute(); 124 | assertThat(response.responseCode()).isEqualTo(200); 125 | 126 | ObjectListing listing = s3Client.listObjects( bucketName); 127 | List summaries = listObjects(listing); 128 | assertThat(summaries) 129 | .extracting(S3ObjectSummary::getKey) 130 | .containsExactly("build.json", "test.json"); 131 | } 132 | 133 | 134 | @Test 135 | public void shouldPublishArtifactFilesMatchingPatternWhenDestinationFolder() throws IOException { 136 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "**/*.json", Optional.of("DestinationFolder")); 137 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 138 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, agentWorkingDir.getAbsolutePath()); 139 | 140 | Path subDir = Paths.get(agentWorkingDir.getAbsolutePath(), "bin"); 141 | subDir.toFile().mkdir(); 142 | 143 | Path buildJsonPath = Paths.get(subDir.toAbsolutePath().toString(), "build.json"); 144 | Files.write(buildJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 145 | Path testJsonPath = Paths.get(subDir.toAbsolutePath().toString(), "test.json"); 146 | Files.write(testJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 147 | Path binPath = Paths.get(subDir.toAbsolutePath().toString(), "test.bin"); 148 | Files.write(binPath, "example binary artifact".getBytes()); 149 | 150 | when(request.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 151 | 152 | final GoPluginApiResponse response = new PublishArtifactExecutor(request, consoleLogger, s3ClientFactory).execute(); 153 | assertThat(response.responseCode()).isEqualTo(200); 154 | 155 | ObjectListing listing = s3Client.listObjects( bucketName); 156 | List summaries = listObjects(listing); 157 | assertThat(summaries) 158 | .extracting(S3ObjectSummary::getKey) 159 | .containsExactly("DestinationFolder/bin/build.json", "DestinationFolder/bin/test.json"); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /src/main/java/diogomrol/gocd/s3/artifact/plugin/executors/FetchArtifactExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 ThoughtWorks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package diogomrol.gocd.s3.artifact.plugin.executors; 18 | 19 | import com.amazonaws.services.s3.model.*; 20 | import diogomrol.gocd.s3.artifact.plugin.ConsoleLogger; 21 | import diogomrol.gocd.s3.artifact.plugin.S3ClientFactory; 22 | import diogomrol.gocd.s3.artifact.plugin.model.AntDirectoryScanner; 23 | import diogomrol.gocd.s3.artifact.plugin.model.ArtifactStoreConfig; 24 | import diogomrol.gocd.s3.artifact.plugin.model.FetchArtifactConfig; 25 | import diogomrol.gocd.s3.artifact.plugin.model.FetchArtifactRequest; 26 | import diogomrol.gocd.s3.artifact.plugin.utils.Util; 27 | import com.amazonaws.services.s3.AmazonS3; 28 | import com.google.gson.annotations.Expose; 29 | import com.google.gson.annotations.SerializedName; 30 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 31 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 32 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 33 | import java.io.*; 34 | import java.nio.file.Paths; 35 | import java.util.Arrays; 36 | import java.util.List; 37 | import java.util.Map; 38 | 39 | import static diogomrol.gocd.s3.artifact.plugin.S3ArtifactPlugin.LOG; 40 | import static diogomrol.gocd.s3.artifact.plugin.utils.Util.normalizePath; 41 | import static java.lang.String.format; 42 | 43 | public class FetchArtifactExecutor implements RequestExecutor { 44 | private FetchArtifactRequest fetchArtifactRequest; 45 | private final ConsoleLogger consoleLogger; 46 | private S3ClientFactory clientFactory; 47 | 48 | public FetchArtifactExecutor(GoPluginApiRequest request, ConsoleLogger consoleLogger) { 49 | this(request, consoleLogger, S3ClientFactory.instance()); 50 | } 51 | 52 | public FetchArtifactExecutor(GoPluginApiRequest request, ConsoleLogger consoleLogger, S3ClientFactory clientFactory) 53 | { 54 | this(FetchArtifactRequest.fromJSON(request.requestBody()), consoleLogger, clientFactory); 55 | } 56 | 57 | public FetchArtifactExecutor(FetchArtifactRequest fetchArtifactRequest, ConsoleLogger consoleLogger, S3ClientFactory clientFactory) { 58 | this.fetchArtifactRequest = fetchArtifactRequest; 59 | this.consoleLogger = consoleLogger; 60 | this.clientFactory = clientFactory; 61 | } 62 | 63 | @Override 64 | public GoPluginApiResponse execute() { 65 | try { 66 | final Map artifactMetadata = fetchArtifactRequest.getMetadata(); 67 | validateMetadata(artifactMetadata); 68 | 69 | FetchArtifactConfig fetchConfig = fetchArtifactRequest.getFetchArtifactConfig(); 70 | String fetchSubPath = fetchConfig.getSubPath(); 71 | boolean fetchIsFile = fetchConfig.getIsFile(); 72 | 73 | final String workingDir = fetchArtifactRequest.getAgentWorkingDir(); 74 | final String gocdSourcePatternOrFilePath = (String)artifactMetadata.get("Source"); 75 | String awsDestinationPath = (String) artifactMetadata.get("Destination"); 76 | if(Util.isBlank(awsDestinationPath)) 77 | awsDestinationPath = ""; 78 | boolean sourceIsFile = (boolean)artifactMetadata.get("IsFile"); 79 | 80 | AmazonS3 s3 = clientFactory.s3(fetchArtifactRequest.getArtifactStoreConfig()); 81 | String bucketName = fetchArtifactRequest.getArtifactStoreConfig().getS3bucket(); 82 | String s3InbucketPath; 83 | 84 | String targetFile; 85 | if(sourceIsFile) { 86 | targetFile = Paths.get(gocdSourcePatternOrFilePath).getFileName().toString(); 87 | s3InbucketPath = normalizePath(Paths.get(awsDestinationPath, gocdSourcePatternOrFilePath)); 88 | } 89 | else { 90 | if(fetchIsFile) { 91 | if(Util.isBlank(fetchSubPath)) { 92 | String errMsg = "Invalid Fetch Configuration: Fetching a single file requires to specify a subpath when multiple artifacts were published"; 93 | consoleLogger.error(errMsg); 94 | LOG.error(errMsg); 95 | return DefaultGoPluginApiResponse.incompleteRequest(errMsg); 96 | } 97 | targetFile = Paths.get(fetchSubPath).getFileName().toString(); 98 | s3InbucketPath = normalizePath(Paths.get(awsDestinationPath, fetchSubPath)); 99 | } 100 | else { 101 | String prefix; 102 | if(Util.isBlank(fetchSubPath) && Util.isBlank(awsDestinationPath) ) 103 | prefix = ""; 104 | else if(!Util.isBlank(fetchSubPath) && Util.isBlank(awsDestinationPath) ) 105 | prefix = fetchSubPath; 106 | else if(Util.isBlank(fetchSubPath) && !Util.isBlank(awsDestinationPath) ) 107 | prefix = awsDestinationPath; 108 | else 109 | prefix = normalizePath(Paths.get(awsDestinationPath, fetchSubPath)); 110 | 111 | ObjectListing listing = Util.isBlank(prefix) ? s3.listObjects(bucketName) : s3.listObjects(bucketName, prefix); 112 | consoleLogger.info(String.format("Retrieving multiple files from S3 bucket `%s` using prefix `%s`", bucketName, prefix)); 113 | int count = 0; 114 | while(true) { 115 | for(S3ObjectSummary obj : listing.getObjectSummaries()) { 116 | targetFile = obj.getKey().replaceFirst(prefix, ""); 117 | File outFile = getTargetFile(fetchConfig, workingDir, targetFile); 118 | s3InbucketPath = obj.getKey(); 119 | LOG.info(String.format("Retrieving file `%s` from S3 bucket `%s`.", s3InbucketPath, bucketName)); 120 | GetObjectRequest getRequest = new GetObjectRequest(bucketName, s3InbucketPath); 121 | s3.getObject(getRequest, outFile); 122 | count++; 123 | } 124 | if(listing.isTruncated()) 125 | listing = s3.listNextBatchOfObjects (listing); 126 | else 127 | break; 128 | } 129 | if(count > 0) { 130 | consoleLogger.info(String.format("Successfully downloaded `%s` files from S3 bucket `%s` using prefix `%s`", count, bucketName, prefix)); 131 | return DefaultGoPluginApiResponse.success(""); 132 | } 133 | else { 134 | String message = String.format("No objects are matching prefix `%s` in S3 bucket `%s`", prefix, bucketName); 135 | consoleLogger.error(message); 136 | LOG.error(message); 137 | return DefaultGoPluginApiResponse.badRequest(message); 138 | } 139 | } 140 | } 141 | File outFile = getTargetFile(fetchConfig, workingDir, targetFile); 142 | consoleLogger.info(String.format("Retrieving file `%s` from S3 bucket `%s`.", s3InbucketPath, bucketName)); 143 | LOG.info(String.format("Retrieving file `%s` from S3 bucket `%s`.", s3InbucketPath, bucketName)); 144 | GetObjectRequest getRequest = new GetObjectRequest(bucketName, s3InbucketPath); 145 | s3.getObject(getRequest, outFile); 146 | 147 | consoleLogger.info(String.format("Source `%s` successfully pulled from S3 bucket `%s` to `%s`.", s3InbucketPath, bucketName, outFile)); 148 | 149 | return DefaultGoPluginApiResponse.success(""); 150 | } catch (Exception e) { 151 | final String message = format("Failed pull source file: %s", e); 152 | consoleLogger.error(message); 153 | LOG.error(message); 154 | return DefaultGoPluginApiResponse.error(message); 155 | } 156 | } 157 | 158 | private File getTargetFile(FetchArtifactConfig fetchConfig, String workingDir, String targetFile) { 159 | File outFile; 160 | if(Util.isBlank(fetchConfig.getDestination())) { 161 | outFile = new File(Paths.get(workingDir, targetFile).toString()); 162 | } 163 | else { 164 | outFile = new File(Paths.get(workingDir, fetchConfig.getDestination(), targetFile).toString()); 165 | } 166 | return outFile; 167 | } 168 | 169 | public void validateMetadata(Map artifactMap) { 170 | if (artifactMap == null) { 171 | throw new RuntimeException(String.format("Cannot fetch the source file from S3: Invalid metadata received from the GoCD server. The artifact metadata is null.")); 172 | } 173 | 174 | for(String requiredKey : Arrays.asList("Source", "Destination", "IsFile")) { 175 | if (!artifactMap.containsKey(requiredKey)) { 176 | throw new RuntimeException(String.format("Cannot fetch the source file from S3: Invalid metadata received from the GoCD server. The artifact metadata must contain the key `%s`.", requiredKey)); 177 | } 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/test/java/diogomrol/gocd/s3/artifact/plugin/executors/PublishAndFetchIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package diogomrol.gocd.s3.artifact.plugin.executors; 2 | 3 | import com.amazonaws.SdkClientException; 4 | import com.amazonaws.services.s3.AmazonS3; 5 | import com.amazonaws.services.s3.model.ObjectListing; 6 | import com.amazonaws.services.s3.model.S3ObjectSummary; 7 | import com.google.gson.Gson; 8 | import com.google.gson.reflect.TypeToken; 9 | import com.thoughtworks.go.plugin.api.request.GoPluginApiRequest; 10 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 11 | import diogomrol.gocd.s3.artifact.plugin.ConsoleLogger; 12 | import diogomrol.gocd.s3.artifact.plugin.IntegrationTests; 13 | import diogomrol.gocd.s3.artifact.plugin.S3ClientFactory; 14 | import diogomrol.gocd.s3.artifact.plugin.model.*; 15 | import org.junit.Before; 16 | import org.junit.Rule; 17 | import org.junit.Test; 18 | import org.junit.experimental.categories.Category; 19 | import org.junit.rules.TemporaryFolder; 20 | import org.mockito.Mock; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.nio.file.Files; 25 | import java.nio.file.Path; 26 | import java.nio.file.Paths; 27 | import java.util.HashMap; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Optional; 31 | 32 | import static org.assertj.core.api.Assertions.assertThat; 33 | import static org.mockito.Mockito.when; 34 | import static org.mockito.MockitoAnnotations.initMocks; 35 | 36 | @Category(IntegrationTests.class) 37 | public class PublishAndFetchIntegrationTest { 38 | @Rule 39 | public TemporaryFolder temporaryFolder = new TemporaryFolder(); 40 | 41 | @Mock 42 | private GoPluginApiRequest publishRequest; 43 | @Mock 44 | private ConsoleLogger consoleLogger; 45 | private S3ClientFactory s3ClientFactory; 46 | 47 | private File sourceWorkingDir; 48 | private AmazonS3 s3Client; 49 | 50 | ArtifactStoreConfig storeConfig; 51 | private String bucketName; 52 | private File destinationWorkingDir; 53 | 54 | @Before 55 | public void setUp() throws IOException, SdkClientException { 56 | initMocks(this); 57 | s3ClientFactory = new S3ClientFactory(); 58 | sourceWorkingDir = temporaryFolder.newFolder("go-agent-source"); 59 | destinationWorkingDir = temporaryFolder.newFolder("go-agent-dest"); 60 | bucketName = System.getenv("AWS_BUCKET"); 61 | storeConfig = new ArtifactStoreConfig(bucketName, "eu-west-1", System.getenv("AWS_ACCESS_KEY"), System.getenv("AWS_SECRET_ACCESS_KEY")); 62 | s3Client = s3ClientFactory.s3(storeConfig); 63 | ObjectListing listing = s3Client.listObjects( bucketName); 64 | List summaries = listObjects(listing); 65 | for(S3ObjectSummary o : summaries) { 66 | s3Client.deleteObject(bucketName, o.getKey()); 67 | } 68 | } 69 | 70 | private List listObjects(ObjectListing listing) { 71 | List summaries = listing.getObjectSummaries(); 72 | while (listing.isTruncated()) { 73 | listing = s3Client.listNextBatchOfObjects (listing); 74 | summaries.addAll (listing.getObjectSummaries()); 75 | } 76 | return summaries; 77 | } 78 | 79 | @Test 80 | public void shouldPublishAndFetchArtifactFileWhenDestinationFolder() throws IOException { 81 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "build.json", Optional.of("DestinationFolder")); 82 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 83 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, sourceWorkingDir.getAbsolutePath()); 84 | 85 | Path path = Paths.get(sourceWorkingDir.getAbsolutePath(), "build.json"); 86 | Files.write(path, "{\"content\":\"example artifact file\"}".getBytes()); 87 | 88 | when(publishRequest.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 89 | 90 | final GoPluginApiResponse response = new PublishArtifactExecutor(publishRequest, consoleLogger, s3ClientFactory).execute(); 91 | assertThat(response.responseCode()).isEqualTo(200); 92 | Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 93 | Map metadata = (Map)responseHash.get("metadata"); 94 | FetchArtifactConfig fetchArtifactConfig = new FetchArtifactConfig(); 95 | FetchArtifactRequest fetchArtifactRequest = new FetchArtifactRequest(storeConfig, metadata, fetchArtifactConfig, destinationWorkingDir.toString()); 96 | FetchArtifactExecutor executor = new FetchArtifactExecutor(fetchArtifactRequest, consoleLogger, s3ClientFactory); 97 | GoPluginApiResponse fetchResponse = executor.execute(); 98 | assertThat(fetchResponse.responseCode()).isEqualTo(200); 99 | Path destPath = Paths.get(destinationWorkingDir.getAbsolutePath(), "build.json"); 100 | assertThat(destPath).isRegularFile(); 101 | } 102 | 103 | @Test 104 | public void shouldPublishAndFetchArtifactFileAtLongPath() throws IOException { 105 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "terraform/env.sh", Optional.of("xyz")); 106 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 107 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, sourceWorkingDir.getAbsolutePath()); 108 | 109 | Path path = Paths.get(sourceWorkingDir.getAbsolutePath(), "terraform", "env.sh"); 110 | Files.createDirectories(Paths.get(sourceWorkingDir.getAbsolutePath(), "terraform")); 111 | Files.write(path, "example artifact file".getBytes()); 112 | 113 | when(publishRequest.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 114 | 115 | final GoPluginApiResponse response = new PublishArtifactExecutor(publishRequest, consoleLogger, s3ClientFactory).execute(); 116 | assertThat(response.responseCode()).isEqualTo(200); 117 | Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 118 | Map metadata = (Map)responseHash.get("metadata"); 119 | FetchArtifactConfig fetchArtifactConfig = new FetchArtifactConfig(); 120 | FetchArtifactRequest fetchArtifactRequest = new FetchArtifactRequest(storeConfig, metadata, fetchArtifactConfig, destinationWorkingDir.toString()); 121 | FetchArtifactExecutor executor = new FetchArtifactExecutor(fetchArtifactRequest, consoleLogger, s3ClientFactory); 122 | GoPluginApiResponse fetchResponse = executor.execute(); 123 | assertThat(fetchResponse.responseCode()).isEqualTo(200); 124 | Path destPath = Paths.get(destinationWorkingDir.getAbsolutePath(), "env.sh"); 125 | assertThat(destPath).isRegularFile(); 126 | } 127 | 128 | @Test 129 | public void shouldPublishAndFetchArtifactFileWhenSourceInSubdirAndNoDestinationFolder() throws IOException { 130 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "bin/build.json", Optional.empty()); 131 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 132 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, sourceWorkingDir.getAbsolutePath()); 133 | 134 | Path path = Paths.get(sourceWorkingDir.getAbsolutePath(), "bin/build.json"); 135 | Paths.get(sourceWorkingDir.getAbsolutePath(), "bin").toFile().mkdirs(); 136 | Files.write(path, "{\"content\":\"example artifact file\"}".getBytes()); 137 | 138 | when(publishRequest.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 139 | 140 | final GoPluginApiResponse response = new PublishArtifactExecutor(publishRequest, consoleLogger, s3ClientFactory).execute(); 141 | assertThat(response.responseCode()).isEqualTo(200); 142 | Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 143 | Map metadata = (Map)responseHash.get("metadata"); 144 | FetchArtifactConfig fetchArtifactConfig = new FetchArtifactConfig(); 145 | FetchArtifactRequest fetchArtifactRequest = new FetchArtifactRequest(storeConfig, metadata, fetchArtifactConfig, destinationWorkingDir.toString()); 146 | FetchArtifactExecutor executor = new FetchArtifactExecutor(fetchArtifactRequest, consoleLogger, s3ClientFactory); 147 | GoPluginApiResponse fetchResponse = executor.execute(); 148 | assertThat(fetchResponse.responseCode()).isEqualTo(200); 149 | Path destPath = Paths.get(destinationWorkingDir.getAbsolutePath(), "build.json"); 150 | assertThat(destPath).isRegularFile(); 151 | } 152 | 153 | @Test 154 | public void shouldPublishAndFetchArtifactFileWhenUploadedManyFiles() throws IOException { 155 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "**/*.json", Optional.of("DestinationFolder")); 156 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 157 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, sourceWorkingDir.getAbsolutePath()); 158 | 159 | Path subDir = Paths.get(sourceWorkingDir.getAbsolutePath(), "bin"); 160 | subDir.toFile().mkdir(); 161 | 162 | Path buildJsonPath = Paths.get(subDir.toAbsolutePath().toString(), "build.json"); 163 | Files.write(buildJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 164 | Path testJsonPath = Paths.get(subDir.toAbsolutePath().toString(), "test.json"); 165 | Files.write(testJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 166 | Path binPath = Paths.get(subDir.toAbsolutePath().toString(), "test.bin"); 167 | Files.write(binPath, "example binary artifact".getBytes()); 168 | 169 | when(publishRequest.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 170 | 171 | final GoPluginApiResponse response = new PublishArtifactExecutor(publishRequest, consoleLogger, s3ClientFactory).execute(); 172 | assertThat(response.responseCode()).isEqualTo(200); 173 | Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 174 | Map metadata = (Map)responseHash.get("metadata"); 175 | FetchArtifactConfig fetchArtifactConfig = new FetchArtifactConfig("bin/build.json", "bin", true); 176 | FetchArtifactRequest fetchArtifactRequest = new FetchArtifactRequest(storeConfig, metadata, fetchArtifactConfig, destinationWorkingDir.toString()); 177 | FetchArtifactExecutor executor = new FetchArtifactExecutor(fetchArtifactRequest, consoleLogger, s3ClientFactory); 178 | GoPluginApiResponse fetchResponse = executor.execute(); 179 | assertThat(fetchResponse.responseCode()).isEqualTo(200); 180 | Path destPath = Paths.get(destinationWorkingDir.getAbsolutePath(), "bin/build.json"); 181 | assertThat(destPath).isRegularFile(); 182 | } 183 | 184 | @Test 185 | public void shouldPublishAndFetchArtifactDirectoryWhenUploadedManyFiles() throws IOException { 186 | final ArtifactPlan artifactPlan = new ArtifactPlan("id", "storeId", "**/*.json", Optional.of("DestinationFolder")); 187 | final ArtifactStore artifactStore = new ArtifactStore(artifactPlan.getId(), storeConfig); 188 | final PublishArtifactRequest publishArtifactRequest = new PublishArtifactRequest(artifactStore, artifactPlan, sourceWorkingDir.getAbsolutePath()); 189 | 190 | Path subDir = Paths.get(sourceWorkingDir.getAbsolutePath(), "bin"); 191 | subDir.toFile().mkdir(); 192 | 193 | Path buildJsonPath = Paths.get(subDir.toAbsolutePath().toString(), "build.json"); 194 | Files.write(buildJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 195 | Path testJsonPath = Paths.get(subDir.toAbsolutePath().toString(), "test.json"); 196 | Files.write(testJsonPath, "{\"content\":\"example build artifact file\"}".getBytes()); 197 | Path binPath = Paths.get(subDir.toAbsolutePath().toString(), "test.bin"); 198 | Files.write(binPath, "example binary artifact".getBytes()); 199 | 200 | when(publishRequest.requestBody()).thenReturn(publishArtifactRequest.toJSON()); 201 | 202 | final GoPluginApiResponse response = new PublishArtifactExecutor(publishRequest, consoleLogger, s3ClientFactory).execute(); 203 | assertThat(response.responseCode()).isEqualTo(200); 204 | Map responseHash = new Gson().fromJson(response.responseBody(), new TypeToken>(){}.getType()); 205 | Map metadata = (Map)responseHash.get("metadata"); 206 | FetchArtifactConfig fetchArtifactConfig = new FetchArtifactConfig("", "", false); 207 | FetchArtifactRequest fetchArtifactRequest = new FetchArtifactRequest(storeConfig, metadata, fetchArtifactConfig, destinationWorkingDir.toString()); 208 | FetchArtifactExecutor executor = new FetchArtifactExecutor(fetchArtifactRequest, consoleLogger, s3ClientFactory); 209 | GoPluginApiResponse fetchResponse = executor.execute(); 210 | assertThat(fetchResponse.responseCode()).isEqualTo(200); 211 | assertThat(Paths.get(destinationWorkingDir.getAbsolutePath(), "bin/build.json")).isRegularFile(); 212 | assertThat(Paths.get(destinationWorkingDir.getAbsolutePath(), "bin/test.json")).isRegularFile(); 213 | } 214 | } 215 | --------------------------------------------------------------------------------