Skip to content

Commit

Permalink
Added Self fetch (#59)
Browse files Browse the repository at this point in the history
* Added changes on current material types to support Stage material

* Added Stage material params

* Removed StagePipeline param

* Added new rendering logic to template

* Create StageFetchExecutor (still missing stage counter find feature)

* Reverted changes on other fetch executor names

* Added logic to find latest stage counter

* Adjustments to UI
  • Loading branch information
thalescm authored and manojlds committed Sep 18, 2017
1 parent df82651 commit 636708b
Show file tree
Hide file tree
Showing 10 changed files with 346 additions and 13 deletions.
42 changes: 33 additions & 9 deletions fetch/src/main/java/com/indix/gocd/s3fetch/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@

public class Config {

public String materialType;
public String repo;
public String pkg;
public String material;
public String job;
public String destination;
private final String materialType;
private final String repo;
private final String pkg;
private final String material;
private final String job;
private final String stage;
private final String source;
private final String sourcePrefix;
private final String destination;

public String getMaterialType() { return materialType; }
public String getMaterialType() {
return materialType;
}

public String getRepo() {
return escapeEnvironmentVariable(repo);
Expand All @@ -23,9 +28,25 @@ public String getPkg() {
return escapeEnvironmentVariable(pkg);
}

public String getMaterial() { return escapeEnvironmentVariable(material); }
public String getMaterial() {
return escapeEnvironmentVariable(material);
}

public String getJob() { return job; }
public String getJob() {
return job;
}

public String getStage() {
return stage;
}

public String getSource() {
return source;
}

public String getSourcePrefix() {
return sourcePrefix;
}

public String getDestination() {
return destination;
Expand All @@ -37,6 +58,9 @@ public Config(Map config) {
pkg = getValue(config, PACKAGE);
material = getValue(config, MATERIAL);
job = getValue(config, JOB);
stage = getValue(config, STAGE);
source = getValue(config, SOURCE);
sourcePrefix = getValue(config, SOURCE_PREFIX);
destination = getValue(config, DESTINATION);
}

Expand Down
17 changes: 17 additions & 0 deletions fetch/src/main/java/com/indix/gocd/s3fetch/FetchTask.java
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ private FetchExecutor getFetchExecutor(Config config) {
return new PackageFetchExecutor();
} else if (materialType.equals("Pipeline")) {
return new PipelineFetchExecutor();
} else if (materialType.equals("Self")) {
return new SelfFetchExecutor();
} else {
throw new IllegalStateException("No such material type: " + materialType);
}
Expand Down Expand Up @@ -123,6 +125,21 @@ private GoPluginApiResponse handleGetConfigRequest() {
job.put("required", false);
config.put(Constants.JOB, job);

HashMap stage = new HashMap();
stage.put("default-value", "");
stage.put("required", false);
config.put(Constants.STAGE, stage);

HashMap source = new HashMap();
source.put("default-value", "");
source.put("required", false);
config.put(Constants.SOURCE, source);

HashMap sourcePrefix = new HashMap();
sourcePrefix.put("default-value", "");
sourcePrefix.put("required", false);
config.put(Constants.SOURCE_PREFIX, sourcePrefix);

HashMap destination = new HashMap();
destination.put("default-value", "");
destination.put("required", false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.util.Map;

public class PipelineFetchExecutor extends FetchExecutor {

@Override
protected String getArtifactsLocationTemplate(Config config, GoEnvironment env) {
String materialLocator = env.get(String.format("GO_DEPENDENCY_LOCATOR_%s", config.getMaterial()));
Expand Down
63 changes: 63 additions & 0 deletions fetch/src/main/java/com/indix/gocd/s3fetch/SelfFetchExecutor.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.indix.gocd.s3fetch;

import com.indix.gocd.utils.Constants;
import com.indix.gocd.utils.GoEnvironment;
import com.indix.gocd.utils.store.S3ArtifactStore;
import org.apache.commons.lang3.StringUtils;

import java.util.HashMap;
import java.util.Map;

public class SelfFetchExecutor extends FetchExecutor {

@Override
protected String getArtifactsLocationTemplate(Config config, GoEnvironment env) {

String prefix = config.getSourcePrefix();
String source = config.getSource();

if (StringUtils.isBlank(prefix)) {
String pipeline = env.get("GO_PIPELINE_NAME");
String pipelineCounter = env.get("GO_PIPELINE_COUNTER");
String stage = config.getStage();
String job = config.getJob();
String bucket = getBucket(config, env);

final S3ArtifactStore store = getS3ArtifactStore(env, bucket);
prefix = store.getLatestPrefix(pipeline, stage, job, pipelineCounter);

if (StringUtils.isBlank(prefix)) {
throw new RuntimeException(
String.format("Could not determine stage counter on s3 with path: s3://%s/%s/%s/%s/%s.",
bucket,
pipeline,
stage,
job,
pipelineCounter));
}
}

return prefix + "/" + source;
}

@Override
public Map<String, String> validate(Config config) {
Map<String, String> errors = new HashMap<>();

if (StringUtils.isBlank(config.getSourcePrefix())) {

if (StringUtils.isBlank(config.getStage())) {
errors.put(Constants.STAGE, Constants.REQUIRED_FIELD_MESSAGE);
}
if (StringUtils.isBlank(config.getJob())) {
errors.put(Constants.JOB, Constants.REQUIRED_FIELD_MESSAGE);
}
}

if (StringUtils.isBlank(config.getSource())) {
errors.put(Constants.SOURCE, Constants.REQUIRED_FIELD_MESSAGE);
}

return errors;
}
}
41 changes: 40 additions & 1 deletion fetch/src/main/resources/views/task.template.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<select ng-model="MaterialType" ng-init="MaterialType = MaterialType || 'Package'" ng-change="clearFields()">
<option value="Package">Package</option>
<option value="Pipeline">Pipeline</option>
<option value="Self">Self</option>
</select>
</div>

Expand Down Expand Up @@ -30,6 +31,36 @@
<span class="form_error" ng-show="GOINPUTNAME[Job].$error.server">{{ GOINPUTNAME[Job].$error.server }}</span>
</div>

<!-- Stage fields -->
<div class="form_item_block" ng-show="MaterialType == 'Self'">
<label>Source Type:<span class="arterisk">*</span></label>
<select ng-model="SourceType" ng-init="SourceType = SourceType || 'Default'" ng-change="clearStageFields()">
<option value="Default">Default prefix</option>
<option value="Custom">Custom prefix</option>
</select>
</div>
<div class="form_item_block" ng-show="MaterialType == 'Self' && SourceType == 'Default'">
<label>Fetch from stage:<span class="asterisk">*</span></label>
<input type="text" ng-model="Stage" ng-required="true">
<span class="form_error" ng-show="GOINPUTNAME[Stage].$error.server">{{ GOINPUTNAME[Stage].$error.server }}</span>
</div>
<div class="form_item_block" ng-show="MaterialType == 'Self' && SourceType == 'Default'">
<label>Fetch from job:<span class="asterisk">*</span></label>
<input type="text" ng-model="Job" ng-required="true">
<span class="form_error" ng-show="GOINPUTNAME[Job].$error.server">{{ GOINPUTNAME[Job].$error.server }}</span>
</div>
<div class="form_item_block" ng-show="MaterialType == 'Self' && SourceType == 'Custom'">
<label>Source Prefix (the same you defined as destination prefix on publish stage)<span class="asterisk">*</span></label>
<input type="text" ng-model="SourcePrefix"/>
<span class="form_error" ng-show="GOINPUTNAME[SourcePrefix].$error.server">{{ GOINPUTNAME[SourcePrefix].$error.server }}</span>
</div>
<div class="form_item_block" ng-show="MaterialType == 'Self'">
<label>Source (the same you defined as destination on publish stage):<span class="asterisk">*</span></label>
<input type="text" ng-model="Source" ng-required="true">
<span class="form_error" ng-show="GOINPUTNAME[Source].$error.server">{{ GOINPUTNAME[Source].$error.server }}</span>
</div>

<!-- Common Fields -->
<div class="form_item_block">
<label>Destination directory:</label>
<input type="text" ng-model="Destination">
Expand All @@ -42,11 +73,19 @@
try {
var $scope = angular.element(document.getElementById("task_angular_pluggable_task_indix_s3fetch")).scope();
$scope.$apply(function() {

$scope.clearStageFields = function() {
this.Job = null;
this.Stage = null;
this.Source = null;
this.SourcePrefix = null;
}

$scope.clearFields = function() {
this.Repo = null;
this.Package = null;
this.Material = null;
this.Job = null;
this.clearStageFields();
};
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,4 @@ private Context mockContext(final Map<String, String> environmentMap) {
private S3ArtifactStore mockStore() { return mock(S3ArtifactStore.class); }

private AmazonS3Client mockClient() { return mock(AmazonS3Client.class); }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package com.indix.gocd.s3fetch;

import com.indix.gocd.utils.Constants;
import com.indix.gocd.utils.Context;
import com.indix.gocd.utils.TaskExecutionResult;
import com.indix.gocd.utils.mocks.MockContext;
import com.indix.gocd.utils.store.S3ArtifactStore;
import com.indix.gocd.utils.utils.Maps;
import org.junit.Before;
import org.junit.Test;

import java.util.Map;

import static com.indix.gocd.utils.Constants.*;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;

public class SelfFetchExecutorTest {

private Maps.MapBuilder<String, String> mockEnvironmentVariables;
private FetchExecutor fetchExecutor;
private Config config;
private S3ArtifactStore store;

private final String PIPELINE = "pipeline";
private final String PIPELINE_COUNTER = "1";
private final String STAGE = "stage";
private final String JOB = "job";

@Before
public void setUp() throws Exception {
mockEnvironmentVariables = Maps.<String, String>builder()
.with(AWS_SECRET_ACCESS_KEY, "secretKey")
.with(AWS_ACCESS_KEY_ID, "accessId")
.with(GO_ARTIFACTS_S3_BUCKET, "bucket")
.with(GO_SERVER_DASHBOARD_URL, "http://go.server:8153")
.with("GO_PIPELINE_NAME", PIPELINE)
.with("GO_PIPELINE_COUNTER", PIPELINE_COUNTER);

config = new Config(Maps.builder()
.with(Constants.STAGE, Maps.builder().with("value", STAGE).build())
.with(Constants.JOB, Maps.builder().with("value", JOB).build())
.with(Constants.SOURCE, Maps.builder().with("value", "source").build())
.with(Constants.DESTINATION, Maps.builder().with("value", "artifacts").build())
.build());

store = mock(S3ArtifactStore.class);
fetchExecutor = spy(new SelfFetchExecutor());
doReturn(store).when(fetchExecutor).getS3ArtifactStore(any(), any());
}

@Test
public void shouldBeFailureIfCouldntFindS3Path() {
Map<String, String> mockVariables = mockEnvironmentVariables.build();
doReturn(null).when(store).getLatestPrefix(PIPELINE, STAGE, JOB, PIPELINE_COUNTER);
TaskExecutionResult result = fetchExecutor.execute(config, mockContext(mockVariables) );

assertFalse(result.isSuccessful());
assertEquals("Failure while downloading artifacts - Could not determine stage counter on s3 with path: s3://bucket/pipeline/stage/job/1.", result.message());
}

@Test
public void shouldBeSuccessWhenAbleToFindSS3Path() {
Map<String, String> mockVariables = mockEnvironmentVariables.build();
doReturn("sourcePrefix").when(store).getLatestPrefix(PIPELINE, STAGE, JOB, PIPELINE_COUNTER);
TaskExecutionResult result = fetchExecutor.execute(config, mockContext(mockVariables) );

assertTrue(result.isSuccessful());
assertThat(result.message(), is("Fetched all artifacts"));
verify(store).getPrefix("sourcePrefix/source", "here/artifacts");
}

@Test
public void shouldBeSuccessWhenCustomPrefixProvided() {
Map<String, String> mockVariables = mockEnvironmentVariables.build();
config = new Config(Maps.builder()
.with(Constants.SOURCE, Maps.builder().with("value", "source").build())
.with(Constants.SOURCE_PREFIX, Maps.builder().with("value", "sourcePrefix").build())
.with(Constants.DESTINATION, Maps.builder().with("value", "artifacts").build())
.build());
TaskExecutionResult result = fetchExecutor.execute(config, mockContext(mockVariables) );

assertTrue(result.isSuccessful());
assertThat(result.message(), is("Fetched all artifacts"));
verify(store).getPrefix("sourcePrefix/source", "here/artifacts");
}

private Context mockContext(final Map<String, String> environmentMap) {
Map<String, Object> contextMap = Maps.<String, Object>builder()
.with("environmentVariables", environmentMap)
.with("workingDirectory", "here")
.build();
return new MockContext(contextMap);
}
}
3 changes: 3 additions & 0 deletions utils/src/main/java/com/indix/gocd/utils/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ public class Constants {
public static final String PACKAGE = "Package";
public static final String MATERIAL = "Material";
public static final String JOB = "Job";
public static final String STAGE = "Stage";
public static final String SOURCE = "Source";
public static final String SOURCE_PREFIX = "SourcePrefix";
public static final String DESTINATION = "Destination";

public static final String REQUIRED_FIELD_MESSAGE = "This field is required";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,10 +197,40 @@ public RevisionStatus getLatest(Artifact artifact) {
return null;
}

public String getLatestPrefix(String pipeline, String stage, String job, String pipelineCounter) {
String prefix = String.format("%s/%s/%s/%s.", pipeline, stage, job, pipelineCounter);
ListObjectsRequest listObjectsRequest = new ListObjectsRequest()
.withBucketName(bucket)
.withPrefix(prefix)
.withDelimiter("/");

ObjectListing listing = client.listObjects(listObjectsRequest);

if (listing != null) {
List<String> commonPrefixes = listing.getCommonPrefixes();
List<String> stageCounters = Lists.map(commonPrefixes,
input ->
input.replaceAll(prefix, "").replaceAll("/", ""));
if (stageCounters.size() > 0) {
int maxStageCounter = Integer.valueOf(stageCounters.get(0));

for (int i = 1; i < stageCounters.size(); i++) {
int stageCounter = Integer.valueOf(stageCounters.get(i));
if (stageCounter > maxStageCounter) {
maxStageCounter = stageCounter;
}
}

return prefix + maxStageCounter;
}
}
return null;
}

public static AmazonS3 getS3client(GoEnvironment env) {
AmazonS3ClientBuilder amazonS3ClientBuilder = AmazonS3ClientBuilder.standard();

if(env.has(AWS_REGION)) {
if (env.has(AWS_REGION)) {
amazonS3ClientBuilder.withRegion(env.get(AWS_REGION));
}
if (env.hasAWSUseIamRole()) {
Expand Down
Loading

0 comments on commit 636708b

Please sign in to comment.