├── src ├── test │ └── java │ │ └── com │ │ └── example │ │ └── elasticagent │ │ ├── executors │ │ ├── PluginStatusReportExecutorTest.java │ │ ├── GetCapabilitiesExecutorTest.java │ │ ├── CreateAgentRequestExecutorTest.java │ │ ├── JobCompletionRequestExecutorTest.java │ │ ├── NumberMetadataTest.java │ │ ├── GetPluginIconExecutorTest.java │ │ ├── MemoryMetadataTest.java │ │ ├── GetClusterProfileViewRequestExecutorTest.java │ │ ├── ValidateElasticAgentProfileRequestExecutorTest.java │ │ ├── GetElasticAgentProfileViewExecutorTest.java │ │ ├── GetElasticAgentProfileMetadataExecutorTest.java │ │ ├── MigrateConfigRequestExecutorTest.java │ │ ├── ClusterProfileValidateRequestExecutorTest.java │ │ └── ShouldAssignWorkRequestExecutorTest.java │ │ ├── models │ │ ├── JobIdentifierTest.java │ │ └── JobIdentifierMother.java │ │ ├── BaseTest.java │ │ ├── requests │ │ ├── ClusterStatusReportRequestTest.java │ │ ├── ServerPingRequestTest.java │ │ ├── AgentStatusReportRequestTest.java │ │ ├── JobCompletionRequestTest.java │ │ ├── ShouldAssignWorkRequestTest.java │ │ ├── CreateAgentRequestTest.java │ │ ├── MigrateConfigPayloadTest.java │ │ └── ClusterProfileChangedRequestTest.java │ │ ├── PluginSettingsTest.java │ │ ├── ClusterProfilePropertiesTest.java │ │ └── AgentTest.java └── main │ ├── resources │ ├── plugin.properties │ ├── plugin-icon.svg │ ├── profile.template.html │ └── cluster-profile.template.html │ ├── java │ └── com │ │ └── example │ │ └── elasticagent │ │ ├── models │ │ ├── StatusReport.java │ │ ├── ServerInfo.java │ │ ├── AgentStatusReport.java │ │ └── JobIdentifier.java │ │ ├── RequestExecutor.java │ │ ├── executors │ │ ├── GetClusterProfileViewRequestExecutor.java │ │ ├── AgentNotFoundException.java │ │ ├── GetCapabilitiesExecutor.java │ │ ├── JobCompletionRequestExecutor.java │ │ ├── NonBlankField.java │ │ ├── NumberMetadata.java │ │ ├── PositiveNumberField.java │ │ ├── GetElasticAgentProfileViewExecutor.java │ │ ├── GetClusterProfileMetadataExecutor.java │ │ ├── GetPluginIconExecutor.java │ │ ├── CreateAgentRequestExecutor.java │ │ ├── MemoryMetadata.java │ │ ├── ClusterStatusReportExecutor.java │ │ ├── GetElasticAgentProfileMetadataExecutor.java │ │ ├── ShouldAssignWorkRequestExecutor.java │ │ ├── ClusterProfileValidateRequestExecutor.java │ │ ├── PluginStatusReportExecutor.java │ │ ├── Field.java │ │ ├── ValidateElasticAgentProfileRequestExecutor.java │ │ ├── MigrateConfigRequestExecutor.java │ │ ├── Metadata.java │ │ ├── ClusterProfileChangedRequestExecutor.java │ │ ├── ServerPingRequestExecutor.java │ │ └── AgentStatusReportExecutor.java │ │ ├── requests │ │ ├── ClusterProfileValidateRequest.java │ │ ├── PluginStatusReportRequest.java │ │ ├── ValidateElasticAgentProfileRequest.java │ │ ├── ServerPingRequest.java │ │ ├── MigrateConfigPayload.java │ │ ├── ClusterStatusReportRequest.java │ │ ├── JobCompletionRequest.java │ │ ├── ShouldAssignWorkRequest.java │ │ ├── AgentStatusReportRequest.java │ │ ├── ClusterProfileChangedRequest.java │ │ └── CreateAgentRequest.java │ │ ├── ClusterProfileProperties.java │ │ ├── Clock.java │ │ ├── Constants.java │ │ ├── ServerRequestFailedException.java │ │ ├── utils │ │ ├── Util.java │ │ ├── SizeUnit.java │ │ └── Size.java │ │ ├── views │ │ └── ViewBuilder.java │ │ ├── ClusterProfile.java │ │ ├── ExampleInstance.java │ │ ├── Request.java │ │ ├── Agents.java │ │ ├── PluginRequest.java │ │ ├── Agent.java │ │ ├── PluginSettings.java │ │ └── AgentInstances.java │ └── resource-templates │ └── plugin.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .github ├── dependabot.yml └── workflows │ └── update-gradle-wrapper.yml ├── settings.gradle ├── README.md └── gradlew.bat /src/test/java/com/example/elasticagent/executors/PluginStatusReportExecutorTest.java: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/elastic-agent-skeleton-plugin/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Intellij files 2 | .idea/ 3 | out/ 4 | *.iml 5 | 6 | # gradle's output dir 7 | build/ 8 | 9 | # the gradle temp dir 10 | .gradle 11 | 12 | # Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) 13 | !gradle-wrapper.jar 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=72f44c9f8ebcb1af43838f45ee5c4aa9c5444898b3468ab3f4af7b6076c5bc3f 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: monthly 7 | groups: 8 | github-actions: 9 | patterns: 10 | - "*" 11 | - package-ecosystem: gradle 12 | directory: / 13 | schedule: 14 | interval: monthly 15 | groups: 16 | gradle-deps: 17 | patterns: 18 | - "*" 19 | -------------------------------------------------------------------------------- /src/main/resources/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 | 17 | pluginId=${id} 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 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 | 17 | rootProject.name = 'elastic-agent-skeleton-plugin' 18 | -------------------------------------------------------------------------------- /.github/workflows/update-gradle-wrapper.yml: -------------------------------------------------------------------------------- 1 | name: Update Gradle Wrapper 2 | 3 | on: 4 | schedule: 5 | - cron: "0 0 1 * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update-gradle-wrapper: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Harden the runner (Audit all outbound calls) 13 | uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 # v2.13.2 14 | with: 15 | egress-policy: audit 16 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0 17 | - name: Update Gradle Wrapper 18 | uses: gradle-update/update-gradle-wrapper-action@512b1875f3b6270828abfe77b247d5895a2da1e5 # v2.1.0 19 | with: 20 | labels: dependencies 21 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/models/JobIdentifierTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.models; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.CoreMatchers.is; 6 | import static org.hamcrest.MatcherAssert.assertThat; 7 | 8 | public class JobIdentifierTest { 9 | 10 | @Test 11 | public void shouldDeserializeFromJson() { 12 | JobIdentifier jobIdentifier = JobIdentifier.fromJson(JobIdentifierMother.getJson().toString()); 13 | 14 | JobIdentifier expected = JobIdentifierMother.get(); 15 | assertThat(jobIdentifier, is(expected)); 16 | } 17 | 18 | @Test 19 | public void shouldGetRepresentation() { 20 | String representation = JobIdentifierMother.get().getRepresentation(); 21 | 22 | assertThat(representation, is("up42/1/stage/1/job1")); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/models/StatusReport.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.models; 2 | 3 | import com.example.elasticagent.ClusterProfileProperties; 4 | import com.example.elasticagent.ExampleInstance; 5 | 6 | import java.util.Map; 7 | 8 | public class StatusReport { 9 | 10 | private final String clusterProfileUuid; 11 | private final Map instances; 12 | //Add fields as needed 13 | 14 | public StatusReport(ClusterProfileProperties clusterProfileProperties, Map instances) { 15 | clusterProfileUuid = clusterProfileProperties.uuid(); 16 | this.instances = instances; 17 | } 18 | 19 | public Map getInstances() { 20 | return instances; 21 | } 22 | 23 | public String getClusterProfileUuid() { 24 | return clusterProfileUuid; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/RequestExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 20 | 21 | public interface RequestExecutor { 22 | 23 | GoPluginApiResponse execute() throws Exception; 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/BaseTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import java.io.IOException; 20 | 21 | public abstract class BaseTest { 22 | 23 | protected PluginSettings createSettings() throws IOException { 24 | return new PluginSettings(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/models/JobIdentifierMother.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.models; 2 | 3 | import com.google.gson.JsonObject; 4 | 5 | public class JobIdentifierMother { 6 | 7 | public static JsonObject getJson() { 8 | JsonObject jobIdentifierJson = new JsonObject(); 9 | jobIdentifierJson.addProperty("pipeline_name", "up42"); 10 | jobIdentifierJson.addProperty("pipeline_counter", 1); 11 | jobIdentifierJson.addProperty("pipeline_label", "label"); 12 | jobIdentifierJson.addProperty("stage_name", "stage"); 13 | jobIdentifierJson.addProperty("stage_counter", "1"); 14 | jobIdentifierJson.addProperty("job_name", "job1"); 15 | jobIdentifierJson.addProperty("job_id", 1); 16 | return jobIdentifierJson; 17 | } 18 | 19 | public static JobIdentifier get() { 20 | return new JobIdentifier("up42", 1L, "label", "stage", "1", "job1", 1L); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/GetClusterProfileViewRequestExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.RequestExecutor; 4 | import com.example.elasticagent.utils.Util; 5 | import com.google.gson.Gson; 6 | import com.google.gson.JsonObject; 7 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 8 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 9 | 10 | public class GetClusterProfileViewRequestExecutor implements RequestExecutor { 11 | private static final Gson GSON = new Gson(); 12 | 13 | @Override 14 | public GoPluginApiResponse execute() throws Exception { 15 | JsonObject jsonObject = new JsonObject(); 16 | jsonObject.addProperty("template", Util.readResource("/cluster-profile.template.html")); 17 | DefaultGoPluginApiResponse defaultGoPluginApiResponse = new DefaultGoPluginApiResponse(200, GSON.toJson(jsonObject)); 18 | return defaultGoPluginApiResponse; 19 | } 20 | } -------------------------------------------------------------------------------- /src/main/resources/plugin-icon.svg: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | 22 | 23 | Example! 24 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/GetCapabilitiesExecutorTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 4 | import org.json.JSONObject; 5 | import org.junit.jupiter.api.Test; 6 | import org.skyscreamer.jsonassert.JSONAssert; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | import static org.skyscreamer.jsonassert.JSONAssert.assertEquals; 11 | 12 | public class GetCapabilitiesExecutorTest { 13 | 14 | @Test 15 | public void shouldReturnResponse() throws Exception { 16 | GoPluginApiResponse response = new GetCapabilitiesExecutor().execute(); 17 | 18 | assertThat(response.responseCode(), is(200)); 19 | JSONObject expected = new JSONObject() 20 | .put("supports_plugin_status_report", true) 21 | .put("supports_agent_status_report", true) 22 | .put("supports_cluster_status_report", true); 23 | assertEquals(expected, new JSONObject(response.responseBody()), true); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/AgentNotFoundException.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | public class AgentNotFoundException extends Exception { 20 | 21 | public AgentNotFoundException(final String message) { 22 | super(message); 23 | } 24 | 25 | public AgentNotFoundException(final String message, final Throwable cause) { 26 | super(message, cause); 27 | } 28 | 29 | public AgentNotFoundException(final Throwable cause) { 30 | super(cause); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/GetCapabilitiesExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.RequestExecutor; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 7 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 8 | 9 | import java.util.LinkedHashMap; 10 | import java.util.Map; 11 | 12 | public class GetCapabilitiesExecutor implements RequestExecutor { 13 | 14 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); 15 | 16 | private static final Map CAPABILITIES_RESPONSE = new LinkedHashMap<>(); 17 | 18 | static { 19 | CAPABILITIES_RESPONSE.put("supports_plugin_status_report", true); 20 | CAPABILITIES_RESPONSE.put("supports_cluster_status_report", true); 21 | CAPABILITIES_RESPONSE.put("supports_agent_status_report", true); 22 | } 23 | 24 | @Override 25 | public GoPluginApiResponse execute() { 26 | return DefaultGoPluginApiResponse.success(GSON.toJson(CAPABILITIES_RESPONSE)); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/JobCompletionRequestExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.AgentInstances; 4 | import com.example.elasticagent.ExampleInstance; 5 | import com.example.elasticagent.RequestExecutor; 6 | import com.example.elasticagent.requests.JobCompletionRequest; 7 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 8 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 9 | 10 | public class JobCompletionRequestExecutor implements RequestExecutor { 11 | private final JobCompletionRequest jobCompletionRequest; 12 | private final AgentInstances agentInstances; 13 | 14 | public JobCompletionRequestExecutor(JobCompletionRequest jobCompletionRequest, AgentInstances agentInstances) { 15 | this.jobCompletionRequest = jobCompletionRequest; 16 | this.agentInstances = agentInstances; 17 | } 18 | 19 | @Override 20 | public GoPluginApiResponse execute() throws Exception { 21 | agentInstances.terminate(jobCompletionRequest.getElasticAgentId(), jobCompletionRequest.clusterProperties()); 22 | return new DefaultGoPluginApiResponse(200); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/ClusterProfileValidateRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.requests; 2 | 3 | import com.example.elasticagent.RequestExecutor; 4 | import com.example.elasticagent.executors.ClusterProfileValidateRequestExecutor; 5 | import com.google.gson.Gson; 6 | import com.google.gson.reflect.TypeToken; 7 | 8 | import java.lang.reflect.Type; 9 | import java.util.Map; 10 | 11 | import static com.example.elasticagent.requests.ShouldAssignWorkRequest.GSON; 12 | 13 | public class ClusterProfileValidateRequest { 14 | private Map properties; 15 | 16 | public ClusterProfileValidateRequest(Map properties) { 17 | this.properties = properties; 18 | } 19 | 20 | public Map getProperties() { 21 | return properties; 22 | } 23 | 24 | public static ClusterProfileValidateRequest fromJSON(String json) { 25 | final Type type = new TypeToken>() { 26 | }.getType(); 27 | final Map properties = GSON.fromJson(json, type); 28 | return new ClusterProfileValidateRequest(properties); 29 | } 30 | 31 | public RequestExecutor executor() { 32 | return new ClusterProfileValidateRequestExecutor(this); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/NonBlankField.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import org.apache.commons.lang3.StringUtils; 20 | 21 | public class NonBlankField extends Field { 22 | 23 | public NonBlankField(String key, String displayName, String defaultValue, Boolean required, Boolean secure, String displayOrder) { 24 | super(key, displayName, defaultValue, required, secure, displayOrder); 25 | } 26 | 27 | @Override 28 | public String doValidate(String input) { 29 | if (StringUtils.isBlank(input)) { 30 | return this.displayName + " must not be blank"; 31 | } 32 | return null; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/NumberMetadata.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | 20 | import static org.apache.commons.lang3.StringUtils.isBlank; 21 | 22 | public class NumberMetadata extends Metadata { 23 | 24 | public NumberMetadata(String key, String displayName, boolean required) { 25 | super(key, displayName, required, false); 26 | } 27 | 28 | @Override 29 | protected String doValidate(String input) { 30 | if (isRequired() || !isBlank(input)) { 31 | if (isBlank(input) || Integer.parseInt(input) < 0) { 32 | return this.displayName + " must be a positive integer."; 33 | } 34 | } 35 | return null; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/models/ServerInfo.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.models; 2 | 3 | import com.google.gson.FieldNamingPolicy; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import com.google.gson.annotations.Expose; 7 | import com.google.gson.annotations.SerializedName; 8 | 9 | public class ServerInfo { 10 | 11 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() 12 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 13 | .create(); 14 | 15 | @Expose 16 | @SerializedName("server_id") 17 | private String serverId; 18 | 19 | @Expose 20 | @SerializedName("site_url") 21 | private String siteUrl; 22 | 23 | @Expose 24 | @SerializedName("secure_site_url") 25 | private String secureSiteUrl; 26 | 27 | public String getServerId() { 28 | return serverId; 29 | } 30 | 31 | public String getSiteUrl() { 32 | return siteUrl; 33 | } 34 | 35 | public String getSecureSiteUrl() { 36 | return secureSiteUrl; 37 | } 38 | 39 | public void setSecureSiteUrl(String secureSiteUrl) { 40 | this.secureSiteUrl = secureSiteUrl; 41 | } 42 | 43 | public static ServerInfo fromJSON(String json) { 44 | return GSON.fromJson(json, ServerInfo.class); 45 | } 46 | 47 | public String toJSON() { 48 | return GSON.toJson(this); 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/models/AgentStatusReport.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.models; 2 | 3 | import java.util.Objects; 4 | 5 | public class AgentStatusReport { 6 | 7 | private final JobIdentifier jobIdentifier; 8 | private final String elasticAgentId; 9 | private final Long createdAt; 10 | //Add fields as needed 11 | 12 | public AgentStatusReport(JobIdentifier jobIdentifier, String elasticAgentId, Long createdAt) { 13 | this.jobIdentifier = jobIdentifier; 14 | this.elasticAgentId = elasticAgentId; 15 | this.createdAt = createdAt; 16 | } 17 | 18 | public JobIdentifier getJobIdentifier() { 19 | return jobIdentifier; 20 | } 21 | 22 | public String getElasticAgentId() { 23 | return elasticAgentId; 24 | } 25 | 26 | public Long getCreatedAt() { 27 | return createdAt; 28 | } 29 | 30 | @Override 31 | public boolean equals(Object o) { 32 | if (this == o) return true; 33 | if (o == null || getClass() != o.getClass()) return false; 34 | AgentStatusReport that = (AgentStatusReport) o; 35 | return Objects.equals(jobIdentifier, that.jobIdentifier) && 36 | Objects.equals(elasticAgentId, that.elasticAgentId) && 37 | Objects.equals(createdAt, that.createdAt); 38 | } 39 | 40 | @Override 41 | public int hashCode() { 42 | return Objects.hash(jobIdentifier, elasticAgentId, createdAt); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/PositiveNumberField.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | public class PositiveNumberField extends Field { 20 | public PositiveNumberField(String key, String displayName, String defaultValue, Boolean required, Boolean secure, String displayOrder) { 21 | super(key, displayName, defaultValue, required, secure, displayOrder); 22 | } 23 | 24 | @Override 25 | public String doValidate(String input) { 26 | try { 27 | if (Integer.parseInt(input) <= 0) { 28 | return this.displayName + " must be a positive integer."; 29 | } 30 | } catch (NumberFormatException e) { 31 | return this.displayName + " must be a positive integer."; 32 | } 33 | 34 | return null; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/ClusterStatusReportRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.requests; 2 | 3 | import com.example.elasticagent.ClusterProfileProperties; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | 9 | public class ClusterStatusReportRequestTest { 10 | 11 | @Test 12 | public void shouldDeserializeFromJSON() { 13 | String json = "{\n" + 14 | " \"cluster_profile_properties\": {\n" + 15 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 16 | " \"auto_register_timeout\": \"20m\",\n" + 17 | " \"api_user\": \"test\",\n" + 18 | " \"api_key\": \"test-api-key\",\n" + 19 | " \"api_url\": \"https://aws.api.com/api\"\n" + 20 | " }\n" + 21 | "}"; 22 | 23 | ClusterStatusReportRequest clusterStatusReportRequest = ClusterStatusReportRequest.fromJSON(json); 24 | 25 | ClusterProfileProperties expectedClusterProfile = new ClusterProfileProperties( 26 | "https://localhost:8154/go", 27 | "20m", 28 | "test", 29 | "test-api-key", 30 | "https://aws.api.com/api", 31 | null 32 | ); 33 | 34 | assertThat(clusterStatusReportRequest.getClusterProfile(), is(expectedClusterProfile)); 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/PluginSettingsTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static org.hamcrest.MatcherAssert.assertThat; 22 | import static org.hamcrest.core.Is.is; 23 | 24 | public class PluginSettingsTest { 25 | @Test 26 | public void shouldDeserializeFromJSON() throws Exception { 27 | PluginSettings pluginSettings = PluginSettings.fromJSON("{" + 28 | "\"api_user\": \"bob\", " + 29 | "\"api_key\": \"p@ssw0rd\", " + 30 | "\"api_url\": \"https://cloud.example.com/api/v1\" " + 31 | "}"); 32 | 33 | assertThat(pluginSettings.getApiUser(), is("bob")); 34 | assertThat(pluginSettings.getApiKey(), is("p@ssw0rd")); 35 | assertThat(pluginSettings.getApiUrl(), is("https://cloud.example.com/api/v1")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/ClusterProfilePropertiesTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static org.hamcrest.MatcherAssert.assertThat; 22 | import static org.hamcrest.core.Is.is; 23 | 24 | public class ClusterProfilePropertiesTest { 25 | @Test 26 | public void shouldDeserializeFromJSON() throws Exception { 27 | ClusterProfileProperties pluginSettings = ClusterProfileProperties.fromJSON("{" + 28 | "\"api_user\": \"bob\", " + 29 | "\"api_key\": \"p@ssw0rd\", " + 30 | "\"api_url\": \"https://cloud.example.com/api/v1\" " + 31 | "}"); 32 | 33 | assertThat(pluginSettings.getApiUser(), is("bob")); 34 | assertThat(pluginSettings.getApiKey(), is("p@ssw0rd")); 35 | assertThat(pluginSettings.getApiUrl(), is("https://cloud.example.com/api/v1")); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/CreateAgentRequestExecutorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.AgentInstances; 20 | import com.example.elasticagent.PluginRequest; 21 | import com.example.elasticagent.PluginSettings; 22 | import com.example.elasticagent.requests.CreateAgentRequest; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import static org.mockito.Mockito.*; 26 | 27 | public class CreateAgentRequestExecutorTest { 28 | @Test 29 | public void shouldAskDockerContainersToCreateAnAgent() throws Exception { 30 | CreateAgentRequest request = new CreateAgentRequest(); 31 | AgentInstances agentInstances = mock(AgentInstances.class); 32 | PluginRequest pluginRequest = mock(PluginRequest.class); 33 | PluginSettings settings = mock(PluginSettings.class); 34 | 35 | new CreateAgentRequestExecutor(request, agentInstances).execute(); 36 | 37 | verify(agentInstances).create(request); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/GetElasticAgentProfileViewExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.RequestExecutor; 20 | import com.example.elasticagent.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 | public class GetElasticAgentProfileViewExecutor implements RequestExecutor { 27 | private static final Gson GSON = new Gson(); 28 | 29 | @Override 30 | public GoPluginApiResponse execute() throws Exception { 31 | JsonObject jsonObject = new JsonObject(); 32 | jsonObject.addProperty("template", Util.readResource("/profile.template.html")); 33 | DefaultGoPluginApiResponse defaultGoPluginApiResponse = new DefaultGoPluginApiResponse(200, GSON.toJson(jsonObject)); 34 | return defaultGoPluginApiResponse; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/GetClusterProfileMetadataExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.RequestExecutor; 4 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 5 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import static com.example.elasticagent.PluginSettings.GSON; 11 | 12 | public class GetClusterProfileMetadataExecutor implements RequestExecutor { 13 | public static final Metadata GO_SERVER_URL = new Metadata("go_server_url", "Go Server URL", true, false); 14 | public static final Metadata API_USER = new Metadata("api_user", "API User", true, false); 15 | public static final Metadata API_KEY = new Metadata("api_key", "API Key", true, false); 16 | public static final Metadata API_URL = new Metadata("api_url", "API URL", true, false); 17 | public static final Metadata AUTO_REGISTER_TIMEOUT = new NumberMetadata("auto_register_timeout", "Agent auto-register Timeout (in minutes)", true); 18 | 19 | 20 | public static final List CLUSTER_PROFILE_FIELDS = new ArrayList<>(); 21 | 22 | static { 23 | CLUSTER_PROFILE_FIELDS.add(GO_SERVER_URL); 24 | CLUSTER_PROFILE_FIELDS.add(API_USER); 25 | CLUSTER_PROFILE_FIELDS.add(API_KEY); 26 | CLUSTER_PROFILE_FIELDS.add(API_URL); 27 | CLUSTER_PROFILE_FIELDS.add(AUTO_REGISTER_TIMEOUT); 28 | } 29 | 30 | @Override 31 | 32 | public GoPluginApiResponse execute() throws Exception { 33 | return new DefaultGoPluginApiResponse(200, GSON.toJson(CLUSTER_PROFILE_FIELDS)); 34 | } 35 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/GetPluginIconExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.RequestExecutor; 20 | import com.example.elasticagent.utils.Util; 21 | import com.google.common.io.BaseEncoding; 22 | import com.google.gson.Gson; 23 | import com.google.gson.JsonObject; 24 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 25 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 26 | 27 | public class GetPluginIconExecutor implements RequestExecutor { 28 | private static final Gson GSON = new Gson(); 29 | 30 | @Override 31 | public GoPluginApiResponse execute() throws Exception { 32 | JsonObject jsonObject = new JsonObject(); 33 | jsonObject.addProperty("content_type", "image/svg+xml"); 34 | jsonObject.addProperty("data", BaseEncoding.base64().encode(Util.readResourceBytes("/plugin-icon.svg"))); 35 | return DefaultGoPluginApiResponse.success(GSON.toJson(jsonObject)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/CreateAgentRequestExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.AgentInstances; 20 | import com.example.elasticagent.RequestExecutor; 21 | import com.example.elasticagent.requests.CreateAgentRequest; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | public class CreateAgentRequestExecutor implements RequestExecutor { 26 | private final AgentInstances agentInstances; 27 | private final CreateAgentRequest request; 28 | 29 | public CreateAgentRequestExecutor(CreateAgentRequest request, AgentInstances agentInstances) { 30 | this.request = request; 31 | this.agentInstances = agentInstances; 32 | } 33 | 34 | @Override 35 | public GoPluginApiResponse execute() throws Exception { 36 | agentInstances.create(request); 37 | return new DefaultGoPluginApiResponse(200); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/MemoryMetadata.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | 20 | import com.example.elasticagent.utils.Size; 21 | import org.apache.commons.lang3.StringUtils; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Arrays; 25 | import java.util.Collections; 26 | import java.util.List; 27 | 28 | public class MemoryMetadata extends Metadata { 29 | 30 | public MemoryMetadata(String key, boolean required) { 31 | super(key, key, required, false); 32 | } 33 | 34 | @Override 35 | protected String doValidate(String input) { 36 | List errors = new ArrayList<>(Arrays.asList(super.doValidate(input))); 37 | 38 | try { 39 | Size.parse(input); 40 | } catch (Exception e) { 41 | errors.add(e.getMessage()); 42 | } 43 | 44 | errors.removeAll(Collections.singleton(null)); 45 | 46 | if (errors.isEmpty()) { 47 | return null; 48 | } 49 | return StringUtils.join(errors, ". "); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/PluginStatusReportRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.requests; 2 | 3 | import com.example.elasticagent.AgentInstances; 4 | import com.example.elasticagent.ClusterProfileProperties; 5 | import com.example.elasticagent.RequestExecutor; 6 | import com.example.elasticagent.executors.PluginStatusReportExecutor; 7 | import com.example.elasticagent.views.ViewBuilder; 8 | import com.google.gson.annotations.Expose; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import static com.example.elasticagent.ExamplePlugin.GSON; 14 | 15 | public class PluginStatusReportRequest { 16 | @Expose 17 | protected List allClusterProfilesProperties; 18 | 19 | public PluginStatusReportRequest() { 20 | } 21 | 22 | public static PluginStatusReportRequest fromJSON(String json) { 23 | return GSON.fromJson(json, PluginStatusReportRequest.class); 24 | } 25 | 26 | public RequestExecutor executor(Map clusterSpecificAgentInstances, ViewBuilder instance) { 27 | return new PluginStatusReportExecutor(this, clusterSpecificAgentInstances, instance); 28 | } 29 | 30 | public List allClusterProfileProperties() { 31 | return allClusterProfilesProperties; 32 | } 33 | 34 | // Dummy implementation to show status report rendering; not relevant in a real implementation 35 | public List allClusterProfilePropertiesWithDefaultClusterProfile() { 36 | return allClusterProfilesProperties.isEmpty() 37 | ? List.of(new ClusterProfileProperties()) 38 | : allClusterProfilesProperties; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/ValidateElasticAgentProfileRequest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.RequestExecutor; 20 | import com.example.elasticagent.executors.ValidateElasticAgentProfileRequestExecutor; 21 | import com.google.gson.Gson; 22 | 23 | import java.util.Map; 24 | 25 | public class ValidateElasticAgentProfileRequest { 26 | private static final Gson GSON = new Gson(); 27 | private Map properties; 28 | 29 | public ValidateElasticAgentProfileRequest(Map properties) { 30 | this.properties = properties; 31 | } 32 | 33 | 34 | public Map getProperties() { 35 | return properties; 36 | } 37 | 38 | public static ValidateElasticAgentProfileRequest fromJSON(String json) { 39 | return new ValidateElasticAgentProfileRequest(GSON.fromJson(json, Map.class)); 40 | } 41 | 42 | public RequestExecutor executor() { 43 | return new ValidateElasticAgentProfileRequestExecutor(this); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/ClusterProfileProperties.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import org.joda.time.Period; 20 | 21 | import java.util.Map; 22 | 23 | // TODO: Implement any settings that your plugin needs 24 | public class ClusterProfileProperties extends PluginSettings{ 25 | public ClusterProfileProperties() { 26 | } 27 | 28 | public ClusterProfileProperties(String goServerUrl, String autoRegisterTimeout, String apiUser, String apiKey, String apiUrl, Period autoRegisterPeriod) { 29 | super(goServerUrl, autoRegisterTimeout, apiUser, apiKey, apiUrl, autoRegisterPeriod); 30 | } 31 | 32 | public static ClusterProfileProperties fromJSON(String json) { 33 | return GSON.fromJson(json, ClusterProfileProperties.class); 34 | } 35 | 36 | public static ClusterProfileProperties fromConfiguration(Map clusterProfile) { 37 | return GSON.fromJson(GSON.toJson(clusterProfile), ClusterProfileProperties.class); 38 | } 39 | 40 | public String uuid() { 41 | return Integer.toHexString(hashCode()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/resources/profile.template.html: -------------------------------------------------------------------------------- 1 | 16 | 17 |
18 |
19 | 20 | 21 | {{GOINPUTNAME[Image].$error.server}} 22 |
23 | 24 |
25 | 26 | 27 | {{GOINPUTNAME[MaxMemory].$error.server}} 28 |
29 |
30 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/JobCompletionRequestExecutorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.AgentInstances; 20 | import com.example.elasticagent.ClusterProfileProperties; 21 | import com.example.elasticagent.models.JobIdentifier; 22 | import com.example.elasticagent.requests.JobCompletionRequest; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import java.util.HashMap; 26 | 27 | import static org.mockito.Mockito.*; 28 | 29 | public class JobCompletionRequestExecutorTest { 30 | @Test 31 | public void shouldAskDockerContainersToCreateAnAgent() throws Exception { 32 | String elasticAgentId = "agent-id"; 33 | ClusterProfileProperties clusterProfileProperties = new ClusterProfileProperties(); 34 | JobCompletionRequest request = new JobCompletionRequest(elasticAgentId, new JobIdentifier(), new HashMap<>(), clusterProfileProperties); 35 | AgentInstances agentInstances = mock(AgentInstances.class); 36 | new JobCompletionRequestExecutor(request, agentInstances).execute(); 37 | verify(agentInstances).terminate(elasticAgentId, clusterProfileProperties); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/NumberMetadataTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Map; 6 | 7 | import static org.hamcrest.CoreMatchers.hasItems; 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | 11 | class NumberMetadataTest { 12 | 13 | @Test 14 | public void shouldErrorOutWhenValueIsRequiredAndBlank() { 15 | Metadata metadata = new NumberMetadata("number-meta", "Foo", true); 16 | Map error = metadata.validate(""); 17 | assertThat(error.keySet(), hasItems("key", "message")); 18 | assertThat(error.get("key"), is("number-meta")); 19 | assertThat(error.get("message"), is("Foo must be a positive integer.")); 20 | } 21 | 22 | @Test 23 | public void shouldNotErrorOutWhenValueIsRequiredAndNotBlank() { 24 | Metadata metadata = new NumberMetadata("number-meta", "Foo", true); 25 | Map error = metadata.validate("20"); 26 | assertThat(error.size(), is(0)); 27 | } 28 | 29 | @Test 30 | public void shouldErrorOutIfValueIsNotPositive() { 31 | Metadata metadata = new NumberMetadata("number-meta", "Foo", false); 32 | Map error = metadata.validate("-20"); 33 | assertThat(error.keySet(), hasItems("key", "message")); 34 | assertThat(error.get("key"), is("number-meta")); 35 | assertThat(error.get("message"), is("Foo must be a positive integer.")); 36 | } 37 | 38 | @Test 39 | public void shouldNotErrorOutWhenValueIsNotRequiredAndBlank() { 40 | Metadata metadata = new NumberMetadata("number-meta", "Foo", false); 41 | Map error = metadata.validate(""); 42 | assertThat(error.size(), is(0)); 43 | } 44 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/ClusterStatusReportExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.AgentInstances; 4 | import com.example.elasticagent.RequestExecutor; 5 | import com.example.elasticagent.models.StatusReport; 6 | import com.example.elasticagent.requests.ClusterStatusReportRequest; 7 | import com.example.elasticagent.views.ViewBuilder; 8 | import com.google.gson.JsonObject; 9 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 10 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 11 | 12 | import static com.example.elasticagent.ExamplePlugin.LOG; 13 | 14 | public class ClusterStatusReportExecutor implements RequestExecutor { 15 | 16 | private final ClusterStatusReportRequest clusterStatusReportRequest; 17 | private final AgentInstances agentInstances; 18 | private final ViewBuilder viewBuilder; 19 | 20 | public ClusterStatusReportExecutor(ClusterStatusReportRequest clusterStatusReportRequest, AgentInstances agentInstances, ViewBuilder viewBuilder) { 21 | this.clusterStatusReportRequest = clusterStatusReportRequest; 22 | this.agentInstances = agentInstances; 23 | this.viewBuilder = viewBuilder; 24 | } 25 | 26 | @Override 27 | public GoPluginApiResponse execute() throws Exception { 28 | LOG.info("[status-report] Generating status report"); 29 | 30 | StatusReport statusReport = agentInstances.getStatusReport(clusterStatusReportRequest.getClusterProfile()); 31 | 32 | final String statusReportView = viewBuilder.build("status-report.template", statusReport); 33 | 34 | JsonObject responseJSON = new JsonObject(); 35 | responseJSON.addProperty("view", statusReportView); 36 | 37 | return DefaultGoPluginApiResponse.success(responseJSON.toString()); 38 | } 39 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/GetElasticAgentProfileMetadataExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.RequestExecutor; 20 | import com.google.gson.Gson; 21 | import com.google.gson.GsonBuilder; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | public class GetElasticAgentProfileMetadataExecutor implements RequestExecutor { 29 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); 30 | 31 | public static final Metadata IMAGE = new Metadata("Image", "Image", true, false); 32 | public static final Metadata MAX_MEMORY = new MemoryMetadata("MaxMemory", false); 33 | 34 | public static final List FIELDS = new ArrayList<>(); 35 | 36 | static { 37 | FIELDS.add(IMAGE); 38 | FIELDS.add(MAX_MEMORY); 39 | } 40 | 41 | @Override 42 | 43 | public GoPluginApiResponse execute() throws Exception { 44 | return new DefaultGoPluginApiResponse(200, GSON.toJson(FIELDS)); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/GetPluginIconExecutorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.utils.Util; 20 | import com.google.common.io.BaseEncoding; 21 | import com.google.gson.Gson; 22 | import com.google.gson.reflect.TypeToken; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | import org.junit.jupiter.api.Test; 25 | 26 | import java.lang.reflect.Type; 27 | import java.util.Map; 28 | 29 | import static org.hamcrest.CoreMatchers.is; 30 | import static org.hamcrest.MatcherAssert.assertThat; 31 | 32 | public class GetPluginIconExecutorTest { 33 | 34 | @Test 35 | public void rendersIconInBase64() throws Exception { 36 | GoPluginApiResponse response = new GetPluginIconExecutor().execute(); 37 | Type type = new TypeToken>() { 38 | }.getType(); 39 | Map map = new Gson().fromJson(response.responseBody(), 40 | type); 41 | assertThat(map.size(), is(2)); 42 | assertThat(map.get("content_type"), is("image/svg+xml")); 43 | System.out.println("hashMap = " + map.get("data")); 44 | assertThat(Util.readResourceBytes("/plugin-icon.svg"), is(BaseEncoding.base64().decode(map.get("data")))); 45 | } 46 | } -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/MemoryMetadataTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import java.util.Map; 22 | 23 | import static org.hamcrest.MatcherAssert.assertThat; 24 | import static org.hamcrest.Matchers.hasEntry; 25 | import static org.hamcrest.Matchers.is; 26 | import static org.junit.jupiter.api.Assertions.assertTrue; 27 | 28 | public class MemoryMetadataTest { 29 | 30 | @Test 31 | public void shouldValidateMemoryBytes() { 32 | assertTrue(new MemoryMetadata("Disk", false).validate("100mb").isEmpty()); 33 | 34 | Map validate = new MemoryMetadata("Disk", false).validate("xxx"); 35 | assertThat(validate.size(), is(2)); 36 | assertThat(validate, hasEntry("message", "Invalid size: xxx")); 37 | assertThat(validate, hasEntry("key", "Disk")); 38 | } 39 | 40 | @Test 41 | public void shouldValidateMemoryBytesWhenRequireField() throws Exception { 42 | Map validate = new MemoryMetadata("Disk", true).validate(null); 43 | assertThat(validate.size(), is(2)); 44 | assertThat(validate, hasEntry("message", "Disk must not be blank. Invalid size: null")); 45 | assertThat(validate, hasEntry("key", "Disk")); 46 | } 47 | } -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/GetClusterProfileViewRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.utils.Util; 4 | import com.google.gson.Gson; 5 | import com.google.gson.reflect.TypeToken; 6 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 7 | import org.junit.jupiter.api.Test; 8 | import static org.hamcrest.Matchers.is; 9 | import static org.hamcrest.Matchers.hasEntry; 10 | import static org.hamcrest.Matchers.containsString; 11 | 12 | 13 | import java.lang.reflect.Type; 14 | import java.util.Map; 15 | 16 | import static org.hamcrest.MatcherAssert.assertThat; 17 | 18 | public class GetClusterProfileViewRequestExecutorTest { 19 | 20 | public static final String CLUSTER_PROFILE_TEMPLATE = "/cluster-profile.template.html"; 21 | 22 | @Test 23 | public void shouldRenderTheTemplateInJSON() throws Exception { 24 | GoPluginApiResponse response = new GetClusterProfileViewRequestExecutor().execute(); 25 | assertThat(response.responseCode(), is(200)); 26 | final Type type = new TypeToken>() { 27 | }.getType(); 28 | Map hashSet = new Gson().fromJson(response.responseBody(), type); 29 | assertThat(hashSet, hasEntry("template", Util.readResource(CLUSTER_PROFILE_TEMPLATE))); 30 | } 31 | 32 | @Test 33 | public void allFieldsShouldBePresentInView() throws Exception { 34 | String template = Util.readResource(CLUSTER_PROFILE_TEMPLATE); 35 | 36 | for (Metadata field : GetClusterProfileMetadataExecutor.CLUSTER_PROFILE_FIELDS) { 37 | assertThat(template, containsString("ng-model=\"" + field.getKey() + "\"")); 38 | assertThat(template, containsString("{{GOINPUTNAME[" + field.getKey() + 40 | "].$error.server}}")); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/ServerPingRequest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.PluginRequest; 21 | import com.example.elasticagent.executors.ServerPingRequestExecutor; 22 | import com.google.gson.annotations.Expose; 23 | 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import static com.example.elasticagent.ExamplePlugin.GSON; 28 | 29 | public class ServerPingRequest { 30 | 31 | @Expose 32 | protected List allClusterProfileProperties; 33 | 34 | public ServerPingRequest() { 35 | } 36 | 37 | public ServerPingRequest(List profileList) { 38 | this.allClusterProfileProperties = profileList; 39 | } 40 | 41 | public static ServerPingRequest fromJSON(String json) { 42 | return GSON.fromJson(json, ServerPingRequest.class); 43 | } 44 | 45 | public List allClusterProfileProperties() { 46 | return allClusterProfileProperties; 47 | } 48 | 49 | public RequestExecutor executor(Map clusterSpecificAgentInstances, PluginRequest pluginRequest) { 50 | return new ServerPingRequestExecutor(clusterSpecificAgentInstances, this, pluginRequest); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/Clock.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import org.joda.time.DateTime; 20 | import org.joda.time.Period; 21 | 22 | public interface Clock { 23 | DateTime now(); 24 | 25 | Clock DEFAULT = new Clock() { 26 | @Override 27 | public DateTime now() { 28 | return new DateTime(); 29 | } 30 | }; 31 | 32 | class TestClock implements Clock { 33 | 34 | DateTime time = null; 35 | 36 | public TestClock(DateTime time) { 37 | this.time = time; 38 | } 39 | 40 | public TestClock() { 41 | this(new DateTime()); 42 | } 43 | 44 | @Override 45 | public DateTime now() { 46 | return time; 47 | } 48 | 49 | public TestClock reset() { 50 | time = new DateTime(); 51 | return this; 52 | } 53 | 54 | public TestClock set(DateTime time) { 55 | this.time = time; 56 | return this; 57 | } 58 | 59 | public TestClock rewind(Period period) { 60 | this.time = this.time.minus(period); 61 | return this; 62 | } 63 | 64 | public TestClock forward(Period period) { 65 | this.time = this.time.plus(period); 66 | return this; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/Constants.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.example.elasticagent.utils.Util; 20 | import com.thoughtworks.go.plugin.api.GoPluginIdentifier; 21 | 22 | import java.util.Collections; 23 | 24 | public interface Constants { 25 | String PLUGIN_ID = Util.pluginId(); 26 | 27 | // The type of this extension 28 | String EXTENSION_TYPE = "elastic-agent"; 29 | 30 | // The extension point API version that this plugin understands 31 | String ELASTIC_PROCESSOR_API_VERSION = "1.0"; 32 | String SERVER_INFO_PROCESSOR_API_VERSION = "1.0"; 33 | String EXTENSION_API_VERSION = "5.0"; 34 | 35 | // the identifier of this plugin 36 | GoPluginIdentifier PLUGIN_IDENTIFIER = new GoPluginIdentifier(EXTENSION_TYPE, Collections.singletonList(EXTENSION_API_VERSION)); 37 | 38 | // requests that the plugin makes to the server 39 | String REQUEST_SERVER_PREFIX = "go.processor"; 40 | String REQUEST_SERVER_DISABLE_AGENT = REQUEST_SERVER_PREFIX + ".elastic-agents.disable-agents"; 41 | String REQUEST_SERVER_DELETE_AGENT = REQUEST_SERVER_PREFIX + ".elastic-agents.delete-agents"; 42 | String REQUEST_SERVER_GET_PLUGIN_SETTINGS = REQUEST_SERVER_PREFIX + ".plugin-settings.get"; 43 | String REQUEST_SERVER_LIST_AGENTS = REQUEST_SERVER_PREFIX + ".elastic-agents.list-agents"; 44 | String REQUEST_SERVER_INFO = REQUEST_SERVER_PREFIX + ".server-info.get"; 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/ShouldAssignWorkRequestExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.AgentInstances; 20 | import com.example.elasticagent.ExampleInstance; 21 | import com.example.elasticagent.RequestExecutor; 22 | import com.example.elasticagent.requests.ShouldAssignWorkRequest; 23 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 24 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 25 | 26 | public class ShouldAssignWorkRequestExecutor implements RequestExecutor { 27 | private final AgentInstances agentInstances; 28 | private final ShouldAssignWorkRequest request; 29 | 30 | public ShouldAssignWorkRequestExecutor(ShouldAssignWorkRequest request, AgentInstances agentInstances) { 31 | this.request = request; 32 | this.agentInstances = agentInstances; 33 | } 34 | 35 | @Override 36 | public GoPluginApiResponse execute() { 37 | ExampleInstance instance = agentInstances.find(request.agent().elasticAgentId()); 38 | 39 | if (instance == null) { 40 | return DefaultGoPluginApiResponse.success("false"); 41 | } 42 | 43 | if (instance.jobIdentifier().equals(request.jobIdentifier())) { 44 | return DefaultGoPluginApiResponse.success("true"); 45 | } 46 | 47 | return DefaultGoPluginApiResponse.success("false"); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/ClusterProfileValidateRequestExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.RequestExecutor; 4 | import com.example.elasticagent.executors.Metadata; 5 | import com.example.elasticagent.requests.ClusterProfileValidateRequest; 6 | import com.google.gson.Gson; 7 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 8 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 9 | 10 | import java.util.*; 11 | 12 | import static com.example.elasticagent.executors.GetClusterProfileMetadataExecutor.CLUSTER_PROFILE_FIELDS; 13 | 14 | public class ClusterProfileValidateRequestExecutor implements RequestExecutor { 15 | private final ClusterProfileValidateRequest request; 16 | private static final Gson GSON = new Gson(); 17 | 18 | public ClusterProfileValidateRequestExecutor(ClusterProfileValidateRequest request) { 19 | this.request = request; 20 | } 21 | 22 | @Override 23 | public GoPluginApiResponse execute() throws Exception { 24 | ArrayList> result = new ArrayList<>(); 25 | 26 | List knownFields = new ArrayList<>(); 27 | 28 | for (Metadata field : CLUSTER_PROFILE_FIELDS) { 29 | knownFields.add(field.getKey()); 30 | Map validationError = field.validate(request.getProperties().get(field.getKey())); 31 | 32 | if (!validationError.isEmpty()) { 33 | result.add(validationError); 34 | } 35 | } 36 | 37 | Set set = new HashSet<>(request.getProperties().keySet()); 38 | set.removeAll(knownFields); 39 | 40 | if (!set.isEmpty()) { 41 | for (String key : set) { 42 | LinkedHashMap validationError = new LinkedHashMap<>(); 43 | validationError.put("key", key); 44 | validationError.put("message", "Is an unknown property"); 45 | result.add(validationError); 46 | } 47 | } 48 | 49 | return DefaultGoPluginApiResponse.success(GSON.toJson(result)); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/ServerPingRequestTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.ClusterProfileProperties; 20 | import org.junit.jupiter.api.Test; 21 | 22 | import static org.hamcrest.MatcherAssert.assertThat; 23 | import static org.hamcrest.Matchers.equalTo; 24 | 25 | public class ServerPingRequestTest { 26 | 27 | @Test 28 | public void shouldDeserializeFromJSON() throws Exception { 29 | String json = "{\n" + 30 | " \"all_cluster_profile_properties\": [{\n" + 31 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 32 | " \"auto_register_timeout\": \"20\",\n" + 33 | " \"api_user\": \"test\",\n" + 34 | " \"api_key\": \"test-api-key\",\n" + 35 | " \"api_url\": \"https://aws.api.com/api\"\n" + 36 | " }]\n" + 37 | "}"; 38 | 39 | ClusterProfileProperties clusterProfile = new ClusterProfileProperties( 40 | "https://localhost:8154/go", 41 | "20", 42 | "test", 43 | "test-api-key", 44 | "https://aws.api.com/api", 45 | null 46 | ); 47 | 48 | ServerPingRequest request = ServerPingRequest.fromJSON(json); 49 | assertThat(request.allClusterProfileProperties().size(), equalTo(1)); 50 | assertThat(request.allClusterProfileProperties().get(0), equalTo(clusterProfile)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/ServerRequestFailedException.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoApiResponse; 20 | 21 | import static java.lang.String.format; 22 | 23 | public class ServerRequestFailedException extends Exception { 24 | 25 | private ServerRequestFailedException(GoApiResponse response, String request) { 26 | super(format( 27 | "The server sent an unexpected status code %d with the response body %s when it was sent a %s message", 28 | response.responseCode(), response.responseBody(), request 29 | )); 30 | } 31 | 32 | public static ServerRequestFailedException disableAgents(GoApiResponse response) { 33 | return new ServerRequestFailedException(response, "disable agents"); 34 | } 35 | 36 | public static ServerRequestFailedException deleteAgents(GoApiResponse response) { 37 | return new ServerRequestFailedException(response, "delete agents"); 38 | } 39 | 40 | public static ServerRequestFailedException listAgents(GoApiResponse response) { 41 | return new ServerRequestFailedException(response, "list agents"); 42 | } 43 | 44 | public static ServerRequestFailedException getPluginSettings(GoApiResponse response) { 45 | return new ServerRequestFailedException(response, "get plugin settings"); 46 | } 47 | 48 | public static ServerRequestFailedException serverInfo(GoApiResponse response) { 49 | return new ServerRequestFailedException(response, "get server info"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/utils/Util.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.utils; 18 | 19 | import com.google.common.io.ByteStreams; 20 | import com.google.common.io.CharStreams; 21 | 22 | import java.io.IOException; 23 | import java.io.InputStream; 24 | import java.io.InputStreamReader; 25 | import java.io.StringReader; 26 | import java.nio.charset.StandardCharsets; 27 | import java.util.Properties; 28 | 29 | public class Util { 30 | 31 | public static String readResource(String resourceFile) { 32 | try (InputStreamReader reader = new InputStreamReader(Util.class.getResourceAsStream(resourceFile), StandardCharsets.UTF_8)) { 33 | return CharStreams.toString(reader); 34 | } catch (IOException e) { 35 | throw new RuntimeException("Could not find resource " + resourceFile, e); 36 | } 37 | } 38 | 39 | public static byte[] readResourceBytes(String resourceFile) { 40 | try (InputStream in = Util.class.getResourceAsStream(resourceFile)) { 41 | return ByteStreams.toByteArray(in); 42 | } catch (IOException e) { 43 | throw new RuntimeException("Could not find resource " + resourceFile, e); 44 | } 45 | } 46 | 47 | public static String pluginId() { 48 | String s = readResource("/plugin.properties"); 49 | try { 50 | Properties properties = new Properties(); 51 | properties.load(new StringReader(s)); 52 | return (String) properties.get("pluginId"); 53 | } catch (IOException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/ValidateElasticAgentProfileRequestExecutorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.requests.ValidateElasticAgentProfileRequest; 20 | import org.junit.jupiter.api.Test; 21 | import org.skyscreamer.jsonassert.JSONAssert; 22 | import org.skyscreamer.jsonassert.JSONCompareMode; 23 | 24 | import java.util.Collections; 25 | 26 | public class ValidateElasticAgentProfileRequestExecutorTest { 27 | @Test 28 | public void shouldBarfWhenUnknownKeysArePassed() throws Exception { 29 | ValidateElasticAgentProfileRequestExecutor executor = new ValidateElasticAgentProfileRequestExecutor(new ValidateElasticAgentProfileRequest(Collections.singletonMap("foo", "bar"))); 30 | String json = executor.execute().responseBody(); 31 | JSONAssert.assertEquals("[{\"message\":\"Image must not be blank\",\"key\":\"Image\"},{\"message\":\"Invalid size: null\",\"key\":\"MaxMemory\"},{\"key\":\"foo\",\"message\":\"Is an unknown property\"}]", json, JSONCompareMode.NON_EXTENSIBLE); 32 | } 33 | 34 | @Test 35 | public void shouldValidateMandatoryKeys() throws Exception { 36 | ValidateElasticAgentProfileRequestExecutor executor = new ValidateElasticAgentProfileRequestExecutor(new ValidateElasticAgentProfileRequest(Collections.emptyMap())); 37 | String json = executor.execute().responseBody(); 38 | JSONAssert.assertEquals("[{\"message\":\"Image must not be blank\",\"key\":\"Image\"},{\"message\":\"Invalid size: null\",\"key\":\"MaxMemory\"}]", json, JSONCompareMode.NON_EXTENSIBLE); 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/AgentStatusReportRequestTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.requests; 2 | 3 | import com.example.elasticagent.ClusterProfileProperties; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import static org.hamcrest.CoreMatchers.is; 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | 9 | public class AgentStatusReportRequestTest { 10 | 11 | @Test 12 | public void shouldDeserializeFromJSON() { 13 | final String requestBody = "{\n" + 14 | " \"job_identifier\": {\n" + 15 | " \"pipeline_name\": \"up42\",\n" + 16 | " \"pipeline_label\": \"Test\",\n" + 17 | " \"pipeline_counter\": 2,\n" + 18 | " \"stage_name\": \"up42_stage\",\n" + 19 | " \"stage_counter\": \"10\",\n" + 20 | " \"job_name\": \"up42_job\",\n" + 21 | " \"job_id\": -1\n" + 22 | " },\n" + 23 | " \"cluster_profile_properties\":{" + 24 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 25 | " \"auto_register_timeout\": \"20m\",\n" + 26 | " \"api_user\": \"test\",\n" + 27 | " \"api_key\": \"test-api-key\",\n" + 28 | " \"api_url\": \"https://aws.api.com/api\"\n" + 29 | " }," + 30 | " \"elastic_agent_id\": \"GoCD193659b3b930480287b898eeef0ade37\"\n" + 31 | "}"; 32 | 33 | AgentStatusReportRequest agentStatusReportRequest = AgentStatusReportRequest.fromJSON(requestBody); 34 | 35 | ClusterProfileProperties expectedClusterProperties = new ClusterProfileProperties( 36 | "https://localhost:8154/go", 37 | "20m", 38 | "test", 39 | "test-api-key", 40 | "https://aws.api.com/api", 41 | null 42 | ); 43 | 44 | assertThat(agentStatusReportRequest.getElasticAgentId(), is("GoCD193659b3b930480287b898eeef0ade37")); 45 | assertThat(agentStatusReportRequest.clusterProperties(), is(expectedClusterProperties)); 46 | assertThat(agentStatusReportRequest.getJobIdentifier().represent(), is("up42/2/up42_stage/10/up42_job")); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/MigrateConfigPayload.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.executors.MigrateConfigRequestExecutor; 21 | import com.google.gson.annotations.Expose; 22 | 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | import static com.example.elasticagent.ExamplePlugin.GSON; 27 | 28 | public class MigrateConfigPayload { 29 | @Expose 30 | private PluginSettings pluginSettings; 31 | 32 | @Expose 33 | private List> elasticAgentProfiles; 34 | 35 | @Expose 36 | private List clusterProfiles; 37 | 38 | public MigrateConfigPayload() { 39 | } 40 | 41 | public MigrateConfigPayload(PluginSettings pluginSettings, List> elasticAgentProfileProperties, List clusterProfiles) { 42 | this.pluginSettings = pluginSettings; 43 | this.elasticAgentProfiles = elasticAgentProfileProperties; 44 | this.clusterProfiles = clusterProfiles; 45 | } 46 | 47 | public static MigrateConfigPayload fromJSON(String json) { 48 | return GSON.fromJson(json, MigrateConfigPayload.class); 49 | } 50 | 51 | public RequestExecutor executor(Map allAgentInstances) { 52 | return new MigrateConfigRequestExecutor(this); 53 | } 54 | 55 | public PluginSettings pluginSettings() { 56 | return pluginSettings; 57 | } 58 | 59 | public List> elasticAgentProfileProperties() { 60 | return elasticAgentProfiles; 61 | } 62 | 63 | public List clusterProfiles() { 64 | return clusterProfiles; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/GetElasticAgentProfileViewExecutorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import java.util.HashMap; 25 | import java.util.Map; 26 | 27 | import static org.hamcrest.MatcherAssert.assertThat; 28 | import static org.hamcrest.Matchers.*; 29 | 30 | public class GetElasticAgentProfileViewExecutorTest { 31 | @Test 32 | public void shouldRenderTheTemplateInJSON() throws Exception { 33 | GoPluginApiResponse response = new GetElasticAgentProfileViewExecutor().execute(); 34 | assertThat(response.responseCode(), is(200)); 35 | Map hashSet = new Gson().fromJson(response.responseBody(), HashMap.class); 36 | assertThat(hashSet, hasEntry("template", Util.readResource("/profile.template.html"))); 37 | } 38 | 39 | @Test 40 | public void allFieldsShouldBePresentInView() throws Exception { 41 | String template = Util.readResource("/profile.template.html"); 42 | 43 | for (Metadata field : GetElasticAgentProfileMetadataExecutor.FIELDS) { 44 | assertThat(template, containsString("ng-model=\"" + field.getKey() + "\"")); 45 | assertThat(template, containsString("{{GOINPUTNAME[" + 48 | field.getKey() + "].$error.server}}")); 49 | } 50 | } 51 | 52 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/views/ViewBuilder.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.views; 2 | 3 | import com.example.elasticagent.ExampleInstance; 4 | import com.example.elasticagent.models.StatusReport; 5 | import com.google.common.html.HtmlEscapers; 6 | import org.joda.time.DateTime; 7 | 8 | import java.util.Map; 9 | import java.util.stream.Collectors; 10 | 11 | //TODO You can modify and use this class to generate views 12 | @SuppressWarnings("UnstableApiUsage") 13 | public class ViewBuilder { 14 | 15 | private static ViewBuilder builder; 16 | 17 | public static ViewBuilder instance() { 18 | if (builder == null) { 19 | builder = new ViewBuilder(); 20 | } 21 | return builder; 22 | } 23 | 24 | public String build(String template) { 25 | return ""; 26 | } 27 | 28 | public String build(String template, Object model) { 29 | return ""; 30 | } 31 | 32 | public String build(String template, StatusReport statusReport) { 33 | // TODO implement me! 34 | 35 | return headingFor(statusReport) + instancesListing(statusReport.getInstances()); 36 | } 37 | 38 | private static String headingFor(StatusReport statusReport) { 39 | return String.format("

Status for cluster profile %s:

", statusReport.getClusterProfileUuid()); 40 | } 41 | 42 | private String instancesListing(Map instances) { 43 | return instances.entrySet().stream() 44 | .map(blah -> titleView(blah.getKey()) + instanceView(blah.getValue())) 45 | .collect(Collectors.joining("
")); 46 | } 47 | 48 | private String titleView(String instanceId) { 49 | return String.format("
ID: %s
", 50 | HtmlEscapers.htmlEscaper().escape(instanceId)); 51 | } 52 | 53 | private String instanceView(ExampleInstance instance) { 54 | return String.format("
Name: %s
Created At: %s
", 55 | HtmlEscapers.htmlEscaper().escape(instance.name()), 56 | toViewRenderedTime(instance.createdAt())); 57 | } 58 | 59 | private String toViewRenderedTime(DateTime dateTime) { 60 | // Render something using Angular! 61 | return HtmlEscapers.htmlEscaper().escape( 62 | String.format("{{ %s | date:'MMM dd, yyyy HH:mm Z' }}", dateTime.getMillis()) 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/PluginStatusReportExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.AgentInstances; 4 | import com.example.elasticagent.ClusterProfileProperties; 5 | import com.example.elasticagent.RequestExecutor; 6 | import com.example.elasticagent.models.StatusReport; 7 | import com.example.elasticagent.requests.PluginStatusReportRequest; 8 | import com.example.elasticagent.views.ViewBuilder; 9 | import com.google.gson.JsonObject; 10 | import com.thoughtworks.go.plugin.api.logging.Logger; 11 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 12 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | 19 | // Make changes as needed 20 | public class PluginStatusReportExecutor implements RequestExecutor { 21 | 22 | private final PluginStatusReportRequest request; 23 | private final Map allClusterInstances; 24 | private final ViewBuilder viewBuilder; 25 | private static final Logger LOG = Logger.getLoggerFor(AgentStatusReportExecutor.class); 26 | 27 | public PluginStatusReportExecutor(PluginStatusReportRequest request, Map allClusterInstances, ViewBuilder viewBuilder) { 28 | this.request = request; 29 | this.allClusterInstances = allClusterInstances; 30 | this.viewBuilder = viewBuilder; 31 | } 32 | 33 | @Override 34 | public GoPluginApiResponse execute() throws Exception { 35 | LOG.info("[status-report] Generating status report"); 36 | 37 | List reports = new ArrayList<>(); 38 | 39 | for (ClusterProfileProperties profile : request.allClusterProfilePropertiesWithDefaultClusterProfile()) { 40 | AgentInstances agentInstances = allClusterInstances.get(profile.uuid()); 41 | StatusReport statusReport = agentInstances.getStatusReport(profile); 42 | reports.add(viewBuilder.build("status-report-template", statusReport)); 43 | } 44 | 45 | // aggregate reports for different cluster into one 46 | JsonObject responseJSON = new JsonObject(); 47 | responseJSON.addProperty("view", String.join("
", reports)); 48 | return DefaultGoPluginApiResponse.success(responseJSON.toString()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GoCD Elastic agent plugin skeleton 2 | 3 | This is merely a skeleton plugin that plugin developers can fork to get quickly 4 | started with writing elastic agent plugins for GoCD. 5 | 6 | All the documentation is hosted at https://plugin-api.gocd.io/current/elastic-agents/. 7 | 8 | 9 | ## Getting started 10 | 11 | * Edit the file `build.gradle` 12 | * Edit the file `settings.gradle` 13 | * Edit the `GetClusterProfileMetadataExecutor.java` class to add any configuration fields that should be shown in the view. 14 | * Edit the `cluster-profile.template.html` file which contains the view for the plugin settings page of your plugin. 15 | * Edit the `ClusterProfile.java` file which contains the model for your cluster profile properties. 16 | * Implement the `ExampleAgentInstances.java` class to get a really basic elastic agent plugin working. 17 | 18 | ## Building the code base 19 | 20 | To build the jar, run `./gradlew clean test assemble` 21 | 22 | ## License 23 | 24 | ```plain 25 | Copyright 2017 ThoughtWorks, Inc. 26 | 27 | Licensed under the Apache License, Version 2.0 (the "License"); 28 | you may not use this file except in compliance with the License. 29 | You may obtain a copy of the License at 30 | 31 | http://www.apache.org/licenses/LICENSE-2.0 32 | 33 | Unless required by applicable law or agreed to in writing, software 34 | distributed under the License is distributed on an "AS IS" BASIS, 35 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 36 | See the License for the specific language governing permissions and 37 | limitations under the License. 38 | ``` 39 | 40 | ## About the license and releasing your plugin under a different license 41 | 42 | The skeleton code in this repository is licensed under the Apache 2.0 license. The license itself specifies the terms 43 | under which derivative works may be distributed (the license also defines derivative works). The Apache 2.0 license is a 44 | permissive open source license that has minimal requirements for downstream licensors/licensees to comply with. 45 | 46 | This does not prevent your plugin from being licensed under a different license as long as you comply with the relevant 47 | clauses of the Apache 2.0 license (especially section 4). Typically, you clone this repository and keep the existing 48 | copyright notices. You are free to add your own license and copyright notice to any modifications. 49 | 50 | This is not legal advice. Please contact your lawyers if needed. 51 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/Field.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | import org.apache.commons.lang3.StringUtils; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | public class Field { 27 | protected final String key; 28 | 29 | @Expose 30 | @SerializedName("display-name") 31 | protected String displayName; 32 | 33 | @Expose 34 | @SerializedName("default-value") 35 | protected String defaultValue; 36 | 37 | @Expose 38 | @SerializedName("required") 39 | protected Boolean required; 40 | 41 | @Expose 42 | @SerializedName("secure") 43 | protected Boolean secure; 44 | 45 | @Expose 46 | @SerializedName("display-order") 47 | protected String displayOrder; 48 | 49 | public Field(String key, String displayName, String defaultValue, Boolean required, Boolean secure, String displayOrder) { 50 | this.key = key; 51 | this.displayName = displayName; 52 | this.defaultValue = defaultValue; 53 | this.required = required; 54 | this.secure = secure; 55 | this.displayOrder = displayOrder; 56 | } 57 | 58 | public Map validate(String input) { 59 | HashMap result = new HashMap<>(); 60 | String validationError = doValidate(input); 61 | if (StringUtils.isNotBlank(validationError)) { 62 | result.put("key", key); 63 | result.put("message", validationError); 64 | } 65 | return result; 66 | } 67 | 68 | protected String doValidate(String input) { 69 | return null; 70 | } 71 | 72 | public String key() { 73 | return key; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/ClusterStatusReportRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.requests; 2 | 3 | import com.example.elasticagent.AgentInstances; 4 | import com.example.elasticagent.ClusterProfileProperties; 5 | import com.example.elasticagent.executors.ClusterStatusReportExecutor; 6 | import com.example.elasticagent.views.ViewBuilder; 7 | import com.google.gson.FieldNamingPolicy; 8 | import com.google.gson.Gson; 9 | import com.google.gson.GsonBuilder; 10 | import com.google.gson.annotations.Expose; 11 | import com.google.gson.annotations.SerializedName; 12 | 13 | import java.io.IOException; 14 | import java.util.Map; 15 | import java.util.Objects; 16 | 17 | public class ClusterStatusReportRequest { 18 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() 19 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 20 | .create(); 21 | 22 | @Expose 23 | @SerializedName("cluster_profile_properties") 24 | private ClusterProfileProperties clusterProfile; 25 | 26 | public ClusterStatusReportRequest() { 27 | } 28 | 29 | public ClusterStatusReportRequest(Map clusterProfileConfigurations) { 30 | this.clusterProfile = ClusterProfileProperties.fromConfiguration(clusterProfileConfigurations); 31 | } 32 | 33 | public ClusterProfileProperties getClusterProfile() { 34 | return clusterProfile; 35 | } 36 | 37 | public static ClusterStatusReportRequest fromJSON(String json) { 38 | return GSON.fromJson(json, ClusterStatusReportRequest.class); 39 | } 40 | 41 | public ClusterStatusReportExecutor executor(AgentInstances agentInstances) throws IOException { 42 | return new ClusterStatusReportExecutor(this, agentInstances, ViewBuilder.instance()); 43 | } 44 | 45 | @Override 46 | public boolean equals(Object o) { 47 | if (this == o) return true; 48 | if (o == null || getClass() != o.getClass()) return false; 49 | ClusterStatusReportRequest that = (ClusterStatusReportRequest) o; 50 | return Objects.equals(clusterProfile, that.clusterProfile); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(clusterProfile); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "ClusterStatusReportRequest{" + 61 | "clusterProfile=" + clusterProfile + 62 | '}'; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/resources/cluster-profile.template.html: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 |
23 | 24 | 25 | {{GOINPUTNAME[go_server_url].$error.server}} 26 |
27 | 28 |
29 | 30 | 31 | {{GOINPUTNAME[auto_register_timeout].$error.server}} 32 |
33 | 34 |
35 | 36 | 37 | {{GOINPUTNAME[api_url].$error.server}} 38 |
39 | 40 |
41 | 42 | 43 | {{GOINPUTNAME[api_user].$error.server}} 44 |
45 | 46 |
47 | 48 | 49 | {{GOINPUTNAME[api_key].$error.server}} 50 |
51 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/ClusterProfile.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.google.gson.annotations.Expose; 20 | 21 | import java.util.Objects; 22 | 23 | import static com.example.elasticagent.ExamplePlugin.GSON; 24 | 25 | // TODO: Implement any settings that your plugin needs 26 | public class ClusterProfile { 27 | @Expose 28 | private final String id; 29 | 30 | @Expose 31 | private final String pluginId; 32 | 33 | @Expose 34 | private final ClusterProfileProperties properties; 35 | 36 | public ClusterProfile() { 37 | this("", "", new ClusterProfileProperties()); 38 | } 39 | 40 | public ClusterProfile(String id, String pluginId, ClusterProfileProperties clusterProfileProperties) { 41 | this.id = id; 42 | this.pluginId = pluginId; 43 | this.properties = clusterProfileProperties; 44 | } 45 | 46 | public static ClusterProfile fromJSON(String json) { 47 | return GSON.fromJson(json, ClusterProfile.class); 48 | } 49 | 50 | public String getId() { 51 | return id; 52 | } 53 | 54 | public String getPluginId() { 55 | return pluginId; 56 | } 57 | 58 | public ClusterProfileProperties getProperties() { 59 | return properties; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | 67 | ClusterProfile that = (ClusterProfile) o; 68 | 69 | if (id != null ? !id.equals(that.id) : that.id != null) return false; 70 | if (pluginId != null ? !pluginId.equals(that.pluginId) : that.pluginId != null) return false; 71 | return properties != null ? properties.equals(that.properties) : that.properties == null; 72 | } 73 | 74 | @Override 75 | public int hashCode() { 76 | return Objects.hash(id, pluginId, properties); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/GetElasticAgentProfileMetadataExecutorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.google.gson.Gson; 20 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 21 | import org.junit.jupiter.api.Test; 22 | import org.skyscreamer.jsonassert.JSONAssert; 23 | 24 | import java.util.List; 25 | 26 | import static org.hamcrest.CoreMatchers.is; 27 | import static org.hamcrest.MatcherAssert.assertThat; 28 | import static org.junit.jupiter.api.Assertions.assertEquals; 29 | 30 | public class GetElasticAgentProfileMetadataExecutorTest { 31 | 32 | @Test 33 | public void shouldSerializeAllFields() throws Exception { 34 | GoPluginApiResponse response = new GetElasticAgentProfileMetadataExecutor().execute(); 35 | List list = new Gson().fromJson(response.responseBody(), List.class); 36 | assertEquals(list.size(), GetElasticAgentProfileMetadataExecutor.FIELDS.size()); 37 | } 38 | 39 | @Test 40 | public void assertJsonStructure() throws Exception { 41 | GoPluginApiResponse response = new GetElasticAgentProfileMetadataExecutor().execute(); 42 | 43 | assertThat(response.responseCode(), is(200)); 44 | String expectedJSON = "[\n" + 45 | " {\n" + 46 | " \"key\": \"Image\",\n" + 47 | " \"metadata\": {\n" + 48 | " \"required\": true,\n" + 49 | " \"secure\": false\n" + 50 | " }\n" + 51 | " },\n" + 52 | " {\n" + 53 | " \"key\": \"MaxMemory\",\n" + 54 | " \"metadata\": {\n" + 55 | " \"required\": false,\n" + 56 | " \"secure\": false\n" + 57 | " }\n" + 58 | " }\n" + 59 | "]"; 60 | 61 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), true); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/ValidateElasticAgentProfileRequestExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.RequestExecutor; 20 | import com.example.elasticagent.requests.ValidateElasticAgentProfileRequest; 21 | import com.google.gson.Gson; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | import java.util.*; 26 | 27 | public class ValidateElasticAgentProfileRequestExecutor implements RequestExecutor { 28 | private final ValidateElasticAgentProfileRequest request; 29 | private static final Gson GSON = new Gson(); 30 | 31 | public ValidateElasticAgentProfileRequestExecutor(ValidateElasticAgentProfileRequest request) { 32 | this.request = request; 33 | } 34 | 35 | @Override 36 | public GoPluginApiResponse execute() throws Exception { 37 | ArrayList> result = new ArrayList<>(); 38 | 39 | List knownFields = new ArrayList<>(); 40 | 41 | for (Metadata field : GetElasticAgentProfileMetadataExecutor.FIELDS) { 42 | knownFields.add(field.getKey()); 43 | 44 | Map validationError = field.validate(request.getProperties().get(field.getKey())); 45 | 46 | if (!validationError.isEmpty()) { 47 | result.add(validationError); 48 | } 49 | } 50 | 51 | 52 | Set set = new HashSet<>(request.getProperties().keySet()); 53 | set.removeAll(knownFields); 54 | 55 | if (!set.isEmpty()) { 56 | for (String key : set) { 57 | LinkedHashMap validationError = new LinkedHashMap<>(); 58 | validationError.put("key", key); 59 | validationError.put("message", "Is an unknown property"); 60 | result.add(validationError); 61 | } 62 | } 63 | 64 | return DefaultGoPluginApiResponse.success(GSON.toJson(result)); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/ExampleInstance.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.example.elasticagent.models.JobIdentifier; 20 | import com.example.elasticagent.requests.CreateAgentRequest; 21 | import org.joda.time.DateTime; 22 | 23 | import java.util.Date; 24 | import java.util.Map; 25 | import java.util.UUID; 26 | 27 | public class ExampleInstance { 28 | private final DateTime createdAt; 29 | private final Map properties; 30 | private final String environment; 31 | private final JobIdentifier jobIdentifier; 32 | private String name; 33 | 34 | public ExampleInstance(String name, Date createdAt, Map properties, String environment, JobIdentifier jobIdentifier) { 35 | this.name = name; 36 | this.createdAt = new DateTime(createdAt); 37 | this.properties = properties; 38 | this.environment = environment; 39 | this.jobIdentifier = jobIdentifier; 40 | } 41 | 42 | public static ExampleInstance create(CreateAgentRequest request) { 43 | return new ExampleInstance("agent_" + UUID.randomUUID().toString(), new Date(), request.profileProperties(), request.environment(), request.jobIdentifier()); 44 | } 45 | 46 | public String name() { 47 | return name; 48 | } 49 | 50 | public DateTime createdAt() { 51 | return createdAt; 52 | } 53 | 54 | public String environment() { 55 | return environment; 56 | } 57 | 58 | public Map properties() { 59 | return properties; 60 | } 61 | 62 | public JobIdentifier jobIdentifier() { 63 | return jobIdentifier; 64 | } 65 | 66 | @Override 67 | public boolean equals(Object o) { 68 | if (this == o) return true; 69 | if (o == null || getClass() != o.getClass()) return false; 70 | 71 | ExampleInstance that = (ExampleInstance) o; 72 | 73 | return name != null ? name.equals(that.name) : that.name == null; 74 | } 75 | 76 | @Override 77 | public int hashCode() { 78 | return name != null ? name.hashCode() : 0; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/MigrateConfigRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.ClusterProfile; 4 | import com.example.elasticagent.ClusterProfileProperties; 5 | import com.example.elasticagent.PluginSettings; 6 | import com.example.elasticagent.requests.MigrateConfigPayload; 7 | import com.example.elasticagent.utils.Util; 8 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 9 | import org.junit.jupiter.api.Test; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | import static java.util.Arrays.asList; 17 | import static org.hamcrest.CoreMatchers.hasItem; 18 | import static org.hamcrest.CoreMatchers.is; 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | 21 | class MigrateConfigRequestExecutorTest { 22 | @Test 23 | public void shouldMigratePluginSettingsToClusterProfile() throws Exception { 24 | ClusterProfile expectedDefaultCluster = new ClusterProfile("default", Util.pluginId(), 25 | new ClusterProfileProperties( 26 | "https://localhost:8154/go", 27 | "20", 28 | "test", 29 | "test-api-key", 30 | "https://aws.api.com/api", 31 | null 32 | )); 33 | 34 | PluginSettings pluginSettings = new PluginSettings( 35 | "https://localhost:8154/go", 36 | "20", 37 | "test", 38 | "test-api-key", 39 | "https://aws.api.com/api", 40 | null 41 | ); 42 | 43 | Map elasticProfile = new HashMap<>(); 44 | elasticProfile.put("Image", "alpine:latest"); 45 | 46 | List clusterProfiles = new ArrayList<>(); 47 | clusterProfiles.add(expectedDefaultCluster); 48 | 49 | GoPluginApiResponse goResponse = new MigrateConfigRequestExecutor(new MigrateConfigPayload( 50 | pluginSettings, 51 | asList(elasticProfile), 52 | new ArrayList<>() 53 | )).execute(); 54 | 55 | MigrateConfigPayload response = MigrateConfigPayload.fromJSON(goResponse.responseBody()); 56 | 57 | assertThat(goResponse.responseCode(), is(200)); 58 | assertThat(response.clusterProfiles().size(), is(1)); 59 | assertThat(response.clusterProfiles().get(0), is(expectedDefaultCluster)); 60 | assertThat(response.elasticAgentProfileProperties().size(), is(1)); 61 | assertThat(response.elasticAgentProfileProperties().get(0).keySet(), hasItem("clusterProfileId")); 62 | assertThat(response.elasticAgentProfileProperties().get(0).get("clusterProfileId"), is(expectedDefaultCluster.getId())); 63 | } 64 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/JobCompletionRequest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.executors.JobCompletionRequestExecutor; 21 | import com.example.elasticagent.models.JobIdentifier; 22 | import com.google.gson.FieldNamingPolicy; 23 | import com.google.gson.Gson; 24 | import com.google.gson.GsonBuilder; 25 | import com.google.gson.annotations.Expose; 26 | 27 | import java.util.Map; 28 | 29 | public class JobCompletionRequest { 30 | public static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); 31 | 32 | @Expose 33 | private String elasticAgentId; 34 | 35 | @Expose 36 | private JobIdentifier jobIdentifier; 37 | 38 | @Expose 39 | private Map elasticAgentProfileProperties; 40 | 41 | @Expose 42 | private ClusterProfileProperties clusterProfileProperties; 43 | 44 | public JobCompletionRequest() { 45 | } 46 | 47 | public JobCompletionRequest(String elasticAgentId, JobIdentifier jobIdentifier, Map elasticAgentProfileProperties, ClusterProfileProperties clusterProfileProperties) { 48 | this.elasticAgentId = elasticAgentId; 49 | this.jobIdentifier = jobIdentifier; 50 | this.elasticAgentProfileProperties = elasticAgentProfileProperties; 51 | this.clusterProfileProperties = clusterProfileProperties; 52 | } 53 | 54 | public static JobCompletionRequest fromJSON(String json) { 55 | return GSON.fromJson(json, JobCompletionRequest.class); 56 | } 57 | 58 | public String getElasticAgentId() { 59 | return elasticAgentId; 60 | } 61 | 62 | public JobIdentifier jobIdentifier() { 63 | return jobIdentifier; 64 | } 65 | 66 | public RequestExecutor executor(AgentInstances agentInstances) { 67 | return new JobCompletionRequestExecutor(this, agentInstances); 68 | } 69 | 70 | public Map profileProperties() { 71 | return elasticAgentProfileProperties; 72 | } 73 | 74 | public ClusterProfileProperties clusterProperties() { 75 | return clusterProfileProperties; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/MigrateConfigRequestExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.requests.MigrateConfigPayload; 21 | import com.example.elasticagent.utils.Util; 22 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | 25 | import java.util.Map; 26 | 27 | import static com.example.elasticagent.PluginSettings.GSON; 28 | 29 | public class MigrateConfigRequestExecutor implements RequestExecutor { 30 | private final MigrateConfigPayload payload; 31 | 32 | public MigrateConfigRequestExecutor(MigrateConfigPayload payload) { 33 | this.payload = payload; 34 | } 35 | 36 | @Override 37 | public GoPluginApiResponse execute() throws Exception { 38 | /* 39 | Derive a default cluster profile from plugin settings 40 | */ 41 | 42 | PluginSettings pluginSettings = payload.pluginSettings(); 43 | 44 | if (!payload.clusterProfiles().isEmpty() && "default".equals(payload.clusterProfiles().get(0).getId())) { 45 | return new DefaultGoPluginApiResponse(200, GSON.toJson(payload)); 46 | } 47 | 48 | ClusterProfile clusterProfile = new ClusterProfile("default", Util.pluginId(), 49 | new ClusterProfileProperties( 50 | pluginSettings.getGoServerUrl(), 51 | String.valueOf(pluginSettings.getAutoRegisterPeriod().getMinutes()), 52 | pluginSettings.getApiUser(), 53 | pluginSettings.getApiKey(), 54 | pluginSettings.getApiUrl(), 55 | pluginSettings.getAutoRegisterPeriod() 56 | )); 57 | 58 | payload.clusterProfiles().add(clusterProfile); 59 | 60 | /* 61 | Link each profile with newly created cluster profile 62 | */ 63 | 64 | for (Map profile : payload.elasticAgentProfileProperties()) { 65 | profile.put("clusterProfileId", clusterProfile.getId()); 66 | } 67 | 68 | return new DefaultGoPluginApiResponse(200, GSON.toJson(payload)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/ClusterProfileValidateRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.requests.ClusterProfileValidateRequest; 4 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 5 | import org.junit.jupiter.api.Test; 6 | import org.skyscreamer.jsonassert.JSONAssert; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | import static org.hamcrest.Matchers.is; 13 | 14 | class ClusterProfileValidateRequestExecutorTest { 15 | @Test 16 | public void shouldValidateABadConfiguration() throws Exception { 17 | ClusterProfileValidateRequest request = new ClusterProfileValidateRequest(new HashMap<>()); 18 | GoPluginApiResponse response = new ClusterProfileValidateRequestExecutor(request).execute(); 19 | 20 | assertThat(response.responseCode(), is(200)); 21 | String expectedJSON = "[\n" + 22 | " {\n" + 23 | " \"message\": \"Go Server URL must not be blank\",\n" + 24 | " \"key\": \"go_server_url\"\n" + 25 | " },\n" + 26 | " {\n" + 27 | " \"message\": \"Agent auto-register Timeout (in minutes) must be a positive integer.\",\n" + 28 | " \"key\": \"auto_register_timeout\"\n" + 29 | " },\n" + 30 | " {\n" + 31 | " \"message\": \"API URL must not be blank\",\n" + 32 | " \"key\": \"api_url\"\n" + 33 | " },\n" + 34 | " {\n" + 35 | " \"message\": \"API User must not be blank\",\n" + 36 | " \"key\": \"api_user\"\n" + 37 | " },\n" + 38 | " {\n" + 39 | " \"message\": \"API Key must not be blank\",\n" + 40 | " \"key\": \"api_key\"\n" + 41 | " }\n" + 42 | "]"; 43 | JSONAssert.assertEquals(expectedJSON, response.responseBody(), false); 44 | } 45 | 46 | @Test 47 | public void shouldValidateAGoodConfiguration() throws Exception { 48 | Map profileProperties = new HashMap<>(); 49 | profileProperties.put("api_url", "https://api.example.com"); 50 | profileProperties.put("api_user", "bob"); 51 | profileProperties.put("api_key", "p@ssw0rd"); 52 | profileProperties.put("go_server_url", "https://ci.example.com"); 53 | profileProperties.put("auto_register_timeout", "10"); 54 | 55 | ClusterProfileValidateRequest request = new ClusterProfileValidateRequest(profileProperties); 56 | GoPluginApiResponse response = new ClusterProfileValidateRequestExecutor(request).execute(); 57 | 58 | assertThat(response.responseCode(), is(200)); 59 | JSONAssert.assertEquals("[]", response.responseBody(), true); 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/ShouldAssignWorkRequest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.executors.ShouldAssignWorkRequestExecutor; 21 | import com.example.elasticagent.models.JobIdentifier; 22 | import com.google.gson.FieldNamingPolicy; 23 | import com.google.gson.Gson; 24 | import com.google.gson.GsonBuilder; 25 | 26 | import java.util.Map; 27 | 28 | /** 29 | * Represents the {@link com.example.elasticagent.Request#REQUEST_SHOULD_ASSIGN_WORK} message. 30 | */ 31 | public class ShouldAssignWorkRequest { 32 | public static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); 33 | private Agent agent; 34 | private String environment; 35 | private JobIdentifier jobIdentifier; 36 | private Map elasticAgentProfileProperties; 37 | private ClusterProfileProperties clusterProfileProperties; 38 | 39 | 40 | public ShouldAssignWorkRequest(Agent agent, String environment, JobIdentifier jobIdentifier, Map elasticAgentProfileProperties, ClusterProfileProperties clusterProfileProperties) { 41 | this.agent = agent; 42 | this.environment = environment; 43 | this.jobIdentifier = jobIdentifier; 44 | this.elasticAgentProfileProperties = elasticAgentProfileProperties; 45 | this.clusterProfileProperties = clusterProfileProperties; 46 | } 47 | 48 | public ShouldAssignWorkRequest() { 49 | } 50 | 51 | public Agent agent() { 52 | return agent; 53 | } 54 | 55 | public String environment() { 56 | return environment; 57 | } 58 | 59 | public JobIdentifier jobIdentifier() { 60 | return jobIdentifier; 61 | } 62 | 63 | public Map profileProperties() { 64 | return elasticAgentProfileProperties; 65 | } 66 | 67 | public ClusterProfileProperties clusterProperties() { 68 | return clusterProfileProperties; 69 | } 70 | 71 | public static ShouldAssignWorkRequest fromJSON(String json) { 72 | return GSON.fromJson(json, ShouldAssignWorkRequest.class); 73 | } 74 | 75 | public RequestExecutor executor(AgentInstances agentInstances) { 76 | return new ShouldAssignWorkRequestExecutor(this, agentInstances); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/Metadata.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | import org.apache.commons.lang3.StringUtils; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import static org.apache.commons.lang3.StringUtils.isNotBlank; 27 | 28 | public class Metadata { 29 | protected final String displayName; 30 | 31 | @Expose 32 | @SerializedName("key") 33 | private String key; 34 | 35 | @Expose 36 | @SerializedName("metadata") 37 | private ProfileMetadata metadata; 38 | 39 | public Metadata(String key, String displayName, boolean required, boolean secure) { 40 | this(key, displayName, new ProfileMetadata(required, secure)); 41 | } 42 | 43 | public Metadata(String key) { 44 | this(key, key, new ProfileMetadata(false, false)); 45 | } 46 | 47 | public Metadata(String key, String displayName, ProfileMetadata metadata) { 48 | this.key = key; 49 | this.metadata = metadata; 50 | this.displayName = displayName; 51 | } 52 | 53 | public Map validate(String input) { 54 | HashMap result = new HashMap<>(); 55 | String validationError = doValidate(input); 56 | if (isNotBlank(validationError)) { 57 | result.put("key", key); 58 | result.put("message", validationError); 59 | } 60 | return result; 61 | } 62 | 63 | protected String doValidate(String input) { 64 | if (isRequired()) { 65 | if (StringUtils.isBlank(input)) { 66 | return this.displayName + " must not be blank"; 67 | } 68 | } 69 | return null; 70 | } 71 | 72 | 73 | public String getKey() { 74 | return key; 75 | } 76 | 77 | public boolean isRequired() { 78 | return metadata.required; 79 | } 80 | 81 | public static class ProfileMetadata { 82 | @Expose 83 | @SerializedName("required") 84 | private Boolean required; 85 | 86 | @Expose 87 | @SerializedName("secure") 88 | private Boolean secure; 89 | 90 | public ProfileMetadata(boolean required, boolean secure) { 91 | this.required = required; 92 | this.secure = secure; 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/JobCompletionRequestTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.ClusterProfileProperties; 20 | import org.hamcrest.Matchers; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static java.util.Collections.*; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.hamcrest.Matchers.equalTo; 26 | 27 | public class JobCompletionRequestTest { 28 | 29 | @Test 30 | public void shouldDeserializeFromJSON() { 31 | String json = "{\n" + 32 | " \"elastic_agent_id\": \"ea1\",\n" + 33 | " \"elastic_agent_profile_properties\": {\n" + 34 | " \"Image\": \"alpine:latest\"\n" + 35 | " },\n" + 36 | " \"cluster_profile_properties\": {\n" + 37 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 38 | " \"auto_register_timeout\": \"20m\",\n" + 39 | " \"api_user\": \"test\",\n" + 40 | " \"api_key\": \"test-api-key\",\n" + 41 | " \"api_url\": \"https://aws.api.com/api\"\n" + 42 | " },\n" + 43 | " \"job_identifier\": {\n" + 44 | " \"pipeline_name\": \"up42\",\n" + 45 | " \"pipeline_label\": \"Test\",\n" + 46 | " \"pipeline_counter\": 2,\n" + 47 | " \"stage_name\": \"up42_stage\",\n" + 48 | " \"stage_counter\": \"10\",\n" + 49 | " \"job_name\": \"up42_job\",\n" + 50 | " \"job_id\": -1\n" + 51 | " }\n" + 52 | "}"; 53 | 54 | ClusterProfileProperties expectedClusterProperties = new ClusterProfileProperties( 55 | "https://localhost:8154/go", 56 | "20m", 57 | "test", 58 | "test-api-key", 59 | "https://aws.api.com/api", 60 | null 61 | ); 62 | 63 | JobCompletionRequest request = JobCompletionRequest.fromJSON(json); 64 | assertThat(request.getElasticAgentId(), equalTo("ea1")); 65 | assertThat(request.profileProperties(), Matchers.equalTo(singletonMap("Image", "alpine:latest"))); 66 | assertThat(request.clusterProperties(), Matchers.equalTo(expectedClusterProperties)); 67 | assertThat(request.jobIdentifier().represent(), Matchers.equalTo("up42/2/up42_stage/10/up42_job")); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/Request.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | /** 20 | * Enumerable that represents one of the messages that the server sends to the plugin 21 | */ 22 | public enum Request { 23 | // elastic agent related requests that the server makes to the plugin 24 | REQUEST_CREATE_AGENT(Constants.REQUEST_PREFIX + ".create-agent"), 25 | REQUEST_SERVER_PING(Constants.REQUEST_PREFIX + ".server-ping"), 26 | REQUEST_SHOULD_ASSIGN_WORK(Constants.REQUEST_PREFIX + ".should-assign-work"), 27 | 28 | REQUEST_GET_ELASTIC_AGENT_PROFILE_METADATA(Constants.REQUEST_PREFIX + ".get-elastic-agent-profile-metadata"), 29 | REQUEST_GET_ELASTIC_AGENT_PROFILE_VIEW(Constants.REQUEST_PREFIX + ".get-elastic-agent-profile-view"), 30 | REQUEST_VALIDATE_ELASTIC_AGENT_PROFILE(Constants.REQUEST_PREFIX + ".validate-elastic-agent-profile"), 31 | 32 | REQUEST_GET_CLUSTER_PROFILE_METADATA(Constants.REQUEST_PREFIX + ".get-cluster-profile-metadata"), 33 | REQUEST_GET_CLUSTER_PROFILE_VIEW(Constants.REQUEST_PREFIX + ".get-cluster-profile-view"), 34 | REQUEST_VALIDATE_CLUSTER_PROFILE(Constants.REQUEST_PREFIX + ".validate-cluster-profile"), 35 | 36 | REQUEST_AGENT_STATUS_REPORT(Constants.REQUEST_PREFIX + ".agent-status-report"), 37 | REQUEST_CLUSTER_STATUS_REPORT(Constants.REQUEST_PREFIX + ".cluster-status-report"), 38 | REQUEST_PLUGIN_STATUS_REPORT(Constants.REQUEST_PREFIX + ".plugin-status-report"), 39 | 40 | REQUEST_CAPABILITIES(Constants.REQUEST_PREFIX + ".get-capabilities"), 41 | REQUEST_GET_ICON(Constants.REQUEST_PREFIX + ".get-icon"), 42 | REQUEST_JOB_COMPLETION(Constants.REQUEST_PREFIX + ".job-completion"), 43 | 44 | REQUEST_MIGRATE_CONFIGURATION(Constants.REQUEST_PREFIX + ".migrate-config"), 45 | REQUEST_CLUSTER_PROFILE_CHANGED(Constants.REQUEST_PREFIX + ".cluster-profile-changed"); 46 | 47 | 48 | private final String requestName; 49 | 50 | Request(String requestName) { 51 | this.requestName = requestName; 52 | } 53 | 54 | public static Request fromString(String requestName) { 55 | if (requestName != null) { 56 | for (Request request : Request.values()) { 57 | if (requestName.equalsIgnoreCase(request.requestName)) { 58 | return request; 59 | } 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | 66 | private static class Constants { 67 | static final String REQUEST_PREFIX = "cd.go.elastic-agent"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/ShouldAssignWorkRequestTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.Agent; 20 | import com.example.elasticagent.ClusterProfileProperties; 21 | import org.hamcrest.Matchers; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import java.util.Collections; 25 | import java.util.Map; 26 | 27 | import static org.hamcrest.MatcherAssert.assertThat; 28 | import static org.hamcrest.Matchers.equalTo; 29 | 30 | public class ShouldAssignWorkRequestTest { 31 | 32 | @Test 33 | public void shouldDeserializeFromJSON() throws Exception { 34 | String json = "{\n" + 35 | " \"environment\": \"prod\",\n" + 36 | " \"agent\": {\n" + 37 | " \"agent_id\": \"42\",\n" + 38 | " \"agent_state\": \"Idle\",\n" + 39 | " \"build_state\": \"Idle\",\n" + 40 | " \"config_state\": \"Enabled\"\n" + 41 | " },\n" + 42 | " \"elastic_agent_profile_properties\": {\n" + 43 | " \"property_name1\": \"property_value1\"\n" + 44 | " },\n" + 45 | " \"cluster_profile_properties\": {\n" + 46 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 47 | " \"auto_register_timeout\": \"20m\",\n" + 48 | " \"api_user\": \"test\",\n" + 49 | " \"api_key\": \"test-api-key\",\n" + 50 | " \"api_url\": \"https://aws.api.com/api\"\n" + 51 | " }\n" + 52 | "}"; 53 | 54 | ClusterProfileProperties expectedClusterProperties = new ClusterProfileProperties( 55 | "https://localhost:8154/go", 56 | "20m", 57 | "test", 58 | "test-api-key", 59 | "https://aws.api.com/api", 60 | null 61 | ); 62 | 63 | ShouldAssignWorkRequest request = ShouldAssignWorkRequest.fromJSON(json); 64 | assertThat(request.environment(), equalTo("prod")); 65 | assertThat(request.agent(), equalTo(new Agent("42", Agent.AgentState.Idle, Agent.BuildState.Idle, Agent.ConfigState.Enabled))); 66 | Map expectedProfileProperties = Collections.singletonMap("property_name1", "property_value1"); 67 | 68 | assertThat(request.profileProperties(), Matchers.equalTo(expectedProfileProperties)); 69 | assertThat(request.clusterProperties(), Matchers.equalTo(expectedClusterProperties)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/CreateAgentRequestTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.ClusterProfileProperties; 20 | import org.hamcrest.Matchers; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import static java.util.Collections.*; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.hamcrest.Matchers.equalTo; 26 | 27 | public class CreateAgentRequestTest { 28 | 29 | @Test 30 | public void shouldDeserializeFromJSON() throws Exception { 31 | String json = "{\n" + 32 | " \"auto_register_key\": \"auto-registration-key\",\n" + 33 | " \"elastic_agent_profile_properties\": {\n" + 34 | " \"Image\": \"alpine:latest\"\n" + 35 | " },\n" + 36 | " \"cluster_profile_properties\": {\n" + 37 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 38 | " \"auto_register_timeout\": \"20m\",\n" + 39 | " \"api_user\": \"test\",\n" + 40 | " \"api_key\": \"test-api-key\",\n" + 41 | " \"api_url\": \"https://aws.api.com/api\"\n" + 42 | " },\n" + 43 | " \"environment\": \"test-env\",\n" + 44 | " \"job_identifier\": {\n" + 45 | " \"pipeline_name\": \"up42\",\n" + 46 | " \"pipeline_label\": \"Test\",\n" + 47 | " \"pipeline_counter\": 2,\n" + 48 | " \"stage_name\": \"up42_stage\",\n" + 49 | " \"stage_counter\": \"10\",\n" + 50 | " \"job_name\": \"up42_job\",\n" + 51 | " \"job_id\": -1\n" + 52 | " }\n" + 53 | "}"; 54 | 55 | ClusterProfileProperties expectedClusterProperties = new ClusterProfileProperties( 56 | "https://localhost:8154/go", 57 | "20m", 58 | "test", 59 | "test-api-key", 60 | "https://aws.api.com/api", 61 | null 62 | ); 63 | 64 | CreateAgentRequest request = CreateAgentRequest.fromJSON(json); 65 | assertThat(request.autoRegisterKey(), equalTo("auto-registration-key")); 66 | assertThat(request.environment(), equalTo("test-env")); 67 | 68 | assertThat(request.profileProperties(), Matchers.equalTo(singletonMap("Image", "alpine:latest"))); 69 | assertThat(request.clusterProperties(), Matchers.equalTo(expectedClusterProperties)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | 74 | 75 | @rem Execute Gradle 76 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 77 | 78 | :end 79 | @rem End local scope for the variables with windows NT shell 80 | if %ERRORLEVEL% equ 0 goto mainEnd 81 | 82 | :fail 83 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 84 | rem the _cmd.exe /c_ return code! 85 | set EXIT_CODE=%ERRORLEVEL% 86 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 87 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 88 | exit /b %EXIT_CODE% 89 | 90 | :mainEnd 91 | if "%OS%"=="Windows_NT" endlocal 92 | 93 | :omega 94 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/AgentStatusReportRequest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.requests; 2 | 3 | import com.example.elasticagent.AgentInstances; 4 | import com.example.elasticagent.ClusterProfileProperties; 5 | import com.example.elasticagent.executors.AgentStatusReportExecutor; 6 | import com.example.elasticagent.models.JobIdentifier; 7 | import com.example.elasticagent.views.ViewBuilder; 8 | import com.google.gson.FieldNamingPolicy; 9 | import com.google.gson.Gson; 10 | import com.google.gson.GsonBuilder; 11 | import com.google.gson.annotations.Expose; 12 | 13 | import java.util.Objects; 14 | 15 | public class AgentStatusReportRequest { 16 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() 17 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 18 | .create(); 19 | 20 | @Expose 21 | private String elasticAgentId; 22 | 23 | @Expose 24 | private JobIdentifier jobIdentifier; 25 | 26 | @Expose 27 | private ClusterProfileProperties clusterProfileProperties; 28 | 29 | public AgentStatusReportRequest() { 30 | } 31 | 32 | public AgentStatusReportRequest(String elasticAgentId, JobIdentifier jobIdentifier, ClusterProfileProperties clusterProfileProperties) { 33 | this.elasticAgentId = elasticAgentId; 34 | this.jobIdentifier = jobIdentifier; 35 | this.clusterProfileProperties = clusterProfileProperties; 36 | } 37 | 38 | public static AgentStatusReportRequest fromJSON(String json) { 39 | return GSON.fromJson(json, AgentStatusReportRequest.class); 40 | } 41 | 42 | public String getElasticAgentId() { 43 | return elasticAgentId; 44 | } 45 | 46 | public JobIdentifier getJobIdentifier() { 47 | return jobIdentifier; 48 | } 49 | 50 | public AgentStatusReportExecutor executor(AgentInstances agentInstances, ViewBuilder viewBuilder) { 51 | return new AgentStatusReportExecutor(this, agentInstances, viewBuilder); 52 | } 53 | 54 | public ClusterProfileProperties clusterProperties() { 55 | return clusterProfileProperties; 56 | } 57 | 58 | @Override 59 | public boolean equals(Object o) { 60 | if (this == o) return true; 61 | if (o == null || getClass() != o.getClass()) return false; 62 | 63 | AgentStatusReportRequest that = (AgentStatusReportRequest) o; 64 | 65 | if (elasticAgentId != null ? !elasticAgentId.equals(that.elasticAgentId) : that.elasticAgentId != null) 66 | return false; 67 | if (jobIdentifier != null ? !jobIdentifier.equals(that.jobIdentifier) : that.jobIdentifier != null) 68 | return false; 69 | return clusterProfileProperties != null ? clusterProfileProperties.equals(that.clusterProfileProperties) : that.clusterProfileProperties == null; 70 | } 71 | 72 | @Override 73 | public int hashCode() { 74 | return Objects.hash(elasticAgentId, jobIdentifier, clusterProfileProperties); 75 | } 76 | 77 | 78 | @Override 79 | public String toString() { 80 | return "AgentStatusReportRequest{" + 81 | "elasticAgentId='" + elasticAgentId + '\'' + 82 | ", jobIdentifier=" + jobIdentifier + 83 | ", clusterProfileProperties=" + clusterProfileProperties + 84 | '}'; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/Agents.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.google.common.base.Predicate; 20 | import com.google.common.collect.FluentIterable; 21 | 22 | import java.util.*; 23 | 24 | /** 25 | * Represents a map of {@link Agent#elasticAgentId()} to the {@link Agent} for easy lookups 26 | */ 27 | public class Agents { 28 | 29 | private final Map agents = new HashMap<>(); 30 | 31 | // Filter for agents that can be disabled safely 32 | private static final Predicate AGENT_IDLE_PREDICATE = new Predicate() { 33 | @Override 34 | public boolean apply(Agent metadata) { 35 | Agent.AgentState agentState = metadata.agentState(); 36 | return metadata.configState().equals(Agent.ConfigState.Enabled) && (agentState.equals(Agent.AgentState.Idle) || agentState.equals(Agent.AgentState.Missing) || agentState.equals(Agent.AgentState.LostContact)); 37 | } 38 | }; 39 | 40 | // Filter for agents that can be terminated safely 41 | private static final Predicate AGENT_DISABLED_PREDICATE = new Predicate() { 42 | @Override 43 | public boolean apply(Agent metadata) { 44 | Agent.AgentState agentState = metadata.agentState(); 45 | return metadata.configState().equals(Agent.ConfigState.Disabled) && (agentState.equals(Agent.AgentState.Idle) || agentState.equals(Agent.AgentState.Missing) || agentState.equals(Agent.AgentState.LostContact)); 46 | } 47 | }; 48 | 49 | public Agents() { 50 | } 51 | 52 | public Agents(Collection toCopy) { 53 | addAll(toCopy); 54 | } 55 | 56 | public void addAll(Collection toAdd) { 57 | for (Agent agent : toAdd) { 58 | add(agent); 59 | } 60 | } 61 | 62 | public void addAll(Agents agents) { 63 | addAll(agents.agents()); 64 | } 65 | 66 | public Collection findInstancesToDisable() { 67 | return FluentIterable.from(agents.values()).filter(AGENT_IDLE_PREDICATE).toList(); 68 | } 69 | 70 | public Collection findInstancesToTerminate() { 71 | return FluentIterable.from(agents.values()).filter(AGENT_DISABLED_PREDICATE).toList(); 72 | } 73 | 74 | public Set agentIds() { 75 | return new LinkedHashSet<>(agents.keySet()); 76 | } 77 | 78 | public boolean containsAgentWithId(String agentId) { 79 | return agents.containsKey(agentId); 80 | } 81 | 82 | public Collection agents() { 83 | return new ArrayList<>(agents.values()); 84 | } 85 | 86 | public void add(Agent agent) { 87 | agents.put(agent.elasticAgentId(), agent); 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/utils/SizeUnit.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2013 Coda Hale and Yammer, Inc., 2014-2016 Dropwizard Team 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 com.example.elasticagent.utils; 18 | 19 | public enum SizeUnit { 20 | /** 21 | * Bytes. 22 | */ 23 | BYTES(8), 24 | 25 | /** 26 | * Kilobytes. 27 | */ 28 | KILOBYTES(8L * 1024), 29 | 30 | /** 31 | * Megabytes. 32 | */ 33 | MEGABYTES(8L * 1024 * 1024), 34 | 35 | /** 36 | * Gigabytes. 37 | */ 38 | GIGABYTES(8L * 1024 * 1024 * 1024), 39 | 40 | /** 41 | * Terabytes. 42 | */ 43 | TERABYTES(8L * 1024 * 1024 * 1024 * 1024); 44 | 45 | private final long bits; 46 | 47 | SizeUnit(long bits) { 48 | this.bits = bits; 49 | } 50 | 51 | /** 52 | * Converts a size of the given unit into the current unit. 53 | * 54 | * @param size the magnitude of the size 55 | * @param unit the unit of the size 56 | * @return the given size in the current unit. 57 | */ 58 | public long convert(long size, SizeUnit unit) { 59 | return (size * unit.bits) / bits; 60 | } 61 | 62 | /** 63 | * Converts the given number of the current units into bytes. 64 | * 65 | * @param l the magnitude of the size in the current unit 66 | * @return {@code l} of the current units in bytes 67 | */ 68 | public long toBytes(long l) { 69 | return BYTES.convert(l, this); 70 | } 71 | 72 | /** 73 | * Converts the given number of the current units into kilobytes. 74 | * 75 | * @param l the magnitude of the size in the current unit 76 | * @return {@code l} of the current units in kilobytes 77 | */ 78 | public long toKilobytes(long l) { 79 | return KILOBYTES.convert(l, this); 80 | } 81 | 82 | /** 83 | * Converts the given number of the current units into megabytes. 84 | * 85 | * @param l the magnitude of the size in the current unit 86 | * @return {@code l} of the current units in megabytes 87 | */ 88 | public long toMegabytes(long l) { 89 | return MEGABYTES.convert(l, this); 90 | } 91 | 92 | /** 93 | * Converts the given number of the current units into gigabytes. 94 | * 95 | * @param l the magnitude of the size in the current unit 96 | * @return {@code l} of the current units in bytes 97 | */ 98 | public long toGigabytes(long l) { 99 | return GIGABYTES.convert(l, this); 100 | } 101 | 102 | /** 103 | * Converts the given number of the current units into terabytes. 104 | * 105 | * @param l the magnitude of the size in the current unit 106 | * @return {@code l} of the current units in terabytes 107 | */ 108 | public long toTerabytes(long l) { 109 | return TERABYTES.convert(l, this); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/MigrateConfigPayloadTest.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.requests; 2 | 3 | import com.example.elasticagent.ClusterProfile; 4 | import com.example.elasticagent.ClusterProfileProperties; 5 | import com.example.elasticagent.PluginSettings; 6 | import org.junit.jupiter.api.Test; 7 | 8 | import static java.util.Collections.singletonMap; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | import static org.hamcrest.Matchers.is; 11 | 12 | class MigrateConfigPayloadTest { 13 | 14 | @Test 15 | public void shouldDeserializeRequest() { 16 | String json = "{" + 17 | " \"plugin_settings\":{}," + 18 | " \"cluster_profiles\":[]," + 19 | " \"elastic_agent_profiles\":[]" + 20 | "}\n"; 21 | 22 | MigrateConfigPayload request = MigrateConfigPayload.fromJSON(json); 23 | 24 | assertThat(request.clusterProfiles().size(), is(0)); 25 | assertThat(request.elasticAgentProfileProperties().size(), is(0)); 26 | assertThat(request.pluginSettings(), is(new PluginSettings())); 27 | } 28 | 29 | @Test 30 | public void shouldDesesrializeRequest() { 31 | String json = "{" + 32 | " \"plugin_settings\":{" + 33 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 34 | " \"auto_register_timeout\": \"20\",\n" + 35 | " \"api_user\": \"test\",\n" + 36 | " \"api_key\": \"test-api-key\",\n" + 37 | " \"api_url\": \"https://aws.api.com/api\"\n" + 38 | " }," + 39 | " \"cluster_profiles\": [{\n" + 40 | " \"id\": \"forTest\",\n" + 41 | " \"plugin_id\": \"go.elastic.plugin1\",\n" + 42 | " \"properties\": {\n" + 43 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 44 | " \"auto_register_timeout\": \"20\",\n" + 45 | " \"api_user\": \"test\",\n" + 46 | " \"api_key\": \"test-api-key\",\n" + 47 | " \"api_url\": \"https://aws.api.com/api\"\n" + 48 | " }\n" + 49 | " }],\n" + 50 | " \"elastic_agent_profiles\":[{" + 51 | " \"Image\": \"alpine:latest\"\n" + 52 | " }]\n" + 53 | "}\n"; 54 | 55 | ClusterProfile expectedClusterProfile = new ClusterProfile("forTest", "go.elastic.plugin1", 56 | new ClusterProfileProperties( 57 | "https://localhost:8154/go", 58 | "20", 59 | "test", 60 | "test-api-key", 61 | "https://aws.api.com/api", 62 | null 63 | )); 64 | 65 | PluginSettings expectedSettings = new PluginSettings( 66 | "https://localhost:8154/go", 67 | "20", 68 | "test", 69 | "test-api-key", 70 | "https://aws.api.com/api", 71 | null 72 | ); 73 | 74 | MigrateConfigPayload request = MigrateConfigPayload.fromJSON(json); 75 | 76 | assertThat(request.clusterProfiles().size(), is(1)); 77 | assertThat(request.clusterProfiles().get(0), is(expectedClusterProfile)); 78 | assertThat(request.pluginSettings(), is(expectedSettings)); 79 | assertThat(request.elasticAgentProfileProperties().size(), is(1)); 80 | assertThat(request.elasticAgentProfileProperties().get(0), is(singletonMap("Image", "alpine:latest"))); 81 | } 82 | } -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/ClusterProfileChangedRequestExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.AgentInstances; 20 | import com.example.elasticagent.ClusterProfileProperties; 21 | import com.example.elasticagent.ExampleAgentInstances; 22 | import com.example.elasticagent.RequestExecutor; 23 | import com.example.elasticagent.requests.ClusterProfileChangedRequest; 24 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 25 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 26 | 27 | import java.util.Map; 28 | 29 | public class ClusterProfileChangedRequestExecutor implements RequestExecutor { 30 | private final Map allClusterInstances; 31 | private final ClusterProfileChangedRequest request; 32 | 33 | public ClusterProfileChangedRequestExecutor(ClusterProfileChangedRequest request, Map allClusterInstance) { 34 | this.request = request; 35 | this.allClusterInstances = allClusterInstance; 36 | } 37 | 38 | @Override 39 | public GoPluginApiResponse execute() throws Exception { 40 | switch(request.changeStatus()) { 41 | case CREATED: 42 | handleCreate(request, allClusterInstances); 43 | break; 44 | case UPDATED: 45 | handleUpdate(request, allClusterInstances); 46 | break; 47 | case DELETED: 48 | handleDelete(request, allClusterInstances); 49 | break; 50 | } 51 | return new DefaultGoPluginApiResponse(200); 52 | } 53 | 54 | private void handleCreate(ClusterProfileChangedRequest request, Map allClusterInstances) { 55 | ClusterProfileProperties newlyCreatedCluster = request.clusterProperties(); 56 | // Create new cluster on cloud 57 | allClusterInstances.put(newlyCreatedCluster.uuid(), new ExampleAgentInstances()); 58 | } 59 | 60 | private void handleDelete(ClusterProfileChangedRequest request, Map allClusterInstances) { 61 | String clusterToDelete = request.clusterProperties().uuid(); 62 | AgentInstances clusterInstancesToDelete = allClusterInstances.get(clusterToDelete); 63 | 64 | // terminate all agent instances from a cluster 65 | 66 | allClusterInstances.remove(clusterToDelete); 67 | } 68 | 69 | private void handleUpdate(ClusterProfileChangedRequest request, Map allClusterInstances) { 70 | ClusterProfileProperties newCluster = request.clusterProperties(); 71 | ClusterProfileProperties oldCluster = request.oldClusterProperties(); 72 | 73 | AgentInstances oldClusterInstances = allClusterInstances.get(oldCluster.uuid()); 74 | 75 | // terminate from old cluster and create instances on new cluster 76 | 77 | allClusterInstances.put(newCluster.uuid(), new ExampleAgentInstances()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/PluginRequest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.example.elasticagent.models.ServerInfo; 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.GoApiResponse; 23 | 24 | import java.util.Collection; 25 | 26 | import static com.example.elasticagent.Constants.*; 27 | 28 | 29 | /** 30 | * Instances of this class know how to send messages to the GoCD Server. 31 | */ 32 | public class PluginRequest { 33 | private final GoApplicationAccessor accessor; 34 | 35 | public PluginRequest(GoApplicationAccessor accessor) { 36 | this.accessor = accessor; 37 | } 38 | 39 | public ServerInfo getServerInfo() throws ServerRequestFailedException { 40 | DefaultGoApiRequest request = new DefaultGoApiRequest(Constants.REQUEST_SERVER_INFO, SERVER_INFO_PROCESSOR_API_VERSION, PLUGIN_IDENTIFIER); 41 | GoApiResponse response = accessor.submit(request); 42 | 43 | if (response.responseCode() != 200) { 44 | throw ServerRequestFailedException.serverInfo(response); 45 | } 46 | 47 | return ServerInfo.fromJSON(response.responseBody()); 48 | } 49 | 50 | public Agents listAgents() throws ServerRequestFailedException { 51 | DefaultGoApiRequest request = new DefaultGoApiRequest(Constants.REQUEST_SERVER_LIST_AGENTS, ELASTIC_PROCESSOR_API_VERSION, PLUGIN_IDENTIFIER); 52 | GoApiResponse response = accessor.submit(request); 53 | 54 | if (response.responseCode() != 200) { 55 | throw ServerRequestFailedException.listAgents(response); 56 | } 57 | 58 | return new Agents(Agent.fromJSONArray(response.responseBody())); 59 | } 60 | 61 | public void disableAgents(Collection toBeDisabled) throws ServerRequestFailedException { 62 | if (toBeDisabled.isEmpty()) { 63 | return; 64 | } 65 | 66 | DefaultGoApiRequest request = new DefaultGoApiRequest(Constants.REQUEST_SERVER_DISABLE_AGENT, ELASTIC_PROCESSOR_API_VERSION, PLUGIN_IDENTIFIER); 67 | request.setRequestBody(Agent.toJSONArray(toBeDisabled)); 68 | 69 | GoApiResponse response = accessor.submit(request); 70 | 71 | if (response.responseCode() != 200) { 72 | throw ServerRequestFailedException.disableAgents(response); 73 | } 74 | } 75 | 76 | public void deleteAgents(Collection toBeDeleted) throws ServerRequestFailedException { 77 | if (toBeDeleted.isEmpty()) { 78 | return; 79 | } 80 | 81 | DefaultGoApiRequest request = new DefaultGoApiRequest(Constants.REQUEST_SERVER_DELETE_AGENT, ELASTIC_PROCESSOR_API_VERSION, PLUGIN_IDENTIFIER); 82 | request.setRequestBody(Agent.toJSONArray(toBeDeleted)); 83 | GoApiResponse response = accessor.submit(request); 84 | 85 | if (response.responseCode() != 200) { 86 | throw ServerRequestFailedException.deleteAgents(response); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/ServerPingRequestExecutor.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.requests.ServerPingRequest; 21 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | 24 | import java.util.*; 25 | 26 | public class ServerPingRequestExecutor implements RequestExecutor { 27 | private final PluginRequest pluginRequest; 28 | private final Map clusterSpecificAgentInstances; 29 | private final ServerPingRequest serverPingRequest; 30 | 31 | public ServerPingRequestExecutor(Map clusterSpecificAgentInstances, ServerPingRequest serverPingRequest, PluginRequest pluginRequest) { 32 | 33 | this.clusterSpecificAgentInstances = clusterSpecificAgentInstances; 34 | this.serverPingRequest = serverPingRequest; 35 | this.pluginRequest = pluginRequest; 36 | } 37 | 38 | @Override 39 | public GoPluginApiResponse execute() throws Exception { 40 | List allClusterProfileProperties = serverPingRequest.allClusterProfileProperties(); 41 | 42 | for (ClusterProfileProperties clusterProfileProperties : allClusterProfileProperties) { 43 | performCleanupForCluster(clusterProfileProperties, clusterSpecificAgentInstances.get(clusterProfileProperties.uuid())); 44 | } 45 | 46 | return DefaultGoPluginApiResponse.success(""); 47 | } 48 | 49 | private void performCleanupForCluster(ClusterProfileProperties clusterProfileProperties, AgentInstances agentInstances) throws Exception { 50 | Agents allAgents = pluginRequest.listAgents(); 51 | Set missingAgents = new HashSet<>(); 52 | 53 | for (Agent agent : allAgents.agents()) { 54 | if (agentInstances.find(agent.elasticAgentId()) == null) { 55 | missingAgents.add(agent); 56 | } else { 57 | missingAgents.remove(agent); 58 | } 59 | } 60 | 61 | Agents agentsToDisable = agentInstances.instancesCreatedAfterTimeout(clusterProfileProperties, allAgents); 62 | 63 | agentsToDisable.addAll(missingAgents); 64 | 65 | disableIdleAgents(agentsToDisable); 66 | 67 | allAgents = pluginRequest.listAgents(); 68 | terminateDisabledAgents(allAgents, clusterProfileProperties, agentInstances); 69 | 70 | agentInstances.terminateUnregisteredInstances(clusterProfileProperties, allAgents); 71 | } 72 | 73 | private void disableIdleAgents(Agents agents) throws ServerRequestFailedException { 74 | pluginRequest.disableAgents(agents.findInstancesToDisable()); 75 | } 76 | 77 | private void terminateDisabledAgents(Agents agents, ClusterProfileProperties pluginSettings, AgentInstances agentInstances) throws Exception { 78 | Collection toBeDeleted = agents.findInstancesToTerminate(); 79 | 80 | for (Agent agent : toBeDeleted) { 81 | agentInstances.terminate(agent.elasticAgentId(), pluginSettings); 82 | } 83 | 84 | pluginRequest.deleteAgents(toBeDeleted); 85 | } 86 | 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/executors/ShouldAssignWorkRequestExecutorTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.executors; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.models.JobIdentifier; 21 | import com.example.elasticagent.models.JobIdentifierMother; 22 | import com.example.elasticagent.requests.CreateAgentRequest; 23 | import com.example.elasticagent.requests.ShouldAssignWorkRequest; 24 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 25 | import org.junit.jupiter.api.BeforeEach; 26 | import org.junit.jupiter.api.Test; 27 | 28 | import java.util.HashMap; 29 | import java.util.Map; 30 | import java.util.UUID; 31 | 32 | import static org.hamcrest.MatcherAssert.assertThat; 33 | import static org.hamcrest.Matchers.is; 34 | 35 | public class ShouldAssignWorkRequestExecutorTest { 36 | 37 | private AgentInstances agentInstances; 38 | private ExampleInstance instance; 39 | private final String environment = "production"; 40 | private Map profileProperties = new HashMap<>(); 41 | private ClusterProfileProperties clusterProperties = new ClusterProfileProperties(); 42 | private final JobIdentifier jobIdentifier = JobIdentifierMother.get(); 43 | 44 | @BeforeEach 45 | public void setUp() throws Exception { 46 | agentInstances = new ExampleAgentInstances(); 47 | profileProperties.put("foo", "bar"); 48 | profileProperties.put("Image", "gocdcontrib/ubuntu-docker-elastic-agent"); 49 | instance = agentInstances.create(new CreateAgentRequest(UUID.randomUUID().toString(), profileProperties, clusterProperties, environment, jobIdentifier)); 50 | } 51 | 52 | @Test 53 | public void shouldAssignWorkToContainerWithSameJobIdentifier() { 54 | ShouldAssignWorkRequest request = new ShouldAssignWorkRequest(new Agent(instance.name(), null, null, null), environment, jobIdentifier, null, null); 55 | GoPluginApiResponse response = new ShouldAssignWorkRequestExecutor(request, agentInstances).execute(); 56 | assertThat(response.responseCode(), is(200)); 57 | assertThat(response.responseBody(), is("true")); 58 | } 59 | 60 | @Test 61 | public void shouldNotAssignWorkToContainerWithDifferentJobIdentifier() { 62 | JobIdentifier otherJobId = new JobIdentifier("up42", 2L, "foo", "stage", "1", "job", 2L); 63 | ShouldAssignWorkRequest request = new ShouldAssignWorkRequest(new Agent(instance.name(), null, null, null), environment, otherJobId, null, null); 64 | GoPluginApiResponse response = new ShouldAssignWorkRequestExecutor(request, agentInstances).execute(); 65 | assertThat(response.responseCode(), is(200)); 66 | assertThat(response.responseBody(), is("false")); 67 | } 68 | 69 | @Test 70 | public void shouldNotAssignWorkIfInstanceIsNotFound() { 71 | ShouldAssignWorkRequest request = new ShouldAssignWorkRequest(new Agent("unknown-name", null, null, null), environment, jobIdentifier, null, null); 72 | GoPluginApiResponse response = new ShouldAssignWorkRequestExecutor(request, agentInstances).execute(); 73 | assertThat(response.responseCode(), is(200)); 74 | assertThat(response.responseBody(), is("false")); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/ClusterProfileChangedRequest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.AgentInstances; 20 | import com.example.elasticagent.ClusterProfileProperties; 21 | import com.example.elasticagent.RequestExecutor; 22 | import com.example.elasticagent.executors.ClusterProfileChangedRequestExecutor; 23 | import com.google.common.base.Strings; 24 | 25 | import java.util.Map; 26 | import java.util.Optional; 27 | 28 | import static com.example.elasticagent.ExamplePlugin.GSON; 29 | 30 | public class ClusterProfileChangedRequest { 31 | public enum ChangeStatus { 32 | CREATED("created"), 33 | UPDATED("updated"), 34 | DELETED("deleted"); 35 | 36 | private final String status; 37 | 38 | ChangeStatus(String status) { 39 | this.status = status; 40 | } 41 | 42 | public static Optional fromString(String status) { 43 | if (Strings.isNullOrEmpty(status)) { 44 | return Optional.empty(); 45 | } 46 | 47 | switch (status) { 48 | case "created": 49 | return Optional.of(CREATED); 50 | case "updated": 51 | return Optional.of(UPDATED); 52 | case "deleted": 53 | return Optional.of(DELETED); 54 | } 55 | 56 | return Optional.empty(); 57 | } 58 | } 59 | 60 | 61 | private String status; 62 | private ChangeStatus changeStatus; 63 | private ClusterProfileProperties clusterProfilesProperties; 64 | private ClusterProfileProperties oldClusterProfilesProperties; 65 | 66 | public ClusterProfileChangedRequest() { 67 | } 68 | 69 | public ClusterProfileChangedRequest(ChangeStatus status, ClusterProfileProperties clusterProfilesProperties, ClusterProfileProperties oldClusterProfilesProperties) { 70 | this.changeStatus = status; 71 | this.clusterProfilesProperties = clusterProfilesProperties; 72 | this.oldClusterProfilesProperties = oldClusterProfilesProperties; 73 | } 74 | 75 | public static ClusterProfileChangedRequest fromJSON(String json) { 76 | 77 | ClusterProfileChangedRequest request = GSON.fromJson(json, ClusterProfileChangedRequest.class); 78 | Optional changeStatus = ChangeStatus.fromString(request.status); 79 | 80 | if (changeStatus.isPresent()) { 81 | request.changeStatus = changeStatus.get(); 82 | return request; 83 | } 84 | 85 | throw new RuntimeException("Invalid ChangeStatus specified '%s', valid values are [created, updated, deleted]"); 86 | } 87 | 88 | public RequestExecutor executor(Map allClusterInstances) { 89 | return new ClusterProfileChangedRequestExecutor(this, allClusterInstances); 90 | } 91 | 92 | public ChangeStatus changeStatus() { 93 | return changeStatus; 94 | } 95 | 96 | public ClusterProfileProperties clusterProperties() { 97 | return clusterProfilesProperties; 98 | } 99 | 100 | public ClusterProfileProperties oldClusterProperties() { 101 | return oldClusterProfilesProperties; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/requests/CreateAgentRequest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.*; 20 | import com.example.elasticagent.executors.CreateAgentRequestExecutor; 21 | import com.example.elasticagent.models.JobIdentifier; 22 | import org.apache.commons.lang3.StringUtils; 23 | 24 | import java.io.IOException; 25 | import java.io.StringWriter; 26 | import java.util.Map; 27 | import java.util.Properties; 28 | 29 | import static com.example.elasticagent.ExamplePlugin.GSON; 30 | 31 | public class CreateAgentRequest { 32 | 33 | private String autoRegisterKey; 34 | private String environment; 35 | private JobIdentifier jobIdentifier; 36 | private Map elasticAgentProfileProperties; 37 | private ClusterProfileProperties clusterProfileProperties; 38 | 39 | public CreateAgentRequest() { 40 | } 41 | 42 | public CreateAgentRequest(String autoRegisterKey, 43 | Map elasticAgentProfileProperties, 44 | ClusterProfileProperties clusterProfileProperties, 45 | String environment, 46 | JobIdentifier jobIdentifier) { 47 | this.autoRegisterKey = autoRegisterKey; 48 | this.environment = environment; 49 | this.jobIdentifier = jobIdentifier; 50 | this.elasticAgentProfileProperties = elasticAgentProfileProperties; 51 | this.clusterProfileProperties = clusterProfileProperties; 52 | } 53 | 54 | public String autoRegisterKey() { 55 | return autoRegisterKey; 56 | } 57 | 58 | public String environment() { 59 | return environment; 60 | } 61 | 62 | public JobIdentifier jobIdentifier() { 63 | return jobIdentifier; 64 | } 65 | 66 | public static CreateAgentRequest fromJSON(String json) { 67 | return GSON.fromJson(json, CreateAgentRequest.class); 68 | } 69 | 70 | public RequestExecutor executor(AgentInstances agentInstances) { 71 | return new CreateAgentRequestExecutor(this, agentInstances); 72 | } 73 | 74 | public Properties autoregisterProperties(String elasticAgentId) { 75 | Properties properties = new Properties(); 76 | 77 | if (StringUtils.isNotBlank(autoRegisterKey)) { 78 | properties.put("agent.auto.register.key", autoRegisterKey); 79 | } 80 | 81 | if (StringUtils.isNotBlank(environment)) { 82 | properties.put("agent.auto.register.environments", environment); 83 | } 84 | 85 | properties.put("agent.auto.register.elasticAgent.agentId", elasticAgentId); 86 | properties.put("agent.auto.register.elasticAgent.pluginId", Constants.PLUGIN_ID); 87 | 88 | return properties; 89 | } 90 | 91 | public String autoregisterPropertiesAsString(String elasticAgentId) { 92 | Properties properties = autoregisterProperties(elasticAgentId); 93 | 94 | StringWriter writer = new StringWriter(); 95 | 96 | try { 97 | properties.store(writer, ""); 98 | } catch (IOException e) { 99 | throw new RuntimeException(e); 100 | } 101 | 102 | return writer.toString(); 103 | } 104 | 105 | public Map profileProperties() { 106 | return elasticAgentProfileProperties; 107 | } 108 | 109 | public ClusterProfileProperties clusterProperties() { 110 | return clusterProfileProperties; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/executors/AgentStatusReportExecutor.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.executors; 2 | 3 | import com.example.elasticagent.AgentInstances; 4 | import com.example.elasticagent.ExampleInstance; 5 | import com.example.elasticagent.models.AgentStatusReport; 6 | import com.example.elasticagent.models.JobIdentifier; 7 | import com.example.elasticagent.requests.AgentStatusReportRequest; 8 | import com.example.elasticagent.views.ViewBuilder; 9 | import com.google.gson.JsonObject; 10 | import com.thoughtworks.go.plugin.api.logging.Logger; 11 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 12 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 13 | import org.apache.commons.lang3.StringUtils; 14 | 15 | public class AgentStatusReportExecutor { 16 | private static final Logger LOG = Logger.getLoggerFor(AgentStatusReportExecutor.class); 17 | private final AgentStatusReportRequest request; 18 | private final AgentInstances agentInstances; 19 | private final ViewBuilder viewBuilder; 20 | 21 | public AgentStatusReportExecutor(AgentStatusReportRequest request, 22 | AgentInstances agentInstances, ViewBuilder viewBuilder) { 23 | this.request = request; 24 | this.agentInstances = agentInstances; 25 | this.viewBuilder = viewBuilder; 26 | } 27 | 28 | public GoPluginApiResponse execute() throws Exception { 29 | String elasticAgentId = request.getElasticAgentId(); 30 | JobIdentifier jobIdentifier = request.getJobIdentifier(); 31 | LOG.info(String.format("[status-report] Generating status report for agent: %s with job: %s", elasticAgentId, jobIdentifier)); 32 | 33 | try { 34 | if (StringUtils.isNotBlank(elasticAgentId)) { 35 | return getStatusReportUsingElasticAgentId(elasticAgentId); 36 | } 37 | return getStatusReportUsingJobIdentifier(jobIdentifier); 38 | } catch (Exception e) { 39 | LOG.debug("Exception while generating agent status report", e); 40 | final String statusReportView = viewBuilder.build("error-template"); 41 | return constructResponseForReport(statusReportView); 42 | } 43 | } 44 | 45 | private GoPluginApiResponse getStatusReportUsingJobIdentifier(JobIdentifier jobIdentifier) throws Exception { 46 | ExampleInstance agentInstance = agentInstances.find(jobIdentifier); 47 | if (agentInstance != null) { 48 | AgentStatusReport agentStatusReport = agentInstances.getAgentStatusReport(request.clusterProperties(), agentInstance); 49 | final String statusReportView = viewBuilder.build("status-report-template", agentStatusReport); 50 | return constructResponseForReport(statusReportView); 51 | } 52 | return containerNotFoundApiResponse(jobIdentifier); 53 | } 54 | 55 | private GoPluginApiResponse getStatusReportUsingElasticAgentId(String elasticAgentId) throws Exception { 56 | ExampleInstance agentInstance = agentInstances.find(elasticAgentId); 57 | if (agentInstance != null) { 58 | AgentStatusReport agentStatusReport = agentInstances.getAgentStatusReport(request.clusterProperties(), agentInstance); 59 | final String statusReportView = viewBuilder.build("status-report-template", agentStatusReport); 60 | return constructResponseForReport(statusReportView); 61 | } 62 | return containerNotFoundApiResponse(elasticAgentId); 63 | } 64 | 65 | private GoPluginApiResponse constructResponseForReport(String statusReportView) { 66 | JsonObject responseJSON = new JsonObject(); 67 | responseJSON.addProperty("view", statusReportView); 68 | return DefaultGoPluginApiResponse.success(responseJSON.toString()); 69 | } 70 | 71 | private GoPluginApiResponse containerNotFoundApiResponse(JobIdentifier jobIdentifier) { 72 | final String statusReportView = viewBuilder.build("not-running-template"); 73 | return constructResponseForReport(statusReportView); 74 | } 75 | 76 | private GoPluginApiResponse containerNotFoundApiResponse(String elasticAgentId) { 77 | final String statusReportView = viewBuilder.build("not-running-template"); 78 | return constructResponseForReport(statusReportView); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/AgentTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import org.junit.jupiter.api.Test; 20 | import org.skyscreamer.jsonassert.JSONAssert; 21 | 22 | import java.util.Arrays; 23 | import java.util.List; 24 | 25 | import static org.hamcrest.MatcherAssert.assertThat; 26 | import static org.hamcrest.Matchers.*; 27 | import static org.junit.jupiter.api.Assertions.assertFalse; 28 | import static org.junit.jupiter.api.Assertions.assertTrue; 29 | 30 | 31 | public class AgentTest { 32 | 33 | @Test 34 | public void shouldSerializeToJSON() throws Exception { 35 | Agent agent = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 36 | String agentsJSON = Agent.toJSONArray(Arrays.asList(agent)); 37 | 38 | JSONAssert.assertEquals("[{\"agent_id\":\"eeb9e0eb-1f12-4366-a5a5-59011810273b\",\"agent_state\":\"Building\",\"build_state\":\"Cancelled\",\"config_state\":\"Disabled\"}]", agentsJSON, true); 39 | } 40 | 41 | @Test 42 | public void shouldDeserializeFromJSON() throws Exception { 43 | List agents = Agent.fromJSONArray("[{\"agent_id\":\"eeb9e0eb-1f12-4366-a5a5-59011810273b\",\"agent_state\":\"Building\",\"build_state\":\"Cancelled\",\"config_state\":\"Disabled\"}]"); 44 | assertThat(agents, hasSize(1)); 45 | 46 | Agent agent = agents.get(0); 47 | 48 | assertThat(agent.elasticAgentId(), is("eeb9e0eb-1f12-4366-a5a5-59011810273b")); 49 | assertThat(agent.agentState(), is(Agent.AgentState.Building)); 50 | assertThat(agent.buildState(), is(Agent.BuildState.Cancelled)); 51 | assertThat(agent.configState(), is(Agent.ConfigState.Disabled)); 52 | } 53 | 54 | @Test 55 | public void agentsWithSameAttributesShouldBeEqual() throws Exception { 56 | Agent agent1 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 57 | Agent agent2 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 58 | 59 | assertTrue(agent1.equals(agent2)); 60 | } 61 | 62 | @Test 63 | public void agentShouldEqualItself() throws Exception { 64 | Agent agent = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 65 | 66 | assertTrue(agent.equals(agent)); 67 | } 68 | 69 | @Test 70 | public void agentShouldNotEqualAnotherAgentWithDifferentAttributes() throws Exception { 71 | Agent agent = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 72 | 73 | assertFalse(agent.equals(new Agent())); 74 | } 75 | 76 | @Test 77 | public void agentsWithSameAttributesShareSameHashCode() throws Exception { 78 | Agent agent1 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 79 | Agent agent2 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 80 | 81 | assertThat(agent1.hashCode(), equalTo(agent2.hashCode())); 82 | } 83 | 84 | @Test 85 | public void agentsWithDifferentAttributesDoNotShareSameHashCode() throws Exception { 86 | Agent agent1 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 87 | Agent agent2 = new Agent(); 88 | 89 | assertThat(agent1.hashCode(), not(equalTo(agent2.hashCode()))); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/models/JobIdentifier.java: -------------------------------------------------------------------------------- 1 | package com.example.elasticagent.models; 2 | 3 | import com.google.gson.FieldNamingPolicy; 4 | import com.google.gson.Gson; 5 | import com.google.gson.GsonBuilder; 6 | import com.google.gson.annotations.Expose; 7 | import org.apache.commons.lang3.StringUtils; 8 | 9 | import java.util.Objects; 10 | 11 | import static java.text.MessageFormat.format; 12 | 13 | public class JobIdentifier { 14 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() 15 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 16 | .create(); 17 | 18 | @Expose 19 | private String pipelineName; 20 | 21 | @Expose 22 | private Long pipelineCounter; 23 | 24 | @Expose 25 | private String pipelineLabel; 26 | 27 | @Expose 28 | private String stageName; 29 | 30 | @Expose 31 | private String stageCounter; 32 | 33 | @Expose 34 | private String jobName; 35 | 36 | @Expose 37 | private Long jobId; 38 | 39 | private String representation; 40 | 41 | public JobIdentifier(Long jobId) { 42 | this.jobId = jobId; 43 | } 44 | 45 | public JobIdentifier() { 46 | } 47 | 48 | public JobIdentifier(String pipelineName, Long pipelineCounter, String pipelineLabel, String stageName, String stageCounter, String jobName, Long jobId) { 49 | this.pipelineName = pipelineName; 50 | this.pipelineCounter = pipelineCounter; 51 | this.pipelineLabel = pipelineLabel; 52 | this.stageName = stageName; 53 | this.stageCounter = stageCounter; 54 | this.jobName = jobName; 55 | this.jobId = jobId; 56 | } 57 | 58 | public Long getJobId() { 59 | return jobId; 60 | } 61 | 62 | public String getJobName() { 63 | return jobName; 64 | } 65 | 66 | public String getPipelineName() { 67 | return pipelineName; 68 | } 69 | 70 | public Long getPipelineCounter() { 71 | return pipelineCounter; 72 | } 73 | 74 | public String getPipelineLabel() { 75 | return pipelineLabel; 76 | } 77 | 78 | public String getStageName() { 79 | return stageName; 80 | } 81 | 82 | public String getStageCounter() { 83 | return stageCounter; 84 | } 85 | 86 | public String getRepresentation() { 87 | if (StringUtils.isBlank(representation)) { 88 | this.representation = format("{0}/{1}/{2}/{3}/{4}", pipelineName, pipelineCounter, stageName, stageCounter, jobName); 89 | } 90 | return representation; 91 | } 92 | 93 | @Override 94 | public boolean equals(Object o) { 95 | if (this == o) return true; 96 | if (o == null || getClass() != o.getClass()) return false; 97 | JobIdentifier that = (JobIdentifier) o; 98 | return Objects.equals(pipelineName, that.pipelineName) && 99 | Objects.equals(pipelineCounter, that.pipelineCounter) && 100 | Objects.equals(pipelineLabel, that.pipelineLabel) && 101 | Objects.equals(stageName, that.stageName) && 102 | Objects.equals(stageCounter, that.stageCounter) && 103 | Objects.equals(jobName, that.jobName) && 104 | Objects.equals(jobId, that.jobId); 105 | } 106 | 107 | @Override 108 | public int hashCode() { 109 | return Objects.hash(pipelineName, pipelineCounter, pipelineLabel, stageName, stageCounter, jobName, jobId); 110 | } 111 | 112 | @Override 113 | public String toString() { 114 | return "JobIdentifier{" + 115 | "pipelineName='" + pipelineName + '\'' + 116 | ", pipelineCounter=" + pipelineCounter + 117 | ", pipelineLabel='" + pipelineLabel + '\'' + 118 | ", stageName='" + stageName + '\'' + 119 | ", stageCounter='" + stageCounter + '\'' + 120 | ", jobName='" + jobName + '\'' + 121 | ", jobId=" + jobId + 122 | '}'; 123 | } 124 | 125 | public String represent() { 126 | return String.format("%s/%d/%s/%s/%s", pipelineName, pipelineCounter, stageName, stageCounter, jobName); 127 | } 128 | 129 | public String toJson() { 130 | return GSON.toJson(this); 131 | } 132 | 133 | public static JobIdentifier fromJson(String json) { 134 | return GSON.fromJson(json, JobIdentifier.class); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/Agent.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.google.gson.FieldNamingPolicy; 20 | import com.google.gson.Gson; 21 | import com.google.gson.GsonBuilder; 22 | import com.google.gson.annotations.Expose; 23 | import com.google.gson.annotations.SerializedName; 24 | import com.google.gson.reflect.TypeToken; 25 | 26 | import java.lang.reflect.Type; 27 | import java.util.ArrayList; 28 | import java.util.Collection; 29 | import java.util.List; 30 | 31 | import static com.example.elasticagent.ExamplePlugin.GSON; 32 | 33 | /** 34 | * Represents an Agent. 35 | * See https://plugin-api.go.cd/current/elastic-agents/#the-elastic-agent-object for more details. 36 | */ 37 | public class Agent { 38 | 39 | public enum AgentState { 40 | Idle, Building, LostContact, Missing, Unknown 41 | } 42 | 43 | public enum BuildState { 44 | Idle, Building, Cancelled, Unknown 45 | } 46 | 47 | public enum ConfigState { 48 | Pending, Enabled, Disabled 49 | } 50 | 51 | public static final Type AGENT_METADATA_LIST_TYPE = new TypeToken>() { 52 | }.getType(); 53 | 54 | 55 | 56 | @Expose 57 | @SerializedName("agent_id") 58 | private String agentId; 59 | 60 | @Expose 61 | @SerializedName("agent_state") 62 | private AgentState agentState; 63 | 64 | @Expose 65 | @SerializedName("build_state") 66 | private BuildState buildState; 67 | 68 | @Expose 69 | @SerializedName("config_state") 70 | private ConfigState configState; 71 | 72 | // Public constructor needed for JSON de-serialization 73 | public Agent() { 74 | } 75 | 76 | // Used in tests 77 | public Agent(String agentId, AgentState agentState, BuildState buildState, ConfigState configState) { 78 | this.agentId = agentId; 79 | this.agentState = agentState; 80 | this.buildState = buildState; 81 | this.configState = configState; 82 | } 83 | 84 | public String elasticAgentId() { 85 | return agentId; 86 | } 87 | 88 | public AgentState agentState() { 89 | return agentState; 90 | } 91 | 92 | public BuildState buildState() { 93 | return buildState; 94 | } 95 | 96 | public ConfigState configState() { 97 | return configState; 98 | } 99 | 100 | public static List fromJSONArray(String json) { 101 | return GSON.fromJson(json, AGENT_METADATA_LIST_TYPE); 102 | } 103 | 104 | public static String toJSONArray(Collection metadata) { 105 | return new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create().toJson(metadata); 106 | } 107 | 108 | @Override 109 | public String toString() { 110 | return "Agent{" + 111 | "agentId='" + agentId + '\'' + 112 | ", agentState='" + agentState + '\'' + 113 | ", buildState='" + buildState + '\'' + 114 | ", configState='" + configState + '\'' + 115 | '}'; 116 | } 117 | 118 | @Override 119 | public boolean equals(Object o) { 120 | if (this == o) return true; 121 | if (o == null || getClass() != o.getClass()) return false; 122 | 123 | Agent agent = (Agent) o; 124 | 125 | if (agentId != null ? !agentId.equals(agent.agentId) : agent.agentId != null) return false; 126 | if (agentState != agent.agentState) return false; 127 | if (buildState != agent.buildState) return false; 128 | return configState == agent.configState; 129 | 130 | } 131 | 132 | @Override 133 | public int hashCode() { 134 | int result = agentId != null ? agentId.hashCode() : 0; 135 | result = 31 * result + (agentState != null ? agentState.hashCode() : 0); 136 | result = 31 * result + (buildState != null ? buildState.hashCode() : 0); 137 | result = 31 * result + (configState != null ? configState.hashCode() : 0); 138 | return result; 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/PluginSettings.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.google.gson.FieldNamingPolicy; 20 | import com.google.gson.Gson; 21 | import com.google.gson.GsonBuilder; 22 | import com.google.gson.annotations.Expose; 23 | import com.google.gson.annotations.SerializedName; 24 | import org.joda.time.Period; 25 | 26 | import java.util.Map; 27 | 28 | // TODO: Implement any settings that your plugin needs 29 | public class PluginSettings { 30 | public static final Gson GSON = new GsonBuilder() 31 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 32 | .excludeFieldsWithoutExposeAnnotation() 33 | .create(); 34 | 35 | @Expose 36 | @SerializedName("go_server_url") 37 | private String goServerUrl; 38 | 39 | @Expose 40 | @SerializedName("auto_register_timeout") 41 | private String autoRegisterTimeout; 42 | 43 | @Expose 44 | @SerializedName("api_user") 45 | private String apiUser; 46 | 47 | @Expose 48 | @SerializedName("api_key") 49 | private String apiKey; 50 | 51 | @Expose 52 | @SerializedName("api_url") 53 | private String apiUrl; 54 | 55 | private Period autoRegisterPeriod; 56 | 57 | public PluginSettings() { 58 | } 59 | 60 | public PluginSettings(String goServerUrl, String autoRegisterTimeout, String apiUser, String apiKey, String apiUrl, Period autoRegisterPeriod) { 61 | this.goServerUrl = goServerUrl; 62 | this.autoRegisterTimeout = autoRegisterTimeout; 63 | this.apiUser = apiUser; 64 | this.apiKey = apiKey; 65 | this.apiUrl = apiUrl; 66 | this.autoRegisterPeriod = autoRegisterPeriod; 67 | } 68 | 69 | public static PluginSettings fromJSON(String json) { 70 | return GSON.fromJson(json, PluginSettings.class); 71 | } 72 | 73 | public static PluginSettings fromConfiguration(Map pluginSettings) { 74 | return GSON.fromJson(GSON.toJson(pluginSettings), PluginSettings.class); 75 | } 76 | 77 | @Override 78 | public boolean equals(Object o) { 79 | if (this == o) return true; 80 | if (o == null || getClass() != o.getClass()) return false; 81 | 82 | PluginSettings that = (PluginSettings) o; 83 | 84 | if (goServerUrl != null ? !goServerUrl.equals(that.goServerUrl) : that.goServerUrl != null) return false; 85 | if (autoRegisterTimeout != null ? !autoRegisterTimeout.equals(that.autoRegisterTimeout) : that.autoRegisterTimeout != null) 86 | return false; 87 | if (apiUser != null ? !apiUser.equals(that.apiUser) : that.apiUser != null) return false; 88 | if (apiKey != null ? !apiKey.equals(that.apiKey) : that.apiKey != null) return false; 89 | return apiUrl != null ? apiUrl.equals(that.apiUrl) : that.apiUrl == null; 90 | } 91 | 92 | @Override 93 | public int hashCode() { 94 | int result = goServerUrl != null ? goServerUrl.hashCode() : 0; 95 | result = 31 * result + (getAutoRegisterPeriod() != null ? getAutoRegisterPeriod().hashCode() : 0); 96 | result = 31 * result + (apiUser != null ? apiUser.hashCode() : 0); 97 | result = 31 * result + (apiKey != null ? apiKey.hashCode() : 0); 98 | result = 31 * result + (apiUrl != null ? apiUrl.hashCode() : 0); 99 | return result; 100 | } 101 | 102 | public Period getAutoRegisterPeriod() { 103 | if (this.autoRegisterPeriod == null) { 104 | this.autoRegisterPeriod = new Period().withMinutes(Integer.parseInt(getAutoRegisterTimeout())); 105 | } 106 | return this.autoRegisterPeriod; 107 | } 108 | 109 | private String getAutoRegisterTimeout() { 110 | if (autoRegisterTimeout == null) { 111 | autoRegisterTimeout = "10"; 112 | } 113 | return autoRegisterTimeout; 114 | } 115 | 116 | public String getApiUser() { 117 | return apiUser; 118 | } 119 | 120 | public String getApiKey() { 121 | return apiKey; 122 | } 123 | 124 | public String getApiUrl() { 125 | return apiUrl; 126 | } 127 | 128 | public String getGoServerUrl() { 129 | return goServerUrl; 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/AgentInstances.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent; 18 | 19 | import com.example.elasticagent.models.AgentStatusReport; 20 | import com.example.elasticagent.models.JobIdentifier; 21 | import com.example.elasticagent.models.StatusReport; 22 | import com.example.elasticagent.requests.CreateAgentRequest; 23 | 24 | 25 | /** 26 | * Plugin implementors should implement these methods to interface to your cloud. 27 | * This interface is merely a suggestion for a very simple plugin. You may change it to your needs. 28 | */ 29 | public interface AgentInstances { 30 | /** 31 | * This message is sent to request creation of an agent instance. 32 | * Implementations may, at their discretion choose to not spin up an agent instance. 33 | *

34 | * So that instances created are auto-registered with the server, the agent instance MUST have an 35 | * autoregister.properties file. 36 | * 37 | * @param request the request object 38 | */ 39 | T create(CreateAgentRequest request) throws Exception; 40 | 41 | /** 42 | * This message is sent when the plugin needs to terminate the agent instance. 43 | * 44 | * @param agentId the elastic agent id 45 | * @param clusterProfile the plugin cluster profile properties object 46 | */ 47 | void terminate(String agentId, ClusterProfileProperties clusterProfile) throws Exception; 48 | 49 | /** 50 | * This message is sent from the {@link com.example.elasticagent.executors.ServerPingRequestExecutor} 51 | * to terminate instances that did not register with the server after a timeout. The timeout may be configurable and 52 | * set via the {@link ClusterProfileProperties} instance that is passed in. 53 | * 54 | * @param clusterProfile the plugin clusterProfile object 55 | * @param agents the list of all the agents 56 | */ 57 | void terminateUnregisteredInstances(ClusterProfileProperties clusterProfile, Agents agents) throws Exception; 58 | 59 | /** 60 | * This message is sent from the {@link com.example.elasticagent.executors.ServerPingRequestExecutor} 61 | * to filter out any new agents, that have registered before the timeout period. The timeout may be configurable and 62 | * set via the {@link ClusterProfileProperties} instance that is passed in. 63 | * 64 | * @param clusterProfile the plugin clusterProfile object 65 | * @param agents the list of all the agents 66 | * @return a list of agent instances which were created after {@link ClusterProfileProperties#getAutoRegisterPeriod()} ago. 67 | */ 68 | Agents instancesCreatedAfterTimeout(ClusterProfileProperties clusterProfile, Agents agents); 69 | 70 | /** 71 | * This message is sent after plugin initialization time so that the plugin may connect to the cloud provider 72 | * and fetch a list of all instances that have been spun up by this plugin (before the server was shut down). 73 | * This call should be should ideally remember if the agent instances are refreshed, and do nothing if instances 74 | * were previously refreshed. 75 | * 76 | * @param clusterProfile the cluster profile properties 77 | */ 78 | void refreshAll(ClusterProfileProperties clusterProfile) throws Exception; 79 | 80 | /** 81 | * This 82 | * Returns an agent instance with the specified id or null, if the agent is not found. 83 | * 84 | * @param agentId the elastic agent id 85 | */ 86 | T find(String agentId); 87 | 88 | /** 89 | * Finds an agent instance with the specified jobIdentifier 90 | * @param jobIdentifier The Job Identifier 91 | * @return An agent instance, or null if the agent is not found 92 | */ 93 | T find(JobIdentifier jobIdentifier); 94 | 95 | /** 96 | * Get the status report from the agents 97 | * @param clusterProfile the cluster properties object 98 | * @return A StatusReport object 99 | * @throws Exception 100 | */ 101 | StatusReport getStatusReport(ClusterProfileProperties clusterProfile) throws Exception; 102 | 103 | /** 104 | * Get the status report of an agent instance 105 | * @param clusterProfile The cluster profile properties object 106 | * @param agentInstance The agent instance 107 | * @return An AgentStatusReport object 108 | */ 109 | AgentStatusReport getAgentStatusReport(ClusterProfileProperties clusterProfile, T agentInstance); 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/test/java/com/example/elasticagent/requests/ClusterProfileChangedRequestTest.java: -------------------------------------------------------------------------------- 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 | 17 | package com.example.elasticagent.requests; 18 | 19 | import com.example.elasticagent.ClusterProfileProperties; 20 | import com.example.elasticagent.requests.ClusterProfileChangedRequest.ChangeStatus; 21 | import org.hamcrest.Matchers; 22 | import org.junit.jupiter.api.Test; 23 | import org.junit.jupiter.params.ParameterizedTest; 24 | import org.junit.jupiter.params.provider.Arguments; 25 | import org.junit.jupiter.params.provider.MethodSource; 26 | 27 | import java.util.stream.Stream; 28 | 29 | import static com.example.elasticagent.requests.ClusterProfileChangedRequest.ChangeStatus.CREATED; 30 | import static com.example.elasticagent.requests.ClusterProfileChangedRequest.ChangeStatus.DELETED; 31 | import static org.hamcrest.MatcherAssert.assertThat; 32 | import static org.hamcrest.Matchers.equalTo; 33 | 34 | public class ClusterProfileChangedRequestTest { 35 | 36 | class CreateAndDelete { 37 | 38 | } 39 | @ParameterizedTest 40 | @MethodSource("inputChangeStatus") 41 | public void shouldDeserializeCreateAndDeleteClusterChangeFromJSON(String status, ChangeStatus changeStatus) throws Exception { 42 | String json = "{\n" + 43 | " \"status\": \"" + status + "\",\n" + 44 | " \"cluster_profiles_properties\": {\n" + 45 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 46 | " \"auto_register_timeout\": \"20m\",\n" + 47 | " \"api_user\": \"test\",\n" + 48 | " \"api_key\": \"test-api-key\",\n" + 49 | " \"api_url\": \"https://aws.api.com/api\"\n" + 50 | " }\n" + 51 | "}"; 52 | 53 | ClusterProfileProperties expectedClusterProperties = new ClusterProfileProperties( 54 | "https://localhost:8154/go", 55 | "20m", 56 | "test", 57 | "test-api-key", 58 | "https://aws.api.com/api", 59 | null 60 | ); 61 | 62 | ClusterProfileChangedRequest request = ClusterProfileChangedRequest.fromJSON(json); 63 | assertThat(request.changeStatus(), equalTo(changeStatus)); 64 | assertThat(request.clusterProperties(), Matchers.equalTo(expectedClusterProperties)); 65 | } 66 | 67 | private static Stream inputChangeStatus() { 68 | return Stream.of( 69 | Arguments.of("created", CREATED), 70 | Arguments.of("deleted", DELETED) 71 | ); 72 | } 73 | 74 | 75 | @Test 76 | public void shouldDeserializeUpdateClusterChangeFromJSON() throws Exception { 77 | String json = "{\n" + 78 | " \"status\": \"updated\",\n" + 79 | " \"cluster_profiles_properties\": {\n" + 80 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 81 | " \"auto_register_timeout\": \"20m\",\n" + 82 | " \"api_user\": \"prod\",\n" + 83 | " \"api_key\": \"prod-api-key\",\n" + 84 | " \"api_url\": \"https://aws.api.com/api\"\n" + 85 | " },\n" + 86 | " \"old_cluster_profiles_properties\": {\n" + 87 | " \"go_server_url\": \"https://localhost:8154/go\",\n" + 88 | " \"auto_register_timeout\": \"20m\",\n" + 89 | " \"api_user\": \"test\",\n" + 90 | " \"api_key\": \"test-api-key\",\n" + 91 | " \"api_url\": \"https://aws.api.com/api\"\n" + 92 | " }\n" + 93 | "}"; 94 | 95 | 96 | 97 | ClusterProfileProperties newClusterProperties = new ClusterProfileProperties( 98 | "https://localhost:8154/go", 99 | "20m", 100 | "prod", 101 | "prod-api-key", 102 | "https://aws.api.com/api", 103 | null 104 | ); 105 | 106 | ClusterProfileProperties oldClusterProperties = new ClusterProfileProperties( 107 | "https://localhost:8154/go", 108 | "20m", 109 | "test", 110 | "test-api-key", 111 | "https://aws.api.com/api", 112 | null 113 | ); 114 | 115 | ClusterProfileChangedRequest request = ClusterProfileChangedRequest.fromJSON(json); 116 | assertThat(request.changeStatus(), equalTo(ChangeStatus.UPDATED)); 117 | assertThat(request.oldClusterProperties(), Matchers.equalTo(oldClusterProperties)); 118 | assertThat(request.clusterProperties(), Matchers.equalTo(newClusterProperties)); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/example/elasticagent/utils/Size.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2010-2013 Coda Hale and Yammer, Inc., 2014-2016 Dropwizard Team 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 com.example.elasticagent.utils; 18 | 19 | import com.google.common.collect.ImmutableSortedMap; 20 | 21 | import java.util.Locale; 22 | import java.util.Map; 23 | import java.util.regex.Matcher; 24 | import java.util.regex.Pattern; 25 | 26 | import static com.google.common.base.Preconditions.checkArgument; 27 | import static java.util.Objects.requireNonNull; 28 | 29 | public class Size implements Comparable { 30 | private static final Pattern SIZE_PATTERN = Pattern.compile("(\\d+)\\s*(\\S+)"); 31 | 32 | private static final Map SUFFIXES = ImmutableSortedMap.orderedBy(String.CASE_INSENSITIVE_ORDER) 33 | .put("B", SizeUnit.BYTES) 34 | .put("byte", SizeUnit.BYTES) 35 | .put("bytes", SizeUnit.BYTES) 36 | .put("K", SizeUnit.KILOBYTES) 37 | .put("KB", SizeUnit.KILOBYTES) 38 | .put("KiB", SizeUnit.KILOBYTES) 39 | .put("kilobyte", SizeUnit.KILOBYTES) 40 | .put("kilobytes", SizeUnit.KILOBYTES) 41 | .put("M", SizeUnit.MEGABYTES) 42 | .put("MB", SizeUnit.MEGABYTES) 43 | .put("MiB", SizeUnit.MEGABYTES) 44 | .put("megabyte", SizeUnit.MEGABYTES) 45 | .put("megabytes", SizeUnit.MEGABYTES) 46 | .put("G", SizeUnit.GIGABYTES) 47 | .put("GB", SizeUnit.GIGABYTES) 48 | .put("GiB", SizeUnit.GIGABYTES) 49 | .put("gigabyte", SizeUnit.GIGABYTES) 50 | .put("gigabytes", SizeUnit.GIGABYTES) 51 | .put("T", SizeUnit.TERABYTES) 52 | .put("TB", SizeUnit.TERABYTES) 53 | .put("TiB", SizeUnit.TERABYTES) 54 | .put("terabyte", SizeUnit.TERABYTES) 55 | .put("terabytes", SizeUnit.TERABYTES) 56 | .build(); 57 | 58 | public static Size bytes(long count) { 59 | return new Size(count, SizeUnit.BYTES); 60 | } 61 | 62 | public static Size kilobytes(long count) { 63 | return new Size(count, SizeUnit.KILOBYTES); 64 | } 65 | 66 | public static Size megabytes(long count) { 67 | return new Size(count, SizeUnit.MEGABYTES); 68 | } 69 | 70 | public static Size gigabytes(long count) { 71 | return new Size(count, SizeUnit.GIGABYTES); 72 | } 73 | 74 | public static Size terabytes(long count) { 75 | return new Size(count, SizeUnit.TERABYTES); 76 | } 77 | 78 | public static Size parse(String size) { 79 | checkArgument(size != null, "Invalid size: null"); 80 | final Matcher matcher = SIZE_PATTERN.matcher(size); 81 | checkArgument(matcher.matches(), "Invalid size: " + size); 82 | 83 | final long count = Long.parseLong(matcher.group(1)); 84 | final SizeUnit unit = SUFFIXES.get(matcher.group(2)); 85 | if (unit == null) { 86 | throw new IllegalArgumentException("Invalid size: " + size + ". Wrong size unit"); 87 | } 88 | 89 | return new Size(count, unit); 90 | } 91 | 92 | private final long count; 93 | private final SizeUnit unit; 94 | 95 | private Size(long count, SizeUnit unit) { 96 | this.count = count; 97 | this.unit = requireNonNull(unit); 98 | } 99 | 100 | public long getQuantity() { 101 | return count; 102 | } 103 | 104 | public SizeUnit getUnit() { 105 | return unit; 106 | } 107 | 108 | public long toBytes() { 109 | return SizeUnit.BYTES.convert(count, unit); 110 | } 111 | 112 | public long toKilobytes() { 113 | return SizeUnit.KILOBYTES.convert(count, unit); 114 | } 115 | 116 | public long toMegabytes() { 117 | return SizeUnit.MEGABYTES.convert(count, unit); 118 | } 119 | 120 | public long toGigabytes() { 121 | return SizeUnit.GIGABYTES.convert(count, unit); 122 | } 123 | 124 | public long toTerabytes() { 125 | return SizeUnit.TERABYTES.convert(count, unit); 126 | } 127 | 128 | @Override 129 | public boolean equals(Object obj) { 130 | if (this == obj) { 131 | return true; 132 | } 133 | if ((obj == null) || (getClass() != obj.getClass())) { 134 | return false; 135 | } 136 | final Size size = (Size) obj; 137 | return (count == size.count) && (unit == size.unit); 138 | } 139 | 140 | @Override 141 | public int hashCode() { 142 | return (31 * (int) (count ^ (count >>> 32))) + unit.hashCode(); 143 | } 144 | 145 | @Override 146 | public String toString() { 147 | String units = unit.toString().toLowerCase(Locale.ENGLISH); 148 | if (count == 1) { 149 | units = units.substring(0, units.length() - 1); 150 | } 151 | return Long.toString(count) + ' ' + units; 152 | } 153 | 154 | @Override 155 | public int compareTo(Size other) { 156 | if (unit == other.unit) { 157 | return Long.compare(count, other.count); 158 | } 159 | 160 | return Long.compare(toBytes(), other.toBytes()); 161 | } 162 | } 163 | --------------------------------------------------------------------------------