├── .github ├── dependabot.yml └── workflows │ ├── pr_workflow.yml │ ├── release.yml │ ├── test_and_build.yml │ └── update-gradle-wrapper.yml ├── .gitignore ├── CHANGELOG.md ├── INSTALL.md ├── LICENSE ├── README.md ├── build.gradle ├── contrib └── scripts │ ├── bootstrap-via-installer │ ├── Dockerfile │ ├── agent-launcher-logback-include.xml │ ├── agent-logback-include.xml │ └── go-agent │ └── bootstrap-without-installed-agent │ ├── Dockerfile │ ├── agent-launcher-logback-include.xml │ ├── agent-logback-include.xml │ └── go-agent ├── docker-files └── scripts │ ├── clean-containers │ └── clean-images ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── cluster-profiles │ ├── basic-settings.png │ └── docker-client-settings.png ├── configure-job.png ├── elastic_profiles_spa.png ├── pipeline.png ├── plugin-settings.png ├── profile.png ├── profiles_page.png └── quick-edit.png ├── settings.gradle └── src ├── main ├── java │ ├── cd │ │ └── go │ │ │ └── contrib │ │ │ └── elasticagents │ │ │ └── docker │ │ │ ├── Agent.java │ │ │ ├── AgentInstances.java │ │ │ ├── Agents.java │ │ │ ├── Clock.java │ │ │ ├── ClusterProfile.java │ │ │ ├── ClusterProfileProperties.java │ │ │ ├── ConsoleLogAppender.java │ │ │ ├── Constants.java │ │ │ ├── CpusSpecification.java │ │ │ ├── DockerClientFactory.java │ │ │ ├── DockerContainer.java │ │ │ ├── DockerContainers.java │ │ │ ├── DockerPlugin.java │ │ │ ├── ElasticAgentProfile.java │ │ │ ├── HostMetadata.java │ │ │ ├── Hosts.java │ │ │ ├── MemorySpecification.java │ │ │ ├── Networks.java │ │ │ ├── PluginRequest.java │ │ │ ├── PluginSettings.java │ │ │ ├── Request.java │ │ │ ├── RequestExecutor.java │ │ │ ├── ServerRequestFailedException.java │ │ │ ├── SetupSemaphore.java │ │ │ ├── executors │ │ │ ├── AgentStatusReportExecutor.java │ │ │ ├── ClusterProfileValidateRequestExecutor.java │ │ │ ├── ClusterStatusReportExecutor.java │ │ │ ├── CpusMetadata.java │ │ │ ├── CreateAgentRequestExecutor.java │ │ │ ├── Field.java │ │ │ ├── GetCapabilitiesExecutor.java │ │ │ ├── GetClusterProfileMetadataExecutor.java │ │ │ ├── GetClusterProfileViewRequestExecutor.java │ │ │ ├── GetPluginSettingsIconExecutor.java │ │ │ ├── GetProfileMetadataExecutor.java │ │ │ ├── GetProfileViewExecutor.java │ │ │ ├── GoServerURLMetadata.java │ │ │ ├── JobCompletionRequestExecutor.java │ │ │ ├── MemoryMetadata.java │ │ │ ├── Metadata.java │ │ │ ├── MigrateConfigurationRequestExecutor.java │ │ │ ├── NonBlankField.java │ │ │ ├── ProfileValidateRequestExecutor.java │ │ │ ├── ServerPingRequestExecutor.java │ │ │ └── ShouldAssignWorkRequestExecutor.java │ │ │ ├── models │ │ │ ├── AgentStatusReport.java │ │ │ ├── ContainerStatusReport.java │ │ │ ├── ExceptionMessage.java │ │ │ ├── JobIdentifier.java │ │ │ ├── NotRunningAgentStatusReport.java │ │ │ └── StatusReport.java │ │ │ ├── requests │ │ │ ├── AgentStatusReportRequest.java │ │ │ ├── ClusterProfileValidateRequest.java │ │ │ ├── ClusterStatusReportRequest.java │ │ │ ├── CreateAgentRequest.java │ │ │ ├── JobCompletionRequest.java │ │ │ ├── MigrateConfigurationRequest.java │ │ │ ├── ProfileValidateRequest.java │ │ │ ├── ServerPingRequest.java │ │ │ └── ShouldAssignWorkRequest.java │ │ │ ├── utils │ │ │ └── Util.java │ │ │ ├── validator │ │ │ ├── MemorySettingsProfileValidator.java │ │ │ └── ProfileValidator.java │ │ │ └── views │ │ │ └── ViewBuilder.java │ └── com │ │ └── spotify │ │ └── docker │ │ └── client │ │ └── messages │ │ └── AutoValue_ImageInfo.java └── resources │ ├── agent-status-report.template.ftlh │ ├── docker-plain.svg │ ├── error.template.ftlh │ ├── not-running-agent-status-report.template.ftlh │ ├── plugin-settings.template.html │ ├── profile.template.html │ └── status-report.template.ftlh └── test └── java └── cd └── go └── contrib └── elasticagents └── docker ├── AgentTest.java ├── BaseTest.java ├── ClusterProfilePropertiesTest.java ├── CpusSpecificationTest.java ├── DockerContainerTest.java ├── DockerContainersTest.java ├── HostMetadataTest.java ├── HostsTest.java ├── MemorySpecificationTest.java ├── NetworksTest.java ├── PluginRequestTest.java ├── SetupSemaphoreTest.java ├── executors ├── AgentStatusReportExecutorTest.java ├── ClusterProfilePropertiesValidateRequestExecutorTest.java ├── ClusterStatusReportExecutorTest.java ├── CreateAgentRequestExecutorTest.java ├── GetCapabilitiesExecutorTest.java ├── GetClusterProfilePropertiesMetadataExecutorTest.java ├── GetClusterProfileViewRequestExecutorTest.java ├── GetPluginSettingsIconExecutorTest.java ├── GetProfileMetadataExecutorTest.java ├── GetProfileViewExecutorTest.java ├── GoServerURLMetadataTest.java ├── JobCompletionRequestExecutorTest.java ├── MigrateConfigurationRequestExecutorTest.java ├── ProfileValidateRequestExecutorTest.java ├── ServerPingRequestExecutorTest.java └── ShouldAssignWorkRequestExecutorTest.java ├── models └── JobIdentifierTest.java ├── requests ├── AgentStatusReportRequestTest.java ├── ClusterStatusReportRequestTest.java ├── CreateAgentRequestTest.java ├── JobCompletionRequestTest.java ├── MigrateConfigurationRequestTest.java ├── ServerPingRequestTest.java └── ShouldAssignWorkRequestTest.java ├── utils ├── JobIdentifierMother.java └── UtilTest.java └── validator └── MemorySettingsProfileValidatorTest.java /.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 | -------------------------------------------------------------------------------- /.github/workflows/pr_workflow.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Testing For PRs 5 | 6 | on: [ pull_request ] 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | docker-version: # See https://endoflife.date/docker-engine / https://download.docker.com/linux/static/stable/x86_64/ 14 | - "v23.0.6" # 2023-02 --> EOL ? 15 | - "v25.0.5" # 2024-01 --> EOL ? 16 | - "v27.5.1" # 2025-01 --> EOL ? 17 | - "v28.1.1" # 2025-04 --> EOL ? 18 | fail-fast: false 19 | steps: 20 | - name: Harden the runner (Audit all outbound calls) 21 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 22 | with: 23 | egress-policy: audit 24 | 25 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 26 | - name: Set up JDK 27 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 28 | with: 29 | java-version: 17 30 | distribution: temurin 31 | - name: Set up Docker 32 | uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19 # v4.3.0 33 | with: 34 | version: ${{ matrix.docker-version }} 35 | - name: Build with Gradle 36 | run: ./gradlew assemble check 37 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Create Stable Release 3 | 4 | # Controls when the action will run. Workflow runs when manually triggered using the UI 5 | # or API. 6 | on: 7 | workflow_dispatch: 8 | # Inputs the workflow accepts. 9 | inputs: 10 | prerelease: 11 | description: 'The release should be an experimental release' 12 | default: 'NO' 13 | required: true 14 | 15 | jobs: 16 | build_and_release: 17 | runs-on: ubuntu-latest 18 | env: 19 | GITHUB_USER: "gocd-contrib" 20 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 21 | PRERELEASE: "${{ github.event.inputs.prerelease }}" 22 | steps: 23 | - name: Harden the runner (Audit all outbound calls) 24 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 25 | with: 26 | egress-policy: audit 27 | 28 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 29 | with: 30 | fetch-depth: 0 31 | - name: Set up JDK 32 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 33 | with: 34 | java-version: 17 35 | distribution: temurin 36 | - name: Release 37 | run: ./gradlew githubRelease 38 | -------------------------------------------------------------------------------- /.github/workflows/test_and_build.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Gradle 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-gradle 3 | 4 | name: Test and Build 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | 10 | jobs: 11 | test: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | docker-version: # See https://endoflife.date/docker-engine / https://download.docker.com/linux/static/stable/x86_64/ 16 | - "v23.0.6" # 2023-02 --> EOL ? 17 | - "v25.0.5" # 2024-01 --> EOL ? 18 | - "v27.5.1" # 2025-01 --> EOL ? 19 | - "v28.1.1" # 2025-04 --> EOL ? 20 | fail-fast: false 21 | steps: 22 | - name: Harden the runner (Audit all outbound calls) 23 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 24 | with: 25 | egress-policy: audit 26 | 27 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 28 | - name: Set up JDK 29 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 30 | with: 31 | java-version: 17 32 | distribution: temurin 33 | - name: Set up Docker 34 | uses: docker/setup-docker-action@b60f85385d03ac8acfca6d9996982511d8620a19 # v4.3.0 35 | with: 36 | version: ${{ matrix.docker-version }} 37 | - name: Build with Gradle 38 | run: ./gradlew assemble check 39 | previewGithubRelease: 40 | needs: test 41 | runs-on: ubuntu-latest 42 | env: 43 | GITHUB_USER: "gocd-contrib" 44 | GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" 45 | steps: 46 | - name: Harden the runner (Audit all outbound calls) 47 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 48 | with: 49 | egress-policy: audit 50 | 51 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 52 | with: 53 | fetch-depth: 0 54 | - name: Set up JDK 55 | uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 56 | with: 57 | java-version: 17 58 | distribution: temurin 59 | - name: Test with Gradle 60 | run: ./gradlew githubRelease 61 | -------------------------------------------------------------------------------- /.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 | 12 | steps: 13 | - name: Harden the runner (Audit all outbound calls) 14 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 15 | with: 16 | egress-policy: audit 17 | 18 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 19 | 20 | - name: Update Gradle Wrapper 21 | uses: gradle-update/update-gradle-wrapper-action@512b1875f3b6270828abfe77b247d5895a2da1e5 # v2.1.0 22 | with: 23 | labels: dependencies 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | target/ 4 | build 5 | .gradle 6 | .gradletasknamecache 7 | .tags 8 | .tags1 9 | out/ 10 | src/main/resources-generated/ 11 | **/.DS_Store 12 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-via-installer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ... 2 | 3 | # GoCD agent needs the jdk 17 and appropriate binaries for your SCM (git/svn/mercurial) 4 | # Add then here by the appropriate package manager 5 | 6 | # add steps to download and install the gocd agent 7 | # See https://docs.gocd.io/current/installation/install/agent/linux.html 8 | 9 | # download tini to ensure that an init process exists 10 | ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini /tini 11 | RUN chmod +x /tini 12 | ENTRYPOINT ["/tini", "--"] 13 | 14 | # ensure that the container logs on stdout 15 | ADD agent-launcher-logback-include.xml /var/lib/go-agent/config/agent-launcher-logback-include.xml 16 | ADD agent-logback-include.xml /var/lib/go-agent/config/agent-logback-include.xml 17 | 18 | ADD go-agent /go-agent 19 | RUN chmod 755 /go-agent 20 | 21 | # Run the bootstrapper as the `go` user 22 | USER go 23 | CMD /go-agent 24 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-via-installer/agent-launcher-logback-include.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | 24 | 25 | 26 | ${gocd.agentLauncher.logback.defaultPattern:-%date{ISO8601} %-5level [%thread] %logger{0}:%line - %msg%n} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-via-installer/agent-logback-include.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | 24 | 25 | 26 | ${gocd.agent.logback.defaultPattern:-%date{ISO8601} %-5level [%thread] %logger{0}:%line - %msg%n} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-via-installer/go-agent: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use this script to start up your GoCD agent process. The script assumes that 4 | # the agent is installed using .deb/.rpm as the case may be with your OS. 5 | 6 | die () { 7 | echo 8 | echo "$*" 9 | echo 10 | exit 1 11 | } 12 | 13 | if ! [[ -x /etc/init.d/go-agent && -e /etc/defaults/go-agent && -d /var/lib/go-agent ]]; then 14 | echo "It looks like the agent was not installed via deb/rpm" 15 | exit -1 16 | fi 17 | 18 | if [[ "$(whoami)" != 'go' ]]; then 19 | echo "Must run this script as the `go` user" 20 | exit -1 21 | fi 22 | 23 | mkdir -p /var/lib/go-agent/config || die "Could not create /var/lib/go-agent/config" 24 | 25 | # write out autoregister.properties 26 | ( 27 | cat < /var/lib/go-agent/config/autoregister.properties 34 | 35 | # write out server url, and prevent backgrounding 36 | echo "GO_SERVER_URL=${GO_EA_SERVER_URL}" > /etc/default/go-agent 37 | 38 | # prevent environment variables from leaking into the agent 39 | unset GO_EA_AUTO_REGISTER_KEY 40 | unset GO_EA_AUTO_REGISTER_ENVIRONMENT 41 | unset GO_EA_AUTO_REGISTER_ELASTIC_AGENT_ID 42 | unset GO_EA_AUTO_REGISTER_ELASTIC_PLUGIN_ID 43 | unset GO_EA_SERVER_URL 44 | 45 | exec /usr/share/go-agent/agent.sh go-agent 46 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-without-installed-agent/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ... 2 | 3 | # GoCD agent needs the jdk 17 and appropriate binaries for your SCM (git/svn/mercurial) 4 | # Add then here by the appropriate package manager 5 | 6 | # download tini to ensure that an init process exists 7 | ADD https://github.com/krallin/tini/releases/download/v0.19.0/tini /tini 8 | RUN chmod +x /tini 9 | ENTRYPOINT ["/tini", "--"] 10 | 11 | # Add a user to run the go agent 12 | RUN adduser go go -h /go -S -D 13 | 14 | # ensure that the container logs on stdout 15 | ADD agent-launcher-logback-include.xml /go/config/agent-launcher-logback-include.xml 16 | ADD agent-logback-include.xml /go/config/agent-logback-include.xml 17 | 18 | ADD go-agent /go-agent 19 | RUN chmod 755 /go-agent 20 | 21 | # Run the bootstrapper as the `go` user 22 | USER go 23 | CMD /go/go-agent 24 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-without-installed-agent/agent-launcher-logback-include.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | 24 | 25 | 26 | ${gocd.agentLauncher.logback.defaultPattern:-%date{ISO8601} %-5level [%thread] %logger{0}:%line - %msg%n} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-without-installed-agent/agent-logback-include.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | 24 | 25 | 26 | ${gocd.agent.logback.defaultPattern:-%date{ISO8601} %-5level [%thread] %logger{0}:%line - %msg%n} 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /contrib/scripts/bootstrap-without-installed-agent/go-agent: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Use this script to start up your GoCD agent process. The script assumes that — 4 | # - no agent is installed via deb/rpm 5 | # - curl is available on $PATH 6 | # - user `go` exists 7 | 8 | die () { 9 | echo 10 | echo "$*" 11 | echo 12 | exit 1 13 | } 14 | 15 | download_file () { 16 | local remote_path="$1" 17 | local local_path="$2" 18 | local url="${GO_EA_SERVER_URL}${remote_path}" 19 | 20 | echo "Downloading remote file ${url} into ${local_path}." 21 | curl --fail --silent --insecure "${url}" -o "${local_path}" || die "Could not fetch ${url}." 22 | } 23 | 24 | get_checksums () { 25 | local url="${GO_EA_SERVER_URL}/admin/latest-agent.status" 26 | curl --fail --silent --insecure "${url}" -D - || die "Could not fetch ${url}" 27 | } 28 | 29 | get_checksum () { 30 | local checksums="${1}" 31 | local header="${2}" 32 | echo "${checksums}" | tr -d '\r' | grep "${header}" | sed -e "s/${header}: //g" 33 | } 34 | 35 | if [[ "$(whoami)" != 'go' ]]; then 36 | echo "Must run this script as the `go` user" 37 | exit -1 38 | fi 39 | 40 | mkdir -p /go/config || die "Could not create /go/config" 41 | cd /go || die "Could not chdir to /go" 42 | 43 | # write out autoregister.properties 44 | ( 45 | cat < ./config/autoregister.properties 52 | 53 | while true; do 54 | download_file '/admin/agent' 'agent.jar' 55 | download_file '/admin/agent-plugins.zip' 'agent-plugins.zip' 56 | download_file '/admin/tfs-impl.jar' 'tfs-impl.jar' 57 | 58 | checksums=$(get_checksums) 59 | agent_md5=$(get_checksum "${checksums}" 'Agent-Content-MD5') 60 | tfs_md5=$(get_checksum "${checksums}" 'TFS-SDK-Content-MD5') 61 | plugins_md5=$(get_checksum "${checksums}" 'Agent-Plugins-Content-MD5') 62 | agent_launcher_md5=$(get_checksum "${checksums}" 'Agent-Launcher-Content-MD5') 63 | 64 | echo "Launching the GoCD Agent, and waiting for it to exit..." 65 | RUN_CMD=(java \ 66 | -Xms128m \ 67 | -Xmx256m \ 68 | -Dagent.plugins.md5="${plugins_md5}" \ 69 | -Dagent.binary.md5="${agent_md5}" \ 70 | -Dagent.launcher.md5="${agent_launcher_md5}" \ 71 | -Dagent.tfs.md5="${tfs_md5}" \ 72 | -jar \ 73 | agent.jar \ 74 | -serverUrl \ 75 | "${GO_EA_SERVER_URL}") 76 | "${RUN_CMD[@]}" 77 | echo "The GoCD Agent exited with code $?. Waiting 10 seconds before re-launching" 78 | sleep 10 79 | done 80 | -------------------------------------------------------------------------------- /docker-files/scripts/clean-containers: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker ps -a -q | xargs --max-procs=10 -I CONTAINER_ID docker stop --time=2 CONTAINER_ID 4 | docker rm $(docker ps -a -q) 5 | -------------------------------------------------------------------------------- /docker-files/scripts/clean-images: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | docker rmi -f $(docker images -q) 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=845952a9d6afa783db70bb3b0effaae45ae5542ca2bb7929619e8af49cb634cf 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /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 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /images/cluster-profiles/basic-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/cluster-profiles/basic-settings.png -------------------------------------------------------------------------------- /images/cluster-profiles/docker-client-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/cluster-profiles/docker-client-settings.png -------------------------------------------------------------------------------- /images/configure-job.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/configure-job.png -------------------------------------------------------------------------------- /images/elastic_profiles_spa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/elastic_profiles_spa.png -------------------------------------------------------------------------------- /images/pipeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/pipeline.png -------------------------------------------------------------------------------- /images/plugin-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/plugin-settings.png -------------------------------------------------------------------------------- /images/profile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/profile.png -------------------------------------------------------------------------------- /images/profiles_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/profiles_page.png -------------------------------------------------------------------------------- /images/quick-edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gocd-contrib/docker-elastic-agents-plugin/06cb80f8e53da99242efb56aa76c44d9f3910074/images/quick-edit.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 = 'docker-elastic-agents' 18 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/AgentInstances.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import cd.go.contrib.elasticagents.docker.requests.CreateAgentRequest; 20 | 21 | /** 22 | * Plugin implementors should implement these methods to interface to your cloud. 23 | * This interface is merely a suggestion for a very simple plugin. You may change it to your needs. 24 | */ 25 | public interface AgentInstances { 26 | /** 27 | * This message is sent to request creation of an agent instance. 28 | * Implementations may, at their discretion choose to not spin up an agent instance. 29 | *

30 | * So that instances created are auto-registered with the server, the agent instance MUST have an 31 | * autoregister.properties file. 32 | * @param request the request object 33 | * @param pluginRequest  the plugin request object 34 | * @param consoleLogAppender appender for console log 35 | */ 36 | T create(CreateAgentRequest request, PluginRequest pluginRequest, ConsoleLogAppender consoleLogAppender) throws Exception; 37 | 38 | /** 39 | * This message is sent when the plugin needs to terminate the agent instance. 40 | * 41 | * @param agentId the elastic agent id 42 | * @param settings the plugin settings object 43 | */ 44 | void terminate(String agentId, PluginSettings settings) throws Exception; 45 | 46 | /** 47 | * This message is sent from the {@link cd.go.contrib.elasticagents.docker.executors.ServerPingRequestExecutor} 48 | * to terminate instances that did not register with the server after a timeout. The timeout may be configurable and 49 | * set via the {@link PluginSettings} instance that is passed in. 50 | * 51 | * @param settings the plugin settings object 52 | * @param agents the list of all the agents 53 | */ 54 | void terminateUnregisteredInstances(PluginSettings settings, Agents agents) throws Exception; 55 | 56 | /** 57 | * This message is sent from the {@link cd.go.contrib.elasticagents.docker.executors.ServerPingRequestExecutor} 58 | * to filter out any new agents, that have registered before the timeout period. The timeout may be configurable and 59 | * set via the {@link PluginSettings} instance that is passed in. 60 | * 61 | * @param settings the plugin settings object 62 | * @param agents the list of all the agents 63 | * @return a list of agent instances which were created after {@link PluginSettings#getAutoRegisterPeriod()} ago. 64 | */ 65 | Agents instancesCreatedAfterTimeout(PluginSettings settings, Agents agents); 66 | 67 | /** 68 | * This message is sent after plugin initialization time so that the plugin may connect to the cloud provider 69 | * and fetch a list of all instances that have been spun up by this plugin (before the server was shut down). 70 | * This call should be should ideally remember if the agent instances are refreshed from the cluster, 71 | * and do nothing if instances were previously refreshed. 72 | * 73 | * @param clusterProfileProperties the list of cluster profile properties 74 | */ 75 | void refreshAll(ClusterProfileProperties clusterProfileProperties) throws Exception; 76 | 77 | /** 78 | * This 79 | * Returns an agent instance with the specified id or null, if the agent is not found. 80 | * 81 | * @param agentId the elastic agent id 82 | */ 83 | T find(String agentId); 84 | } 85 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/Agents.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import java.util.*; 20 | import java.util.function.Predicate; 21 | import java.util.stream.Collectors; 22 | 23 | /** 24 | * Represents a map of {@link Agent#elasticAgentId()} to the {@link Agent} for easy lookups 25 | */ 26 | public class Agents { 27 | 28 | private final Map agents = new HashMap<>(); 29 | 30 | // Filter for agents that can be disabled safely 31 | private static final Predicate AGENT_IDLE_PREDICATE = metadata -> { 32 | Agent.AgentState agentState = metadata.agentState(); 33 | return metadata.configState().equals(Agent.ConfigState.Enabled) && (agentState.equals(Agent.AgentState.Idle) || agentState.equals(Agent.AgentState.Missing) || agentState.equals(Agent.AgentState.LostContact)); 34 | }; 35 | 36 | // Filter for agents that can be terminated safely 37 | private static final Predicate AGENT_DISABLED_PREDICATE = metadata -> { 38 | Agent.AgentState agentState = metadata.agentState(); 39 | return metadata.configState().equals(Agent.ConfigState.Disabled) && (agentState.equals(Agent.AgentState.Idle) || agentState.equals(Agent.AgentState.Missing) || agentState.equals(Agent.AgentState.LostContact)); 40 | }; 41 | 42 | public Agents() { 43 | } 44 | 45 | public Agents(Collection toCopy) { 46 | addAll(toCopy); 47 | } 48 | 49 | public void addAll(Collection toAdd) { 50 | for (Agent agent : toAdd) { 51 | add(agent); 52 | } 53 | } 54 | 55 | public void addAll(Agents agents) { 56 | addAll(agents.agents()); 57 | } 58 | 59 | public Collection findInstancesToDisable() { 60 | return agents.values().stream().filter(AGENT_IDLE_PREDICATE).collect(Collectors.toList()); 61 | } 62 | 63 | public Collection findInstancesToTerminate() { 64 | return agents.values().stream().filter(AGENT_DISABLED_PREDICATE).collect(Collectors.toList()); 65 | } 66 | 67 | public Set agentIds() { 68 | return new LinkedHashSet<>(agents.keySet()); 69 | } 70 | 71 | public boolean containsAgentWithId(String agentId) { 72 | return agents.containsKey(agentId); 73 | } 74 | 75 | public Collection agents() { 76 | return new ArrayList<>(agents.values()); 77 | } 78 | 79 | public void add(Agent agent) { 80 | agents.put(agent.elasticAgentId(), agent); 81 | } 82 | 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/Clock.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 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 = DateTime::new; 26 | 27 | class TestClock implements Clock { 28 | 29 | DateTime time = null; 30 | 31 | public TestClock(DateTime time) { 32 | this.time = time; 33 | } 34 | 35 | public TestClock() { 36 | this(new DateTime()); 37 | } 38 | 39 | @Override 40 | public DateTime now() { 41 | return time; 42 | } 43 | 44 | public TestClock reset() { 45 | time = new DateTime(); 46 | return this; 47 | } 48 | 49 | public TestClock set(DateTime time) { 50 | this.time = time; 51 | return this; 52 | } 53 | 54 | public TestClock rewind(Period period) { 55 | this.time = this.time.minus(period); 56 | return this; 57 | } 58 | 59 | public TestClock forward(Period period) { 60 | this.time = this.time.plus(period); 61 | return this; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/ClusterProfile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 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 | 25 | import java.util.HashMap; 26 | import java.util.Objects; 27 | 28 | public class ClusterProfile { 29 | public static final Gson GSON = new GsonBuilder() 30 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 31 | .excludeFieldsWithoutExposeAnnotation() 32 | .create(); 33 | 34 | @Expose 35 | @SerializedName("id") 36 | private String id; 37 | 38 | @Expose 39 | @SerializedName("plugin_id") 40 | private String pluginId; 41 | 42 | @Expose 43 | @SerializedName("properties") 44 | private ClusterProfileProperties clusterProfileProperties; 45 | 46 | 47 | public ClusterProfile() { 48 | } 49 | 50 | public ClusterProfile(String id, String pluginId, PluginSettings pluginSettings) { 51 | this.id = id; 52 | this.pluginId = pluginId; 53 | setClusterProfileProperties(pluginSettings); 54 | } 55 | 56 | public static ClusterProfile fromJSON(String json) { 57 | return GSON.fromJson(json, ClusterProfile.class); 58 | } 59 | 60 | @Override 61 | public boolean equals(Object o) { 62 | if (this == o) return true; 63 | if (o == null || getClass() != o.getClass()) return false; 64 | ClusterProfile that = (ClusterProfile) o; 65 | return Objects.equals(id, that.id) && 66 | Objects.equals(pluginId, that.pluginId) && 67 | Objects.equals(clusterProfileProperties, that.clusterProfileProperties); 68 | } 69 | 70 | @Override 71 | public int hashCode() { 72 | return Objects.hash(id, pluginId, clusterProfileProperties); 73 | } 74 | 75 | @Override 76 | public String toString() { 77 | return "ClusterProfile{" + 78 | "id='" + id + '\'' + 79 | ", pluginId='" + pluginId + '\'' + 80 | ", clusterProfileProperties=" + clusterProfileProperties + 81 | '}'; 82 | } 83 | 84 | public String getId() { 85 | return id; 86 | } 87 | 88 | public String getPluginId() { 89 | return pluginId; 90 | } 91 | 92 | public ClusterProfileProperties getClusterProfileProperties() { 93 | return clusterProfileProperties; 94 | } 95 | 96 | public void setId(String id) { 97 | this.id = id; 98 | } 99 | 100 | public void setPluginId(String pluginId) { 101 | this.pluginId = pluginId; 102 | } 103 | 104 | public void setClusterProfileProperties(ClusterProfileProperties clusterProfileProperties) { 105 | this.clusterProfileProperties = clusterProfileProperties; 106 | } 107 | 108 | public void setClusterProfileProperties(PluginSettings pluginSettings) { 109 | this.clusterProfileProperties = ClusterProfileProperties.fromConfiguration(GSON.fromJson(GSON.toJson(pluginSettings), HashMap.class)); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/ClusterProfileProperties.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import java.util.Map; 20 | import java.util.Objects; 21 | 22 | public class ClusterProfileProperties extends PluginSettings { 23 | public static ClusterProfileProperties fromJSON(String json) { 24 | return GSON.fromJson(json, ClusterProfileProperties.class); 25 | } 26 | 27 | public static ClusterProfileProperties fromConfiguration(Map clusterProfileProperties) { 28 | return GSON.fromJson(GSON.toJson(clusterProfileProperties), ClusterProfileProperties.class); 29 | } 30 | 31 | public String uuid() { 32 | return Integer.toHexString(Objects.hash(this)); 33 | } 34 | 35 | @Override 36 | public String toString() { 37 | return super.toString(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/ConsoleLogAppender.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import java.util.function.Consumer; 4 | 5 | public interface ConsoleLogAppender extends Consumer { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Thoughtworks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cd.go.contrib.elasticagents.docker; 18 | 19 | import cd.go.contrib.elasticagents.docker.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 PROCESSOR_API_VERSION = "1.0"; 32 | String EXTENSION_API_VERSION = "5.0"; 33 | String CONSOLE_LOG_API_VERSION = "1.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_LIST_AGENTS = REQUEST_SERVER_PREFIX + ".elastic-agents.list-agents"; 43 | String REQUEST_SERVER_SERVER_HEALTH_ADD_MESSAGES = REQUEST_SERVER_PREFIX + ".server-health.add-messages"; 44 | String REQUEST_SERVER_APPEND_TO_CONSOLE_LOG = REQUEST_SERVER_PREFIX + ".console-log.append"; 45 | 46 | // internal use only 47 | String CREATED_BY_LABEL_KEY = "Elastic-Agent-Created-By"; 48 | String JOB_IDENTIFIER_LABEL_KEY = "Elastic-Agent-Job-Identifier"; 49 | String ENVIRONMENT_LABEL_KEY = "Elastic-Agent-Environment-Name"; 50 | String CONFIGURATION_LABEL_KEY = "Elastic-Agent-Configuration"; 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/CpusSpecification.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import org.apache.commons.lang.StringUtils; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /** 9 | * Parse Cpus value, expected format 10 | *

    11 | *
  • float number
  • 12 | *
  • decimal separator: a dot (.)
  • 13 | *
14 | * The "cpus" settings is not allowed by the Spotify docker client yet. 15 | * Once the client supports the parameter, this can be simplified. 16 | * Currently, the "cpus" value is translated as it is written in documentation: 17 | * --cpus="1.5" is equivalent of setting --cpu-period="100000" 18 | * and --cpu-quota="150000". 19 | * 20 | * @see Docker CPU settings. 21 | */ 22 | public class CpusSpecification { 23 | private List errors = new ArrayList<>(); 24 | private Float cpus; 25 | 26 | public CpusSpecification(String cpus) { 27 | this.cpus = parse(cpus, errors); 28 | } 29 | 30 | public Float getCpus() { 31 | return cpus; 32 | } 33 | 34 | public long getCpuPeriod() { 35 | return 100_000l; 36 | } 37 | 38 | public long getCpuQuota() { 39 | return ((long) (getCpus() * getCpuPeriod())); 40 | } 41 | 42 | public List getErrors() { 43 | return errors; 44 | } 45 | 46 | public static Float parse(String cpus, List errors) { 47 | if (StringUtils.isBlank(cpus)) { 48 | return null; 49 | } 50 | 51 | try { 52 | return Float.parseFloat(cpus); 53 | } catch (NumberFormatException e) { 54 | errors.add("Invalid float number: " + cpus); 55 | return null; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/ElasticAgentProfile.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 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 | 25 | import java.util.HashMap; 26 | import java.util.Objects; 27 | 28 | public class ElasticAgentProfile { 29 | public static final Gson GSON = new GsonBuilder() 30 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 31 | .excludeFieldsWithoutExposeAnnotation() 32 | .create(); 33 | 34 | @Expose 35 | @SerializedName("id") 36 | private String id; 37 | 38 | @Expose 39 | @SerializedName("plugin_id") 40 | private String pluginId; 41 | 42 | @Expose 43 | @SerializedName("cluster_profile_id") 44 | private String clusterProfileId; 45 | 46 | @Expose 47 | @SerializedName("properties") 48 | private HashMap properties; 49 | 50 | public static ElasticAgentProfile fromJSON(String json) { 51 | return GSON.fromJson(json, ElasticAgentProfile.class); 52 | } 53 | 54 | public String getId() { 55 | return id; 56 | } 57 | 58 | public String getPluginId() { 59 | return pluginId; 60 | } 61 | 62 | public String getClusterProfileId() { 63 | return clusterProfileId; 64 | } 65 | 66 | public HashMap getProperties() { 67 | return properties; 68 | } 69 | 70 | public void setId(String id) { 71 | this.id = id; 72 | } 73 | 74 | public void setPluginId(String pluginId) { 75 | this.pluginId = pluginId; 76 | } 77 | 78 | public void setClusterProfileId(String clusterProfileId) { 79 | this.clusterProfileId = clusterProfileId; 80 | } 81 | 82 | public void setProperties(HashMap properties) { 83 | this.properties = properties; 84 | } 85 | 86 | @Override 87 | public boolean equals(Object o) { 88 | if (this == o) return true; 89 | if (o == null || getClass() != o.getClass()) return false; 90 | ElasticAgentProfile that = (ElasticAgentProfile) o; 91 | return Objects.equals(id, that.id) && 92 | Objects.equals(pluginId, that.pluginId) && 93 | Objects.equals(clusterProfileId, that.clusterProfileId) && 94 | Objects.equals(properties, that.properties); 95 | } 96 | 97 | @Override 98 | public int hashCode() { 99 | return Objects.hash(id, pluginId, clusterProfileId, properties); 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return "ElasticAgentProfile{" + 105 | "id='" + id + '\'' + 106 | ", pluginId='" + pluginId + '\'' + 107 | ", clusterProfileId='" + clusterProfileId + '\'' + 108 | ", properties=" + properties + 109 | '}'; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/HostMetadata.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import cd.go.contrib.elasticagents.docker.executors.Metadata; 4 | import org.apache.commons.lang.StringUtils; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.apache.commons.lang.StringUtils.isNotBlank; 10 | 11 | public class HostMetadata extends Metadata { 12 | public HostMetadata() { 13 | super("Hosts", false, false); 14 | } 15 | 16 | @Override 17 | protected String doValidate(String input) { 18 | final List errors = new ArrayList<>(); 19 | final String doValidateResult = super.doValidate(input); 20 | 21 | if (isNotBlank(doValidateResult)) { 22 | errors.add(doValidateResult); 23 | } 24 | 25 | errors.addAll(new Hosts(input).getErrors()); 26 | 27 | return errors.isEmpty() ? null : StringUtils.join(errors, ". "); 28 | } 29 | } -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/Hosts.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import cd.go.contrib.elasticagents.docker.utils.Util; 4 | import com.google.common.net.InetAddresses; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collection; 8 | import java.util.List; 9 | 10 | import static java.text.MessageFormat.format; 11 | import static org.apache.commons.lang.StringUtils.trimToEmpty; 12 | 13 | public class Hosts extends ArrayList { 14 | private List errors = new ArrayList<>(); 15 | 16 | public Hosts(String hostConfig) { 17 | final Collection hostEntries = Util.splitIntoLinesAndTrimSpaces(hostConfig); 18 | 19 | for (String hostEntry : hostEntries) { 20 | if (validate(hostEntry)) { 21 | String[] parts = hostEntry.split("\\s+", 2); 22 | add(trimToEmpty(parts[1]) + ":" + trimToEmpty(parts[0])); 23 | } 24 | } 25 | } 26 | 27 | private boolean validate(String hostEntry) { 28 | String[] parts = hostEntry.split("\\s+", 2); 29 | if (parts.length != 2) { 30 | this.errors.add(format("Host entry `{0}` is invalid.", hostEntry)); 31 | return false; 32 | } 33 | 34 | if (validIPAddress(parts[0])) { 35 | return true; 36 | } 37 | 38 | return false; 39 | } 40 | 41 | private boolean validIPAddress(String ipAddress) { 42 | try { 43 | InetAddresses.forString(ipAddress); 44 | return true; 45 | } catch (Exception e) { 46 | this.errors.add(e.getMessage()); 47 | } 48 | 49 | return false; 50 | } 51 | 52 | public List getErrors() { 53 | return errors; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/MemorySpecification.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import com.google.common.collect.ImmutableSortedMap; 4 | import org.apache.commons.lang.StringUtils; 5 | 6 | import java.math.BigDecimal; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.regex.Matcher; 11 | import java.util.regex.Pattern; 12 | 13 | /** 14 | * Parse memory specification. Expected format is: 15 | *
    16 | *
  • a float number
  • 17 | *
  • followed by a letter: M, G, T
  • 18 | *
19 | */ 20 | public class MemorySpecification { 21 | private static final Pattern PATTERN = Pattern.compile("(\\d+(\\.\\d+)?)(\\D)"); 22 | 23 | private static final Map SUFFIXES = ImmutableSortedMap.orderedBy(java.lang.String.CASE_INSENSITIVE_ORDER) 24 | .put("M", BigDecimal.valueOf(1024L * 1024L)) 25 | .put("G", BigDecimal.valueOf(1024L * 1024L * 1024L)) 26 | .put("T", BigDecimal.valueOf(1024L * 1024L * 1024L * 1024L)) 27 | .build(); 28 | 29 | private List errors = new ArrayList<>(); 30 | private Long memory; 31 | 32 | MemorySpecification(String memory) { 33 | this.memory = parse(memory, errors); 34 | } 35 | 36 | Long getMemory() { 37 | return memory; 38 | } 39 | 40 | public static Long parse(String memory, List errors) { 41 | if (StringUtils.isBlank(memory)) { 42 | return null; 43 | } 44 | 45 | final Matcher matcher = PATTERN.matcher(memory); 46 | if (!matcher.matches()) { 47 | errors.add("Invalid size: " + memory); 48 | return null; 49 | } 50 | 51 | final BigDecimal size = new BigDecimal(matcher.group(1)); 52 | final BigDecimal unit = SUFFIXES.get(matcher.group(3)); 53 | if (unit == null) { 54 | errors.add("Invalid size: " + memory + ". Wrong size unit"); 55 | return null; 56 | } 57 | 58 | return size.multiply(unit).longValue(); 59 | } 60 | 61 | public List getErrors() { 62 | return errors; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/Networks.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import static cd.go.contrib.elasticagents.docker.utils.Util.splitIntoLinesAndTrimSpaces; 4 | import static org.apache.commons.lang.StringUtils.isBlank; 5 | 6 | import java.util.Collection; 7 | import java.util.Collections; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | import com.spotify.docker.client.messages.Network; 12 | 13 | public class Networks { 14 | 15 | private Networks() {} 16 | 17 | public static String firstMatching(String networkConfig, List dockerNetworks) { 18 | if (isBlank(networkConfig)) { 19 | return null; 20 | } 21 | 22 | final List networkEntries = splitIntoLinesAndTrimSpaces(networkConfig); 23 | 24 | final Collection missingNetworks = networkEntries 25 | .stream() 26 | .filter(networkEntry -> dockerNetworks.stream().noneMatch(network -> network.name().equals(networkEntry))) 27 | .collect(Collectors.toList()); 28 | 29 | if (!missingNetworks.isEmpty()) { 30 | throw new RuntimeException(String.format("Networks %s do not exist.", missingNetworks)); 31 | } 32 | 33 | return networkEntries.get(0); 34 | } 35 | 36 | public static Collection getAdditionalNetworks(String networkConfig) { 37 | if (isBlank(networkConfig)) { 38 | return Collections.emptyList(); 39 | } 40 | 41 | final List networkEntries = splitIntoLinesAndTrimSpaces(networkConfig); 42 | 43 | return networkEntries.isEmpty() ? networkEntries : networkEntries.subList(1, networkEntries.size()); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/Request.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 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.ELASTIC_AGENT_REQUEST_PREFIX + ".create-agent"), 25 | REQUEST_SERVER_PING(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".server-ping"), 26 | REQUEST_SHOULD_ASSIGN_WORK(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".should-assign-work"), 27 | REQUEST_JOB_COMPLETION(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".job-completion"), 28 | 29 | REQUEST_GET_ELASTIC_AGENT_PROFILE_METADATA(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".get-elastic-agent-profile-metadata"), 30 | REQUEST_VALIDATE_ELASTIC_AGENT_PROFILE(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".validate-elastic-agent-profile"), 31 | REQUEST_GET_ELASTIC_AGENT_PROFILE_VIEW(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".get-elastic-agent-profile-view"), 32 | 33 | REQUEST_GET_ICON(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".get-icon"), 34 | 35 | REQUEST_GET_CLUSTER_PROFILE_METADATA(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".get-cluster-profile-metadata"), 36 | REQUEST_VALIDATE_CLUSTER_PROFILE_CONFIGURATION(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".validate-cluster-profile"), 37 | REQUEST_GET_CLUSTER_PROFILE_VIEW(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".get-cluster-profile-view"), 38 | 39 | REQUEST_CLUSTER_STATUS_REPORT(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".cluster-status-report"), 40 | REQUEST_AGENT_STATUS_REPORT(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".agent-status-report"), 41 | 42 | REQUEST_CAPABILITIES(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".get-capabilities"), 43 | 44 | REQUEST_MIGRATE_CONFIGURATION(Constants.ELASTIC_AGENT_REQUEST_PREFIX + ".migrate-config"), 45 | 46 | REQUEST_UNHANDLED("Unknown Request"); 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 REQUEST_UNHANDLED; 64 | } 65 | 66 | private static class Constants { 67 | public static final String ELASTIC_AGENT_REQUEST_PREFIX = "cd.go.elastic-agent"; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/RequestExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 20 | 21 | public interface RequestExecutor { 22 | 23 | GoPluginApiResponse execute() throws Exception; 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/ServerRequestFailedException.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import com.thoughtworks.go.plugin.api.response.GoApiResponse; 20 | 21 | import static java.lang.String.format; 22 | 23 | public class ServerRequestFailedException extends RuntimeException { 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 appendToConsoleLog(GoApiResponse response) { 49 | return new ServerRequestFailedException(response, "append to console log"); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/SetupSemaphore.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import java.util.Map; 20 | import java.util.concurrent.Semaphore; 21 | 22 | class SetupSemaphore implements Runnable { 23 | private final Integer maxAllowedContainers; 24 | private final Map instances; 25 | private final Semaphore semaphore; 26 | 27 | public SetupSemaphore(Integer maxAllowedContainers, Map instances, Semaphore semaphore) { 28 | this.maxAllowedContainers = maxAllowedContainers; 29 | this.instances = instances; 30 | this.semaphore = semaphore; 31 | } 32 | 33 | @Override 34 | public void run() { 35 | int currentContainers = instances.size(); 36 | int availablePermits = maxAllowedContainers - currentContainers; 37 | if (availablePermits <= 0) { 38 | // no more capacity available. 39 | semaphore.drainPermits(); 40 | } else { 41 | int semaphoreValueDifference = availablePermits - semaphore.availablePermits(); 42 | if (semaphoreValueDifference > 0) { 43 | semaphore.release(semaphoreValueDifference); 44 | } else if (semaphoreValueDifference < 0) { 45 | semaphore.tryAcquire(Math.abs(semaphoreValueDifference)); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/ClusterStatusReportExecutor.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.executors; 2 | 3 | import cd.go.contrib.elasticagents.docker.DockerContainers; 4 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 5 | import cd.go.contrib.elasticagents.docker.models.StatusReport; 6 | import cd.go.contrib.elasticagents.docker.requests.ClusterStatusReportRequest; 7 | import cd.go.contrib.elasticagents.docker.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 | import freemarker.template.Template; 12 | 13 | import static cd.go.contrib.elasticagents.docker.DockerPlugin.LOG; 14 | 15 | public class ClusterStatusReportExecutor implements RequestExecutor { 16 | 17 | private final ClusterStatusReportRequest clusterStatusReportRequest; 18 | private final DockerContainers dockerContainers; 19 | private final ViewBuilder viewBuilder; 20 | 21 | public ClusterStatusReportExecutor(ClusterStatusReportRequest clusterStatusReportRequest, DockerContainers dockerContainers, ViewBuilder viewBuilder) { 22 | this.clusterStatusReportRequest = clusterStatusReportRequest; 23 | this.dockerContainers = dockerContainers; 24 | this.viewBuilder = viewBuilder; 25 | } 26 | 27 | @Override 28 | public GoPluginApiResponse execute() throws Exception { 29 | LOG.info("[status-report] Generating status report"); 30 | 31 | StatusReport statusReport = dockerContainers.getStatusReport(clusterStatusReportRequest.getClusterProfile()); 32 | 33 | final Template template = viewBuilder.getTemplate("status-report.template.ftlh"); 34 | final String statusReportView = viewBuilder.build(template, statusReport); 35 | 36 | JsonObject responseJSON = new JsonObject(); 37 | responseJSON.addProperty("view", statusReportView); 38 | 39 | return DefaultGoPluginApiResponse.success(responseJSON.toString()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/CpusMetadata.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.executors; 2 | 3 | import cd.go.contrib.elasticagents.docker.CpusSpecification; 4 | import org.apache.commons.lang.StringUtils; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.apache.commons.lang.StringUtils.isNotBlank; 10 | 11 | /** 12 | * Accept CPUS value (docker --cpus) and transform it to --cpu-period and --cpu-quota 13 | * parameters until following issue is solved: 14 | *
15 | * 16 | * Support host config cpus 17 | */ 18 | public class CpusMetadata extends Metadata { 19 | public CpusMetadata(String key) { 20 | super(key); 21 | } 22 | 23 | @Override 24 | protected String doValidate(String input) { 25 | final List errors = new ArrayList<>(); 26 | final String doValidateResult = super.doValidate(input); 27 | 28 | if (isNotBlank(doValidateResult)) { 29 | errors.add(doValidateResult); 30 | } 31 | 32 | CpusSpecification.parse(input, errors); 33 | 34 | return errors.isEmpty() ? null : StringUtils.join(errors, ". "); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/CreateAgentRequestExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.*; 20 | import cd.go.contrib.elasticagents.docker.requests.CreateAgentRequest; 21 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.joda.time.DateTime; 24 | import org.joda.time.LocalTime; 25 | import org.joda.time.format.DateTimeFormat; 26 | import org.joda.time.format.DateTimeFormatter; 27 | 28 | public class CreateAgentRequestExecutor implements RequestExecutor { 29 | private static final DateTimeFormatter MESSAGE_PREFIX_FORMATTER = DateTimeFormat.forPattern("'##|'HH:mm:ss.SSS '[go]'"); 30 | private final AgentInstances agentInstances; 31 | private final PluginRequest pluginRequest; 32 | private final CreateAgentRequest request; 33 | 34 | public CreateAgentRequestExecutor(CreateAgentRequest request, AgentInstances agentInstances, PluginRequest pluginRequest) { 35 | this.request = request; 36 | this.agentInstances = agentInstances; 37 | this.pluginRequest = pluginRequest; 38 | } 39 | 40 | @Override 41 | public GoPluginApiResponse execute() throws Exception { 42 | ConsoleLogAppender consoleLogAppender = text -> { 43 | final String message = String.format("%s %s\n", LocalTime.now().toString(MESSAGE_PREFIX_FORMATTER), text); 44 | pluginRequest.appendToConsoleLog(request.jobIdentifier(), message); 45 | }; 46 | 47 | consoleLogAppender.accept(String.format("Received request to create a container of %s at %s", request.dockerImage(), new DateTime().toString("yyyy-MM-dd HH:mm:ss ZZ"))); 48 | 49 | try { 50 | agentInstances.create(request, pluginRequest, consoleLogAppender); 51 | } catch (Exception e) { 52 | consoleLogAppender.accept(String.format("Failed while creating container: %s", e.getMessage())); 53 | throw e; 54 | } 55 | 56 | return new DefaultGoPluginApiResponse(200); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/Field.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | 22 | import java.util.concurrent.atomic.AtomicInteger; 23 | 24 | public class Field { 25 | protected static final AtomicInteger DISPLAY_ORDER_GENERATOR = new AtomicInteger(); 26 | protected final String key; 27 | 28 | @Expose 29 | @SerializedName("display-name") 30 | protected String displayName; 31 | 32 | @Expose 33 | @SerializedName("default-value") 34 | protected String defaultValue; 35 | 36 | @Expose 37 | @SerializedName("required") 38 | protected Boolean required; 39 | 40 | @Expose 41 | @SerializedName("secure") 42 | protected Boolean secure; 43 | 44 | @Expose 45 | @SerializedName("display-order") 46 | protected String displayOrder; 47 | 48 | public Field(String key, String displayName, String defaultValue, Boolean required, Boolean secure, String displayOrder) { 49 | this.key = key; 50 | this.displayName = displayName; 51 | this.defaultValue = defaultValue; 52 | this.required = required; 53 | this.secure = secure; 54 | this.displayOrder = displayOrder; 55 | } 56 | 57 | protected String doValidate(String input) { 58 | return null; 59 | } 60 | 61 | public String key() { 62 | return key; 63 | } 64 | 65 | public static String next() { 66 | return String.valueOf(DISPLAY_ORDER_GENERATOR.getAndIncrement()); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/GetCapabilitiesExecutor.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.executors; 2 | 3 | import cd.go.contrib.elasticagents.docker.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", false); 20 | CAPABILITIES_RESPONSE.put("supports_agent_status_report", true); 21 | CAPABILITIES_RESPONSE.put("supports_cluster_status_report", true); 22 | } 23 | 24 | @Override 25 | public GoPluginApiResponse execute() { 26 | return DefaultGoPluginApiResponse.success(GSON.toJson(CAPABILITIES_RESPONSE)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/GetClusterProfileMetadataExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.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 GetClusterProfileMetadataExecutor implements RequestExecutor { 29 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); 30 | 31 | public static final Metadata GO_SERVER_URL = new GoServerURLMetadata(); 32 | public static final Metadata ENVIRONMENT_VARIABLES = new Metadata("environment_variables", false, false); 33 | public static final Metadata MAX_DOCKER_CONTAINERS = new Metadata("max_docker_containers", true, false); 34 | public static final Metadata DOCKER_URI = new Metadata("docker_uri", true, false); 35 | public static final Metadata AUTO_REGISTER_TIMEOUT = new Metadata("auto_register_timeout", true, false); 36 | public static final Metadata DOCKER_CA_CERT = new Metadata("docker_ca_cert", false, false); 37 | public static final Metadata DOCKER_CLIENT_KEY = new Metadata("docker_client_key", false, true); 38 | public static final Metadata DOCKER_CLIENT_CERT = new Metadata("docker_client_cert", false, false); 39 | public static final Metadata ENABLE_PRIVATE_REGISTRY_AUTHENTICATION = new Metadata("enable_private_registry_authentication", false, false); 40 | public static final Metadata PRIVATE_REGISTRY_SERVER = new Metadata("private_registry_server", false, false); 41 | public static final Metadata PRIVATE_REGISTRY_CUSTOM_CREDENTIALS = new Metadata("private_registry_custom_credentials", false, false); 42 | public static final Metadata PRIVATE_REGISTRY_USERNAME = new Metadata("private_registry_username", false, false); 43 | public static final Metadata PRIVATE_REGISTRY_PASSWORD = new Metadata("private_registry_password", false, true); 44 | public static final Metadata PULL_ON_CONTAINER_CREATE = new Metadata("pull_on_container_create", false, false); 45 | 46 | public static final List FIELDS = new ArrayList<>(); 47 | 48 | static { 49 | FIELDS.add(GO_SERVER_URL); 50 | FIELDS.add(ENVIRONMENT_VARIABLES); 51 | FIELDS.add(MAX_DOCKER_CONTAINERS); 52 | FIELDS.add(DOCKER_URI); 53 | FIELDS.add(AUTO_REGISTER_TIMEOUT); 54 | FIELDS.add(DOCKER_CA_CERT); 55 | FIELDS.add(DOCKER_CLIENT_KEY); 56 | FIELDS.add(DOCKER_CLIENT_CERT); 57 | FIELDS.add(ENABLE_PRIVATE_REGISTRY_AUTHENTICATION); 58 | FIELDS.add(PRIVATE_REGISTRY_SERVER); 59 | FIELDS.add(PRIVATE_REGISTRY_CUSTOM_CREDENTIALS); 60 | FIELDS.add(PRIVATE_REGISTRY_USERNAME); 61 | FIELDS.add(PRIVATE_REGISTRY_PASSWORD); 62 | FIELDS.add(PULL_ON_CONTAINER_CREATE); 63 | } 64 | 65 | @Override 66 | 67 | public GoPluginApiResponse execute() { 68 | return new DefaultGoPluginApiResponse(200, GSON.toJson(FIELDS)); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/GetClusterProfileViewRequestExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 20 | import cd.go.contrib.elasticagents.docker.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 GetClusterProfileViewRequestExecutor implements RequestExecutor { 27 | private static final Gson GSON = new Gson(); 28 | 29 | @Override 30 | public GoPluginApiResponse execute() { 31 | JsonObject jsonObject = new JsonObject(); 32 | jsonObject.addProperty("template", Util.readResource("/plugin-settings.template.html")); 33 | DefaultGoPluginApiResponse defaultGoPluginApiResponse = new DefaultGoPluginApiResponse(200, GSON.toJson(jsonObject)); 34 | return defaultGoPluginApiResponse; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/GetPluginSettingsIconExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 20 | import cd.go.contrib.elasticagents.docker.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 | import org.apache.commons.codec.binary.Base64; 26 | 27 | public class GetPluginSettingsIconExecutor implements RequestExecutor { 28 | private static final Gson GSON = new Gson(); 29 | 30 | @Override 31 | public GoPluginApiResponse execute() { 32 | JsonObject jsonObject = new JsonObject(); 33 | jsonObject.addProperty("content_type", "image/svg+xml"); 34 | jsonObject.addProperty("data", Base64.encodeBase64String(Util.readResourceBytes("/docker-plain.svg"))); 35 | DefaultGoPluginApiResponse defaultGoPluginApiResponse = new DefaultGoPluginApiResponse(200, GSON.toJson(jsonObject)); 36 | return defaultGoPluginApiResponse; 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/GetProfileMetadataExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.HostMetadata; 20 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 21 | import com.google.gson.Gson; 22 | import com.google.gson.GsonBuilder; 23 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 24 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | public class GetProfileMetadataExecutor implements RequestExecutor { 30 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create(); 31 | 32 | public static final Metadata IMAGE = new Metadata("Image", true, false); 33 | public static final Metadata COMMAND = new Metadata("Command", false, false); 34 | public static final Metadata ENVIRONMENT = new Metadata("Environment", false, false); 35 | public static final Metadata RESERVED_MEMORY = new MemoryMetadata("ReservedMemory"); 36 | public static final Metadata MAX_MEMORY = new MemoryMetadata("MaxMemory"); 37 | public static final Metadata CPUS = new CpusMetadata("Cpus"); 38 | public static final Metadata MOUNTS = new Metadata("Mounts"); 39 | public static final Metadata NETWORKS = new Metadata("Networks", false, false); 40 | public static final Metadata HOSTS = new HostMetadata(); 41 | public static final Metadata PRIVILEGED = new Metadata("Privileged", false, false); 42 | 43 | public static final List FIELDS = new ArrayList<>(); 44 | 45 | static { 46 | FIELDS.add(IMAGE); 47 | FIELDS.add(COMMAND); 48 | FIELDS.add(ENVIRONMENT); 49 | FIELDS.add(RESERVED_MEMORY); 50 | FIELDS.add(MAX_MEMORY); 51 | FIELDS.add(CPUS); 52 | FIELDS.add(MOUNTS); 53 | FIELDS.add(NETWORKS); 54 | FIELDS.add(HOSTS); 55 | FIELDS.add(PRIVILEGED); 56 | } 57 | 58 | @Override 59 | 60 | public GoPluginApiResponse execute() { 61 | return new DefaultGoPluginApiResponse(200, GSON.toJson(FIELDS)); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/GetProfileViewExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 20 | import cd.go.contrib.elasticagents.docker.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 GetProfileViewExecutor implements RequestExecutor { 27 | private static final Gson GSON = new Gson(); 28 | 29 | @Override 30 | public GoPluginApiResponse execute() { 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/cd/go/contrib/elasticagents/docker/executors/GoServerURLMetadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import org.apache.http.client.utils.URIBuilder; 20 | 21 | import java.net.URISyntaxException; 22 | import java.util.Arrays; 23 | 24 | import static org.apache.commons.lang.StringUtils.isBlank; 25 | 26 | public class GoServerURLMetadata extends Metadata { 27 | private static String GO_SERVER_URL = "go_server_url"; 28 | private static String GO_SERVER_URL_DISPLAY_VALUE = "Go Server URL"; 29 | 30 | public GoServerURLMetadata() { 31 | super(GO_SERVER_URL, true, false); 32 | } 33 | 34 | @Override 35 | public String doValidate(String input) { 36 | if (isBlank(input)) { 37 | return GO_SERVER_URL_DISPLAY_VALUE + " must not be blank."; 38 | } 39 | 40 | URIBuilder uriBuilder = null; 41 | try { 42 | uriBuilder = new URIBuilder(input); 43 | } catch (URISyntaxException e) { 44 | return GO_SERVER_URL_DISPLAY_VALUE + " must be a valid URL (http://example.com:8153/go)"; 45 | } 46 | 47 | if (isBlank(uriBuilder.getScheme())) { 48 | return GO_SERVER_URL_DISPLAY_VALUE + " must be a valid URL (http://example.com:8153/go)"; 49 | } 50 | 51 | if (!Arrays.asList("http", "https").contains(uriBuilder.getScheme())) { 52 | return GO_SERVER_URL_DISPLAY_VALUE + " must use http or https protocol"; 53 | } 54 | 55 | if (uriBuilder.getHost().equalsIgnoreCase("localhost") || uriBuilder.getHost().equalsIgnoreCase("127.0.0.1")) { 56 | return GO_SERVER_URL_DISPLAY_VALUE + " must not be localhost, since this gets resolved on the agents"; 57 | } 58 | 59 | if (!(uriBuilder.getPath().endsWith("/go") || uriBuilder.getPath().endsWith("/go/"))) { 60 | return GO_SERVER_URL_DISPLAY_VALUE + " must be a valid URL ending with '/go' (http://example.com:8153/go)"; 61 | } 62 | 63 | return null; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/JobCompletionRequestExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Thoughtworks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.*; 20 | import cd.go.contrib.elasticagents.docker.requests.JobCompletionRequest; 21 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | 24 | import java.util.Arrays; 25 | import java.util.List; 26 | 27 | import static cd.go.contrib.elasticagents.docker.DockerPlugin.LOG; 28 | 29 | public class JobCompletionRequestExecutor implements RequestExecutor { 30 | private final JobCompletionRequest jobCompletionRequest; 31 | private final AgentInstances agentInstances; 32 | private final PluginRequest pluginRequest; 33 | 34 | public JobCompletionRequestExecutor(JobCompletionRequest jobCompletionRequest, AgentInstances agentInstances, PluginRequest pluginRequest) { 35 | this.jobCompletionRequest = jobCompletionRequest; 36 | this.agentInstances = agentInstances; 37 | this.pluginRequest = pluginRequest; 38 | } 39 | 40 | @Override 41 | public GoPluginApiResponse execute() throws Exception { 42 | ClusterProfileProperties clusterProfileProperties = jobCompletionRequest.getClusterProfileProperties(); 43 | String elasticAgentId = jobCompletionRequest.getElasticAgentId(); 44 | Agent agent = new Agent(elasticAgentId); 45 | LOG.info("[Job Completion] Terminating elastic agent with id {} on job completion {} in cluster {}.", agent.elasticAgentId(), jobCompletionRequest.jobIdentifier(), clusterProfileProperties); 46 | List agents = Arrays.asList(agent); 47 | pluginRequest.disableAgents(agents); 48 | agentInstances.terminate(agent.elasticAgentId(), clusterProfileProperties); 49 | pluginRequest.deleteAgents(agents); 50 | return DefaultGoPluginApiResponse.success(""); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/MemoryMetadata.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.executors; 2 | 3 | import cd.go.contrib.elasticagents.docker.MemorySpecification; 4 | import org.apache.commons.lang.StringUtils; 5 | 6 | import java.util.ArrayList; 7 | import java.util.List; 8 | 9 | import static org.apache.commons.lang.StringUtils.isNotBlank; 10 | 11 | /** 12 | * Memory specification: number followed by M (megabytes), G (gigabytes), T (terabytes) 13 | */ 14 | public class MemoryMetadata extends Metadata { 15 | MemoryMetadata(String key) { 16 | super(key); 17 | } 18 | 19 | @Override 20 | protected String doValidate(String input) { 21 | final List errors = new ArrayList<>(); 22 | final String doValidateResult = super.doValidate(input); 23 | 24 | if (isNotBlank(doValidateResult)) { 25 | errors.add(doValidateResult); 26 | } 27 | 28 | MemorySpecification.parse(input, errors); 29 | 30 | return errors.isEmpty() ? null : StringUtils.join(errors, ". "); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/Metadata.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import com.google.gson.annotations.Expose; 20 | import com.google.gson.annotations.SerializedName; 21 | import org.apache.commons.lang.StringUtils; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | public class Metadata { 27 | 28 | @Expose 29 | @SerializedName("key") 30 | private String key; 31 | 32 | @Expose 33 | @SerializedName("metadata") 34 | private ProfileMetadata metadata; 35 | 36 | public Metadata(String key, boolean required, boolean secure) { 37 | this(key, new ProfileMetadata(required, secure)); 38 | } 39 | 40 | public Metadata(String key) { 41 | this(key, new ProfileMetadata(false, false)); 42 | } 43 | 44 | public Metadata(String key, ProfileMetadata metadata) { 45 | this.key = key; 46 | this.metadata = metadata; 47 | } 48 | 49 | public Map validate(String input) { 50 | HashMap result = new HashMap<>(); 51 | String validationError = doValidate(input); 52 | if (StringUtils.isNotBlank(validationError)) { 53 | result.put("key", key); 54 | result.put("message", validationError); 55 | } 56 | return result; 57 | } 58 | 59 | protected String doValidate(String input) { 60 | if (isRequired()) { 61 | if (StringUtils.isBlank(input)) { 62 | return this.key + " must not be blank."; 63 | } 64 | } 65 | return null; 66 | } 67 | 68 | 69 | public String getKey() { 70 | return key; 71 | } 72 | 73 | public boolean isRequired() { 74 | return metadata.required; 75 | } 76 | 77 | public static class ProfileMetadata { 78 | @Expose 79 | @SerializedName("required") 80 | private Boolean required; 81 | 82 | @Expose 83 | @SerializedName("secure") 84 | private Boolean secure; 85 | 86 | public ProfileMetadata(boolean required, boolean secure) { 87 | this.required = required; 88 | this.secure = secure; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/NonBlankField.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import org.apache.commons.lang.StringUtils; 20 | 21 | public class NonBlankField extends Field { 22 | 23 | public NonBlankField(String key, String displayName, String defaultValue, Boolean secure, String displayOrder) { 24 | super(key, displayName, defaultValue, true, 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/cd/go/contrib/elasticagents/docker/executors/ProfileValidateRequestExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 20 | import cd.go.contrib.elasticagents.docker.requests.ProfileValidateRequest; 21 | import cd.go.contrib.elasticagents.docker.validator.MemorySettingsProfileValidator; 22 | import cd.go.contrib.elasticagents.docker.validator.ProfileValidator; 23 | import com.google.gson.Gson; 24 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 25 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 26 | 27 | import java.util.*; 28 | 29 | import static cd.go.contrib.elasticagents.docker.executors.GetProfileMetadataExecutor.FIELDS; 30 | 31 | public class ProfileValidateRequestExecutor implements RequestExecutor { 32 | private final ProfileValidateRequest request; 33 | private final List validators = new ArrayList<>(); 34 | private static final Gson GSON = new Gson(); 35 | 36 | public ProfileValidateRequestExecutor(ProfileValidateRequest request) { 37 | this.request = request; 38 | validators.add(new MemorySettingsProfileValidator()); 39 | } 40 | 41 | @Override 42 | public GoPluginApiResponse execute() { 43 | ArrayList> result = new ArrayList<>(); 44 | 45 | List knownFields = new ArrayList<>(); 46 | 47 | for (Metadata field : FIELDS) { 48 | knownFields.add(field.getKey()); 49 | 50 | Map validationError = field.validate(request.getProperties().get(field.getKey())); 51 | 52 | if (!validationError.isEmpty()) { 53 | result.add(validationError); 54 | } 55 | } 56 | 57 | for (ProfileValidator validator : validators) { 58 | Map validationError = validator.validate(request.getProperties()); 59 | if (!validationError.isEmpty()) { 60 | result.add(validationError); 61 | } 62 | } 63 | 64 | Set set = new HashSet<>(request.getProperties().keySet()); 65 | set.removeAll(knownFields); 66 | 67 | if (!set.isEmpty()) { 68 | for (String key : set) { 69 | LinkedHashMap validationError = new LinkedHashMap<>(); 70 | validationError.put("key", key); 71 | validationError.put("message", "Is an unknown property"); 72 | result.add(validationError); 73 | } 74 | } 75 | 76 | return DefaultGoPluginApiResponse.success(GSON.toJson(result)); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/executors/ShouldAssignWorkRequestExecutor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.AgentInstances; 20 | import cd.go.contrib.elasticagents.docker.DockerContainer; 21 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 22 | import cd.go.contrib.elasticagents.docker.requests.ShouldAssignWorkRequest; 23 | import com.thoughtworks.go.plugin.api.response.DefaultGoPluginApiResponse; 24 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 25 | 26 | import static cd.go.contrib.elasticagents.docker.DockerPlugin.LOG; 27 | 28 | public class ShouldAssignWorkRequestExecutor implements RequestExecutor { 29 | private final AgentInstances agentInstances; 30 | private final ShouldAssignWorkRequest request; 31 | 32 | public ShouldAssignWorkRequestExecutor(ShouldAssignWorkRequest request, AgentInstances agentInstances) { 33 | this.request = request; 34 | this.agentInstances = agentInstances; 35 | } 36 | 37 | @Override 38 | public GoPluginApiResponse execute() { 39 | LOG.info(String.format("[Should Assign Work] Processing should assign work request for job %s and agent %s", request.jobIdentifier(), request.agent())); 40 | 41 | DockerContainer instance = agentInstances.find(request.agent().elasticAgentId()); 42 | 43 | if (instance == null) { 44 | return DefaultGoPluginApiResponse.success("false"); 45 | } 46 | 47 | if (instance.getJobIdentifier().equals(request.jobIdentifier())) { 48 | return DefaultGoPluginApiResponse.success("true"); 49 | } 50 | 51 | return DefaultGoPluginApiResponse.success("false"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/models/AgentStatusReport.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.models; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | public class AgentStatusReport { 7 | 8 | private final JobIdentifier jobIdentifier; 9 | private final String elasticAgentId; 10 | private final Long createdAt; 11 | private final String image; 12 | private final String command; 13 | private final String ipAddress; 14 | private final String logs; 15 | private final Map environmentVariables; 16 | private final List hosts; 17 | 18 | public AgentStatusReport(JobIdentifier jobIdentifier, String elasticAgentId, Long createdAt, String image, String command, 19 | String ipAddress, String logs, Map environmentVariables, List hosts) { 20 | this.jobIdentifier = jobIdentifier; 21 | this.elasticAgentId = elasticAgentId; 22 | this.createdAt = createdAt; 23 | this.image = image; 24 | this.command = command; 25 | this.ipAddress = ipAddress; 26 | this.logs = logs; 27 | this.environmentVariables = environmentVariables; 28 | this.hosts = hosts; 29 | } 30 | 31 | public JobIdentifier getJobIdentifier() { 32 | return jobIdentifier; 33 | } 34 | 35 | public String getElasticAgentId() { 36 | return elasticAgentId; 37 | } 38 | 39 | public Long getCreatedAt() { 40 | return createdAt; 41 | } 42 | 43 | public String getImage() { 44 | return image; 45 | } 46 | 47 | public String getCommand() { 48 | return command; 49 | } 50 | 51 | public String getIpAddress() { 52 | return ipAddress; 53 | } 54 | 55 | public String getLogs() { 56 | return logs; 57 | } 58 | 59 | public Map getEnvironmentVariables() { 60 | return environmentVariables; 61 | } 62 | 63 | public List getHosts() { 64 | return hosts; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/models/ContainerStatusReport.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.models; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | public class ContainerStatusReport { 6 | 7 | @Expose 8 | private String id; 9 | @Expose 10 | private String image; 11 | @Expose 12 | private String state; 13 | @Expose 14 | private Long createdAt; 15 | @Expose 16 | private final JobIdentifier jobIdentifier; 17 | @Expose 18 | private final String elasticAgentId; 19 | 20 | public ContainerStatusReport(String id, String image, String state, Long createdAt, JobIdentifier jobIdentifier, String elasticAgentId) { 21 | this.id = id; 22 | this.image = image; 23 | this.state = state; 24 | this.createdAt = createdAt; 25 | this.jobIdentifier = jobIdentifier; 26 | this.elasticAgentId = elasticAgentId; 27 | } 28 | 29 | public String getId() { 30 | return id; 31 | } 32 | 33 | public String getImage() { 34 | return image; 35 | } 36 | 37 | public String getState() { 38 | return state; 39 | } 40 | 41 | public Long getCreatedAt() { 42 | return createdAt; 43 | } 44 | 45 | public JobIdentifier getJobIdentifier() { 46 | return jobIdentifier; 47 | } 48 | 49 | public String getElasticAgentId() { 50 | return elasticAgentId; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/models/ExceptionMessage.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Thoughtworks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cd.go.contrib.elasticagents.docker.models; 18 | 19 | import org.apache.commons.lang.StringUtils; 20 | 21 | import java.io.Closeable; 22 | import java.io.PrintWriter; 23 | import java.io.StringWriter; 24 | 25 | public class ExceptionMessage { 26 | private final Throwable throwable; 27 | private String stacktrace; 28 | private String message; 29 | 30 | public ExceptionMessage(Throwable throwable) { 31 | this.throwable = throwable; 32 | this.stacktrace = initStacktrace(throwable); 33 | this.message = initMessage(throwable); 34 | } 35 | 36 | public String getStacktrace() { 37 | return stacktrace; 38 | } 39 | 40 | public String getMessage() { 41 | return message; 42 | } 43 | 44 | private String initMessage(Throwable throwable) { 45 | if (StringUtils.isNotBlank(throwable.getMessage())) { 46 | return throwable.getMessage(); 47 | } 48 | 49 | if (StringUtils.isNotBlank(this.stacktrace)) { 50 | return stacktrace.split("\n")[0]; 51 | } 52 | 53 | return null; 54 | } 55 | 56 | private String initStacktrace(Throwable throwable) { 57 | StringWriter sw = new StringWriter(); 58 | PrintWriter pw = new PrintWriter(sw); 59 | try { 60 | throwable.printStackTrace(pw); 61 | String stacktrace = sw.toString(); 62 | return stacktrace; 63 | } finally { 64 | closeQuietly(sw); 65 | closeQuietly(pw); 66 | } 67 | } 68 | 69 | private void closeQuietly(Closeable closeable) { 70 | try { 71 | closeable.close(); 72 | } catch (Exception e) { 73 | //Ignore 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/models/NotRunningAgentStatusReport.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.models; 2 | 3 | public class NotRunningAgentStatusReport { 4 | private final String entity; 5 | 6 | public NotRunningAgentStatusReport(JobIdentifier jobIdentifier) { 7 | this.entity = String.format("Job Identifier: %s", jobIdentifier.represent()); 8 | } 9 | 10 | public NotRunningAgentStatusReport(String elasticAgentId) { 11 | this.entity = String.format("Elastic Agent ID: %s", elasticAgentId); 12 | } 13 | 14 | public String getEntity() { 15 | return entity; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/models/StatusReport.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.models; 2 | 3 | import com.google.gson.annotations.Expose; 4 | 5 | import java.util.List; 6 | 7 | public class StatusReport { 8 | 9 | @Expose 10 | private final Integer cpus; 11 | @Expose 12 | private final String memory; 13 | @Expose 14 | private final String os; 15 | @Expose 16 | private final String architecture; 17 | @Expose 18 | private final String dockerVersion; 19 | @Expose 20 | private final List containerStatusReports; 21 | 22 | public StatusReport(String os, String architecture, String dockerVersion, Integer cpus, String memory, 23 | List containerStatusReports) { 24 | this.os = os; 25 | this.architecture = architecture; 26 | this.dockerVersion = dockerVersion; 27 | this.cpus = cpus; 28 | this.memory = memory; 29 | this.containerStatusReports = containerStatusReports; 30 | } 31 | 32 | public Integer getCpus() { 33 | return cpus; 34 | } 35 | 36 | public String getMemory() { 37 | return memory; 38 | } 39 | 40 | public String getOs() { 41 | return os; 42 | } 43 | 44 | public String getArchitecture() { 45 | return architecture; 46 | } 47 | 48 | public String getDockerVersion() { 49 | return dockerVersion; 50 | } 51 | 52 | public List getContainerStatusReports() { 53 | return containerStatusReports; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/requests/AgentStatusReportRequest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.requests; 2 | 3 | import cd.go.contrib.elasticagents.docker.ClusterProfileProperties; 4 | import cd.go.contrib.elasticagents.docker.DockerContainers; 5 | import cd.go.contrib.elasticagents.docker.PluginRequest; 6 | import cd.go.contrib.elasticagents.docker.executors.AgentStatusReportExecutor; 7 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 8 | import cd.go.contrib.elasticagents.docker.views.ViewBuilder; 9 | import com.google.gson.FieldNamingPolicy; 10 | import com.google.gson.Gson; 11 | import com.google.gson.GsonBuilder; 12 | import com.google.gson.annotations.Expose; 13 | import com.google.gson.annotations.SerializedName; 14 | 15 | import java.io.IOException; 16 | import java.util.Map; 17 | import java.util.Objects; 18 | 19 | public class AgentStatusReportRequest { 20 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() 21 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 22 | .create(); 23 | 24 | @Expose 25 | private String elasticAgentId; 26 | 27 | @Expose 28 | private JobIdentifier jobIdentifier; 29 | 30 | @Expose 31 | @SerializedName("cluster_profile_properties") 32 | private ClusterProfileProperties clusterProfile; 33 | 34 | public AgentStatusReportRequest() { 35 | } 36 | 37 | public AgentStatusReportRequest(String elasticAgentId, JobIdentifier jobIdentifier, Map clusterProfileProperties) { 38 | this.elasticAgentId = elasticAgentId; 39 | this.jobIdentifier = jobIdentifier; 40 | this.clusterProfile = ClusterProfileProperties.fromConfiguration(clusterProfileProperties); 41 | } 42 | 43 | public static AgentStatusReportRequest fromJSON(String json) { 44 | return GSON.fromJson(json, AgentStatusReportRequest.class); 45 | } 46 | 47 | public String getElasticAgentId() { 48 | return elasticAgentId; 49 | } 50 | 51 | public JobIdentifier getJobIdentifier() { 52 | return jobIdentifier; 53 | } 54 | 55 | public AgentStatusReportExecutor executor(PluginRequest pluginRequest, DockerContainers dockerContainers) { 56 | return new AgentStatusReportExecutor(this, pluginRequest, dockerContainers, ViewBuilder.instance()); 57 | } 58 | 59 | public ClusterProfileProperties getClusterProfile() { 60 | return clusterProfile; 61 | } 62 | 63 | @Override 64 | public boolean equals(Object o) { 65 | if (this == o) return true; 66 | if (o == null || getClass() != o.getClass()) return false; 67 | AgentStatusReportRequest that = (AgentStatusReportRequest) o; 68 | return Objects.equals(elasticAgentId, that.elasticAgentId) && 69 | Objects.equals(jobIdentifier, that.jobIdentifier) && 70 | Objects.equals(clusterProfile, that.clusterProfile); 71 | } 72 | 73 | @Override 74 | public int hashCode() { 75 | return Objects.hash(elasticAgentId, jobIdentifier, clusterProfile); 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | return "AgentStatusReportRequest{" + 81 | "elasticAgentId='" + elasticAgentId + '\'' + 82 | ", jobIdentifier=" + jobIdentifier + 83 | ", clusterProfile=" + clusterProfile + 84 | '}'; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/requests/ClusterProfileValidateRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 20 | import cd.go.contrib.elasticagents.docker.executors.ClusterProfileValidateRequestExecutor; 21 | import com.google.gson.Gson; 22 | import com.google.gson.reflect.TypeToken; 23 | 24 | import java.lang.reflect.Type; 25 | import java.util.Map; 26 | 27 | public class ClusterProfileValidateRequest { 28 | 29 | private static final Gson GSON = new Gson(); 30 | private Map properties; 31 | 32 | public ClusterProfileValidateRequest(Map properties) { 33 | this.properties = properties; 34 | } 35 | 36 | public Map getProperties() { 37 | return properties; 38 | } 39 | 40 | public static ClusterProfileValidateRequest fromJSON(String json) { 41 | final Type type = new TypeToken>() { 42 | }.getType(); 43 | final Map properties = GSON.fromJson(json, type); 44 | return new ClusterProfileValidateRequest(properties); 45 | } 46 | 47 | public RequestExecutor executor() { 48 | return new ClusterProfileValidateRequestExecutor(this); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/requests/ClusterStatusReportRequest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.requests; 2 | 3 | import cd.go.contrib.elasticagents.docker.ClusterProfileProperties; 4 | import cd.go.contrib.elasticagents.docker.DockerContainers; 5 | import cd.go.contrib.elasticagents.docker.executors.ClusterStatusReportExecutor; 6 | import cd.go.contrib.elasticagents.docker.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(DockerContainers dockerContainers) { 42 | return new ClusterStatusReportExecutor(this, dockerContainers, 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/java/cd/go/contrib/elasticagents/docker/requests/CreateAgentRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.*; 20 | import cd.go.contrib.elasticagents.docker.executors.CreateAgentRequestExecutor; 21 | import cd.go.contrib.elasticagents.docker.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.ArrayList; 27 | import java.util.Collection; 28 | import java.util.Map; 29 | 30 | import static org.apache.commons.lang.StringUtils.isNotBlank; 31 | 32 | public class CreateAgentRequest { 33 | private static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); 34 | private String autoRegisterKey; 35 | private Map elasticAgentProfileProperties; 36 | private String environment; 37 | private JobIdentifier jobIdentifier; 38 | private ClusterProfileProperties clusterProfileProperties; 39 | 40 | public CreateAgentRequest() { 41 | } 42 | 43 | public CreateAgentRequest(String autoRegisterKey, Map elasticAgentProfileProperties, String environment, JobIdentifier jobIdentifier, Map clusterProfileProperties) { 44 | this.autoRegisterKey = autoRegisterKey; 45 | this.elasticAgentProfileProperties = elasticAgentProfileProperties; 46 | this.environment = environment; 47 | this.jobIdentifier = jobIdentifier; 48 | this.clusterProfileProperties = ClusterProfileProperties.fromConfiguration(clusterProfileProperties); 49 | } 50 | 51 | public CreateAgentRequest(String autoRegisterKey, Map elasticAgentProfileProperties, String environment, JobIdentifier jobIdentifier, ClusterProfileProperties clusterProfileProperties) { 52 | this.autoRegisterKey = autoRegisterKey; 53 | this.elasticAgentProfileProperties = elasticAgentProfileProperties; 54 | this.environment = environment; 55 | this.jobIdentifier = jobIdentifier; 56 | this.clusterProfileProperties = clusterProfileProperties; 57 | } 58 | 59 | String autoRegisterKey() { 60 | return autoRegisterKey; 61 | } 62 | 63 | public Map properties() { 64 | return elasticAgentProfileProperties; 65 | } 66 | 67 | public ClusterProfileProperties getClusterProfileProperties() { 68 | return clusterProfileProperties; 69 | } 70 | 71 | public String environment() { 72 | return environment; 73 | } 74 | 75 | public JobIdentifier jobIdentifier() { 76 | return jobIdentifier; 77 | } 78 | 79 | public static CreateAgentRequest fromJSON(String json) { 80 | return GSON.fromJson(json, CreateAgentRequest.class); 81 | } 82 | 83 | public RequestExecutor executor(AgentInstances agentInstances, PluginRequest pluginRequest) { 84 | return new CreateAgentRequestExecutor(this, agentInstances, pluginRequest); 85 | } 86 | 87 | public Collection autoregisterPropertiesAsEnvironmentVars(String elasticAgentId) { 88 | ArrayList vars = new ArrayList<>(); 89 | if (isNotBlank(autoRegisterKey)) { 90 | vars.add("GO_EA_AUTO_REGISTER_KEY=" + autoRegisterKey); 91 | } 92 | if (isNotBlank(environment)) { 93 | vars.add("GO_EA_AUTO_REGISTER_ENVIRONMENT=" + environment); 94 | } 95 | vars.add("GO_EA_AUTO_REGISTER_ELASTIC_AGENT_ID=" + elasticAgentId); 96 | vars.add("GO_EA_AUTO_REGISTER_ELASTIC_PLUGIN_ID=" + Constants.PLUGIN_ID); 97 | return vars; 98 | } 99 | 100 | public String dockerImage() { 101 | return properties().get("Image"); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/requests/JobCompletionRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | 20 | import cd.go.contrib.elasticagents.docker.*; 21 | import cd.go.contrib.elasticagents.docker.executors.JobCompletionRequestExecutor; 22 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 23 | import com.google.gson.FieldNamingPolicy; 24 | import com.google.gson.Gson; 25 | import com.google.gson.GsonBuilder; 26 | import com.google.gson.annotations.Expose; 27 | import com.google.gson.annotations.SerializedName; 28 | 29 | import java.util.Map; 30 | 31 | public class JobCompletionRequest { 32 | 33 | private static final Gson GSON = new GsonBuilder().excludeFieldsWithoutExposeAnnotation() 34 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 35 | .create(); 36 | @Expose 37 | @SerializedName("elastic_agent_id") 38 | private String elasticAgentId; 39 | 40 | @Expose 41 | @SerializedName("job_identifier") 42 | private JobIdentifier jobIdentifier; 43 | 44 | @Expose 45 | @SerializedName("elastic_agent_profile_properties") 46 | private Map properties; 47 | 48 | @Expose 49 | @SerializedName("cluster_profile_properties") 50 | private ClusterProfileProperties clusterProfileProperties; 51 | 52 | public JobCompletionRequest() { 53 | } 54 | 55 | public JobCompletionRequest(String elasticAgentId, JobIdentifier jobIdentifier, Map properties, Map clusterProfile) { 56 | this.elasticAgentId = elasticAgentId; 57 | this.jobIdentifier = jobIdentifier; 58 | this.properties = properties; 59 | this.clusterProfileProperties = ClusterProfileProperties.fromConfiguration(clusterProfile); 60 | } 61 | 62 | public JobCompletionRequest(String elasticAgentId, JobIdentifier jobIdentifier, Map properties, ClusterProfileProperties clusterProfileProperties) { 63 | this.elasticAgentId = elasticAgentId; 64 | this.jobIdentifier = jobIdentifier; 65 | this.properties = properties; 66 | this.clusterProfileProperties = clusterProfileProperties; 67 | } 68 | 69 | public static JobCompletionRequest fromJSON(String json) { 70 | JobCompletionRequest jobCompletionRequest = GSON.fromJson(json, JobCompletionRequest.class); 71 | return jobCompletionRequest; 72 | } 73 | 74 | public String getElasticAgentId() { 75 | return elasticAgentId; 76 | } 77 | 78 | public JobIdentifier jobIdentifier() { 79 | return jobIdentifier; 80 | } 81 | 82 | public ClusterProfileProperties getClusterProfileProperties() { 83 | return clusterProfileProperties; 84 | } 85 | 86 | public Map getProperties() { 87 | return properties; 88 | } 89 | 90 | public RequestExecutor executor(AgentInstances agentInstances, PluginRequest pluginRequest) { 91 | return new JobCompletionRequestExecutor(this, agentInstances, pluginRequest); 92 | } 93 | 94 | @Override 95 | public String toString() { 96 | return "JobCompletionRequest{" + 97 | "elasticAgentId='" + elasticAgentId + '\'' + 98 | ", jobIdentifier=" + jobIdentifier + 99 | ", properties=" + properties + 100 | ", clusterProfileProperties=" + clusterProfileProperties + 101 | '}'; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/requests/ProfileValidateRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.RequestExecutor; 20 | import cd.go.contrib.elasticagents.docker.executors.ProfileValidateRequestExecutor; 21 | import com.google.gson.Gson; 22 | import com.google.gson.reflect.TypeToken; 23 | 24 | import java.lang.reflect.Type; 25 | import java.util.Map; 26 | 27 | public class ProfileValidateRequest { 28 | 29 | private static final Gson GSON = new Gson(); 30 | private Map properties; 31 | 32 | public ProfileValidateRequest(Map properties) { 33 | this.properties = properties; 34 | } 35 | 36 | 37 | public Map getProperties() { 38 | return properties; 39 | } 40 | 41 | public static ProfileValidateRequest fromJSON(String json) { 42 | final Type type = new TypeToken>() { 43 | }.getType(); 44 | final Map properties = GSON.fromJson(json, type); 45 | return new ProfileValidateRequest(properties); 46 | } 47 | 48 | public RequestExecutor executor() { 49 | return new ProfileValidateRequestExecutor(this); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/requests/ServerPingRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.ClusterProfileProperties; 20 | import cd.go.contrib.elasticagents.docker.DockerContainers; 21 | import cd.go.contrib.elasticagents.docker.PluginRequest; 22 | import cd.go.contrib.elasticagents.docker.executors.ServerPingRequestExecutor; 23 | import com.google.gson.FieldNamingPolicy; 24 | import com.google.gson.Gson; 25 | import com.google.gson.GsonBuilder; 26 | 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.Objects; 31 | import java.util.stream.Collectors; 32 | 33 | public class ServerPingRequest { 34 | private static final Gson GSON = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); 35 | private List allClusterProfileProperties = new ArrayList<>(); 36 | 37 | public ServerPingRequest() { 38 | } 39 | 40 | public ServerPingRequest(List> allClusterProfileProperties) { 41 | this.allClusterProfileProperties = allClusterProfileProperties.stream() 42 | .map(ClusterProfileProperties::fromConfiguration) 43 | .collect(Collectors.toList()); 44 | } 45 | 46 | public List allClusterProfileProperties() { 47 | return allClusterProfileProperties; 48 | } 49 | 50 | public static ServerPingRequest fromJSON(String json) { 51 | return GSON.fromJson(json, ServerPingRequest.class); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "ServerPingRequest{" + 57 | "allClusterProfileProperties=" + allClusterProfileProperties + 58 | '}'; 59 | } 60 | 61 | @Override 62 | public boolean equals(Object o) { 63 | if (this == o) return true; 64 | if (o == null || getClass() != o.getClass()) return false; 65 | ServerPingRequest that = (ServerPingRequest) o; 66 | return Objects.equals(allClusterProfileProperties, that.allClusterProfileProperties); 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | return Objects.hash(allClusterProfileProperties); 72 | } 73 | 74 | public ServerPingRequestExecutor executor(Map clusterSpecificAgentInstances, PluginRequest pluginRequest) { 75 | return new ServerPingRequestExecutor(this, clusterSpecificAgentInstances, pluginRequest); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/requests/ShouldAssignWorkRequest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.*; 20 | import cd.go.contrib.elasticagents.docker.executors.ShouldAssignWorkRequestExecutor; 21 | import cd.go.contrib.elasticagents.docker.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 cd.go.contrib.elasticagents.docker.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 properties; 37 | private ClusterProfileProperties clusterProfileProperties; 38 | 39 | public ShouldAssignWorkRequest(Agent agent, String environment, JobIdentifier jobIdentifier, Map properties, Map clusterProfileProperties) { 40 | this.agent = agent; 41 | this.environment = environment; 42 | this.jobIdentifier = jobIdentifier; 43 | this.properties = properties; 44 | this.clusterProfileProperties = ClusterProfileProperties.fromConfiguration(clusterProfileProperties); 45 | } 46 | 47 | public ShouldAssignWorkRequest() { 48 | } 49 | 50 | public Agent agent() { 51 | return agent; 52 | } 53 | 54 | public String environment() { 55 | return environment; 56 | } 57 | 58 | public JobIdentifier jobIdentifier() { 59 | return jobIdentifier; 60 | } 61 | 62 | public Map properties() { 63 | return properties; 64 | } 65 | 66 | public ClusterProfileProperties getClusterProfileProperties() { 67 | return clusterProfileProperties; 68 | } 69 | 70 | public static ShouldAssignWorkRequest fromJSON(String json) { 71 | return GSON.fromJson(json, ShouldAssignWorkRequest.class); 72 | } 73 | 74 | public RequestExecutor executor(AgentInstances agentInstances) { 75 | return new ShouldAssignWorkRequestExecutor(this, agentInstances); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/utils/Util.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.utils; 18 | 19 | import cd.go.contrib.elasticagents.docker.executors.GetClusterProfileViewRequestExecutor; 20 | import com.google.common.collect.Collections2; 21 | import com.google.common.io.ByteStreams; 22 | import com.google.common.io.CharStreams; 23 | 24 | import java.io.IOException; 25 | import java.io.InputStream; 26 | import java.io.InputStreamReader; 27 | import java.io.StringReader; 28 | import java.nio.charset.StandardCharsets; 29 | import java.text.DecimalFormat; 30 | import java.util.*; 31 | import java.util.stream.Collectors; 32 | 33 | import static org.apache.commons.lang.StringUtils.isBlank; 34 | 35 | public class Util { 36 | public static String readResource(String resourceFile) { 37 | try (InputStreamReader reader = new InputStreamReader(Util.class.getResourceAsStream(resourceFile), StandardCharsets.UTF_8)) { 38 | return CharStreams.toString(reader); 39 | } catch (IOException e) { 40 | throw new RuntimeException("Could not find resource " + resourceFile, e); 41 | } 42 | } 43 | 44 | public static byte[] readResourceBytes(String resourceFile) { 45 | try (InputStream in = GetClusterProfileViewRequestExecutor.class.getResourceAsStream(resourceFile)) { 46 | return ByteStreams.toByteArray(in); 47 | } catch (IOException e) { 48 | throw new RuntimeException("Could not find resource " + resourceFile, e); 49 | } 50 | } 51 | 52 | public static String pluginId() { 53 | String s = readResource("/plugin.properties"); 54 | try { 55 | Properties properties = new Properties(); 56 | properties.load(new StringReader(s)); 57 | return (String) properties.get("id"); 58 | } catch (IOException e) { 59 | throw new RuntimeException(e); 60 | } 61 | } 62 | 63 | public static List splitIntoLinesAndTrimSpaces(String lines) { 64 | if (isBlank(lines)) { 65 | return Collections.emptyList(); 66 | } 67 | 68 | return Arrays.stream(lines.split("[\r\n]+")).map(String::trim).collect(Collectors.toList()); 69 | } 70 | 71 | public static String readableSize(long size) { 72 | if (size <= 0) return "0"; 73 | final String[] units = new String[]{"B", "KB", "MB", "GB", "TB", "PB", "EB"}; 74 | int digitGroups = (int) (Math.log10(size) / Math.log10(1024)); 75 | return new DecimalFormat("#,##0.##").format(size / Math.pow(1024, digitGroups)) + " " + units[digitGroups]; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/validator/MemorySettingsProfileValidator.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.validator; 2 | 3 | import cd.go.contrib.elasticagents.docker.MemorySpecification; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | import static cd.go.contrib.elasticagents.docker.executors.GetProfileMetadataExecutor.MAX_MEMORY; 11 | import static cd.go.contrib.elasticagents.docker.executors.GetProfileMetadataExecutor.RESERVED_MEMORY; 12 | 13 | /** 14 | * Check that max memory is not lower than reserved memory. 15 | */ 16 | public class MemorySettingsProfileValidator implements ProfileValidator { 17 | @Override 18 | public Map validate(Map elasticProfile) { 19 | final List fieldErrors = new ArrayList<>(); 20 | String maxMemoryInput = elasticProfile.get(MAX_MEMORY.getKey()); 21 | String reservedMemoryInput = elasticProfile.get(RESERVED_MEMORY.getKey()); 22 | 23 | Long maxMemory = MemorySpecification.parse(maxMemoryInput, fieldErrors); 24 | Long reservedMemory = MemorySpecification.parse(reservedMemoryInput, fieldErrors); 25 | 26 | Map errors = new HashMap<>(); 27 | 28 | if (fieldErrors.isEmpty()) { 29 | if (maxMemory != null && maxMemory < 4 * 1024 * 1024) { 30 | // this is docker limitation, 31 | // see https://docs.docker.com/config/containers/resource_constraints/#memory 32 | addError(errors, MAX_MEMORY.getKey(), "Minimum allowed value is 4M"); 33 | } else if (maxMemory != null && reservedMemory != null && maxMemory < reservedMemory) { 34 | addError(errors, MAX_MEMORY.getKey(), "Max memory is lower than reserved memory"); 35 | } 36 | } 37 | 38 | return errors; 39 | } 40 | 41 | private void addError(Map errors, String key, String message) { 42 | errors.put("key", key); 43 | errors.put("message", message); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/validator/ProfileValidator.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.validator; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * May validate relationships among profile fields. 7 | */ 8 | public interface ProfileValidator { 9 | /** 10 | * Do a validation. 11 | * 12 | * @param elasticProfile map of all profile fields and theirs values 13 | * @return map of errors 14 | */ 15 | Map validate(Map elasticProfile); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/cd/go/contrib/elasticagents/docker/views/ViewBuilder.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.views; 2 | 3 | import freemarker.cache.ClassTemplateLoader; 4 | import freemarker.template.Configuration; 5 | import freemarker.template.Template; 6 | import freemarker.template.TemplateException; 7 | import freemarker.template.TemplateExceptionHandler; 8 | 9 | import java.io.IOException; 10 | import java.io.StringWriter; 11 | import java.io.Writer; 12 | 13 | public class ViewBuilder { 14 | private static ViewBuilder builder; 15 | private final Configuration configuration; 16 | 17 | private ViewBuilder() { 18 | configuration = new Configuration(Configuration.VERSION_2_3_23); 19 | configuration.setTemplateLoader(new ClassTemplateLoader(getClass(), "/")); 20 | configuration.setDefaultEncoding("UTF-8"); 21 | configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); 22 | configuration.setLogTemplateExceptions(false); 23 | configuration.setDateTimeFormat("iso"); 24 | } 25 | 26 | public Template getTemplate(String template) throws IOException { 27 | return configuration.getTemplate(template); 28 | } 29 | 30 | public String build(Template template, Object model) throws IOException, TemplateException { 31 | Writer writer = new StringWriter(); 32 | template.process(model, writer); 33 | return writer.toString(); 34 | } 35 | 36 | public static ViewBuilder instance() { 37 | if (builder == null) { 38 | builder = new ViewBuilder(); 39 | } 40 | return builder; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/resources/docker-plain.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/main/resources/error.template.ftlh: -------------------------------------------------------------------------------- 1 | 50 | 51 |
52 |
53 |
54 |
55 |
56 | Error while generating status report: ${ message! } 57 |
58 |
59 | 60 |
61 |
62 |
63 |
64 |
65 | -------------------------------------------------------------------------------- /src/main/resources/not-running-agent-status-report.template.ftlh: -------------------------------------------------------------------------------- 1 | 41 | 42 |
43 |
44 |
45 |
46 |
47 |
48 | No Running container found for specified ${ entity !}. 49 |
50 |

51 | Either the container is starting or has been terminated. 52 |

53 |
54 |
55 |
56 |
57 |
58 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/AgentTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 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 | public class AgentTest { 31 | 32 | @Test 33 | public void shouldSerializeToJSON() throws Exception { 34 | Agent agent = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 35 | String agentsJSON = Agent.toJSONArray(Arrays.asList(agent)); 36 | 37 | JSONAssert.assertEquals("[{\"agent_id\":\"eeb9e0eb-1f12-4366-a5a5-59011810273b\",\"agent_state\":\"Building\",\"build_state\":\"Cancelled\",\"config_state\":\"Disabled\"}]", agentsJSON, true); 38 | } 39 | 40 | @Test 41 | public void shouldDeserializeFromJSON() throws Exception { 42 | List agents = Agent.fromJSONArray("[{\"agent_id\":\"eeb9e0eb-1f12-4366-a5a5-59011810273b\",\"agent_state\":\"Building\",\"build_state\":\"Cancelled\",\"config_state\":\"Disabled\"}]"); 43 | assertThat(agents, hasSize(1)); 44 | 45 | Agent agent = agents.get(0); 46 | 47 | assertThat(agent.elasticAgentId(), is("eeb9e0eb-1f12-4366-a5a5-59011810273b")); 48 | assertThat(agent.agentState(), is(Agent.AgentState.Building)); 49 | assertThat(agent.buildState(), is(Agent.BuildState.Cancelled)); 50 | assertThat(agent.configState(), is(Agent.ConfigState.Disabled)); 51 | } 52 | 53 | @Test 54 | public void agentsWithSameAttributesShouldBeEqual() throws Exception { 55 | Agent agent1 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 56 | Agent agent2 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 57 | 58 | assertTrue(agent1.equals(agent2)); 59 | } 60 | 61 | @Test 62 | public void agentShouldEqualItself() throws Exception { 63 | Agent agent = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 64 | 65 | assertTrue(agent.equals(agent)); 66 | } 67 | 68 | @Test 69 | public void agentShouldNotEqualAnotherAgentWithDifferentAttributes() throws Exception { 70 | Agent agent = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 71 | 72 | assertFalse(agent.equals(new Agent())); 73 | } 74 | 75 | @Test 76 | public void agentsWithSameAttributesShareSameHashCode() throws Exception { 77 | Agent agent1 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 78 | Agent agent2 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 79 | 80 | assertThat(agent1.hashCode(), equalTo(agent2.hashCode())); 81 | } 82 | 83 | @Test 84 | public void agentsWithDifferentAttributesDoNotShareSameHashCode() throws Exception { 85 | Agent agent1 = new Agent("eeb9e0eb-1f12-4366-a5a5-59011810273b", Agent.AgentState.Building, Agent.BuildState.Cancelled, Agent.ConfigState.Disabled); 86 | Agent agent2 = new Agent(); 87 | 88 | assertThat(agent1.hashCode(), not(equalTo(agent2.hashCode()))); 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/BaseTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import com.spotify.docker.client.DefaultDockerClient; 20 | import com.spotify.docker.client.DockerCertificates; 21 | import com.spotify.docker.client.DockerClient.ListVolumesParam; 22 | import com.spotify.docker.client.exceptions.ContainerNotFoundException; 23 | import com.spotify.docker.client.exceptions.DockerException; 24 | import com.spotify.docker.client.exceptions.VolumeNotFoundException; 25 | 26 | import org.junit.jupiter.api.AfterAll; 27 | import org.junit.jupiter.api.BeforeAll; 28 | 29 | import java.io.IOException; 30 | import java.nio.charset.StandardCharsets; 31 | import java.nio.file.Files; 32 | import java.nio.file.Paths; 33 | import java.util.HashSet; 34 | 35 | import static java.lang.System.getenv; 36 | import static org.assertj.core.api.Assertions.assertThatThrownBy; 37 | import static org.junit.jupiter.api.Assertions.assertNotNull; 38 | 39 | public abstract class BaseTest { 40 | 41 | protected static DefaultDockerClient.Builder builder; 42 | protected static DefaultDockerClient docker; 43 | protected static HashSet containers; 44 | 45 | @BeforeAll 46 | public static void beforeClass() throws Exception { 47 | builder = DefaultDockerClient.fromEnv(); 48 | docker = builder.build(); 49 | containers = new HashSet<>(); 50 | } 51 | 52 | @AfterAll 53 | public static void afterClass() throws Exception { 54 | for (String container : containers) { 55 | try { 56 | docker.inspectContainer(container); 57 | docker.stopContainer(container, 2); 58 | docker.removeContainer(container); 59 | } catch (ContainerNotFoundException ignore) { 60 | 61 | } 62 | } 63 | } 64 | 65 | protected ClusterProfileProperties createClusterProfiles() throws IOException { 66 | ClusterProfileProperties settings = new ClusterProfileProperties(); 67 | 68 | settings.setMaxDockerContainers(1); 69 | settings.setDockerURI(builder.uri().toString()); 70 | if (settings.getDockerURI().startsWith("https://")) { 71 | settings.setDockerCACert(Files.readString(Paths.get(getenv("DOCKER_CERT_PATH"), DockerCertificates.DEFAULT_CA_CERT_NAME), StandardCharsets.UTF_8)); 72 | settings.setDockerClientCert(Files.readString(Paths.get(getenv("DOCKER_CERT_PATH"), DockerCertificates.DEFAULT_CLIENT_CERT_NAME), StandardCharsets.UTF_8)); 73 | settings.setDockerClientKey(Files.readString(Paths.get(getenv("DOCKER_CERT_PATH"), DockerCertificates.DEFAULT_CLIENT_KEY_NAME), StandardCharsets.UTF_8)); 74 | } 75 | 76 | return settings; 77 | } 78 | 79 | protected void assertContainerDoesNotExist(String id) { 80 | assertThatThrownBy(() -> docker.inspectContainer(id)) 81 | .isInstanceOf(ContainerNotFoundException.class); 82 | } 83 | 84 | protected void assertContainerExist(String id) throws DockerException, InterruptedException { 85 | assertNotNull(docker.inspectContainer(id)); 86 | } 87 | 88 | protected void assertVolumeDoesNotExist(String volumeName) throws DockerException, InterruptedException { 89 | assertThatThrownBy(() -> docker.inspectVolume(volumeName)) 90 | .isInstanceOf(VolumeNotFoundException.class); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/ClusterProfilePropertiesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import cd.go.contrib.elasticagents.docker.requests.CreateAgentRequest; 20 | import cd.go.contrib.elasticagents.docker.requests.JobCompletionRequest; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.Collections; 24 | import java.util.Map; 25 | 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | import static org.hamcrest.Matchers.is; 28 | 29 | public class ClusterProfilePropertiesTest { 30 | 31 | @Test 32 | public void shouldGenerateSameUUIDForClusterProfileProperties() { 33 | Map clusterProfileConfigurations = Collections.singletonMap("go_server_url", "http://go-server-url/go"); 34 | ClusterProfileProperties clusterProfileProperties = ClusterProfileProperties.fromConfiguration(clusterProfileConfigurations); 35 | 36 | assertThat(clusterProfileProperties.uuid(), is(clusterProfileProperties.uuid())); 37 | } 38 | 39 | @Test 40 | public void shouldGenerateSameUUIDForClusterProfilePropertiesAcrossRequests() { 41 | String createAgentRequestJSON = "{\n" + 42 | " \"auto_register_key\": \"secret-key\",\n" + 43 | " \"elastic_agent_profile_properties\": {\n" + 44 | " \"key1\": \"value1\",\n" + 45 | " \"key2\": \"value2\"\n" + 46 | " },\n" + 47 | " \"cluster_profile_properties\": {\n" + 48 | " \"go_server_url\": \"https://foo.com/go\",\n" + 49 | " \"docker_uri\": \"unix:///var/run/docker.sock\"\n" + 50 | " },\n" + 51 | " \"environment\": \"prod\"\n" + 52 | "}"; 53 | 54 | CreateAgentRequest createAgentRequest = CreateAgentRequest.fromJSON(createAgentRequestJSON); 55 | 56 | String jobCompletionRequestJSON = "{\n" + 57 | " \"elastic_agent_id\": \"ea1\",\n" + 58 | " \"elastic_agent_profile_properties\": {\n" + 59 | " \"Image\": \"alpine:latest\"\n" + 60 | " },\n" + 61 | " \"cluster_profile_properties\": {\n" + 62 | " \"go_server_url\": \"https://foo.com/go\", \n" + 63 | " \"docker_uri\": \"unix:///var/run/docker.sock\"\n" + 64 | " },\n" + 65 | " \"job_identifier\": {\n" + 66 | " \"pipeline_name\": \"test-pipeline\",\n" + 67 | " \"pipeline_counter\": 1,\n" + 68 | " \"pipeline_label\": \"Test Pipeline\",\n" + 69 | " \"stage_name\": \"test-stage\",\n" + 70 | " \"stage_counter\": \"1\",\n" + 71 | " \"job_name\": \"test-job\",\n" + 72 | " \"job_id\": 100\n" + 73 | " }\n" + 74 | "}"; 75 | 76 | JobCompletionRequest jobCompletionRequest = JobCompletionRequest.fromJSON(jobCompletionRequestJSON); 77 | assertThat(jobCompletionRequest.getClusterProfileProperties().uuid(), is(createAgentRequest.getClusterProfileProperties().uuid())); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/CpusSpecificationTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Collections; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class CpusSpecificationTest { 10 | @Test 11 | public void cpusIsTranslatedToCpuPeriodAndCpuQuota() { 12 | CpusSpecification cpus1 = new CpusSpecification("1.5"); 13 | 14 | assertEquals(100_000L, cpus1.getCpuPeriod()); 15 | assertEquals(150_000L, cpus1.getCpuQuota()); 16 | 17 | CpusSpecification cpus2 = new CpusSpecification(".5"); 18 | 19 | assertEquals(100_000L, cpus2.getCpuPeriod()); 20 | assertEquals(50_000L, cpus2.getCpuQuota()); 21 | } 22 | 23 | @Test 24 | public void cpusParsedWithErrors() { 25 | assertEquals(Collections.singletonList("Invalid float number: 0,3"), new CpusSpecification("0,3").getErrors()); 26 | assertEquals(Collections.singletonList("Invalid float number: abc"), new CpusSpecification("abc").getErrors()); 27 | } 28 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/HostMetadataTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Map; 6 | 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.hamcrest.Matchers.hasEntry; 9 | import static org.hamcrest.Matchers.is; 10 | import static org.junit.jupiter.api.Assertions.assertTrue; 11 | 12 | public class HostMetadataTest { 13 | 14 | @Test 15 | public void shouldValidateHostConfig() throws Exception { 16 | final Map validate = new HostMetadata().validate("10.0.0.1 hostname-1 hostname-2"); 17 | 18 | assertTrue(validate.isEmpty()); 19 | } 20 | 21 | @Test 22 | public void shouldValidateIPAddress() throws Exception { 23 | final Map validationResult = new HostMetadata().validate("10.0.0.foo hostname"); 24 | 25 | assertThat(validationResult.size(), is(2)); 26 | assertThat(validationResult, hasEntry("message", "'10.0.0.foo' is not an IP string literal.")); 27 | assertThat(validationResult, hasEntry("key", "Hosts")); 28 | } 29 | 30 | @Test 31 | public void shouldValidateInvalidHostConfig() throws Exception { 32 | Map validationResult = new HostMetadata().validate("some-config"); 33 | 34 | assertThat(validationResult.size(), is(2)); 35 | assertThat(validationResult, hasEntry("message", "Host entry `some-config` is invalid.")); 36 | assertThat(validationResult, hasEntry("key", "Hosts")); 37 | } 38 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/HostsTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.hamcrest.MatcherAssert.assertThat; 6 | import static org.hamcrest.Matchers.*; 7 | 8 | public class HostsTest { 9 | 10 | @Test 11 | public void shouldReturnEmptyListWhenHostConfigIsNotProvided() throws Exception { 12 | assertThat(new Hosts(null), hasSize(0)); 13 | assertThat(new Hosts(""), hasSize(0)); 14 | } 15 | 16 | @Test 17 | public void shouldReturnHostMappingForOneIpToOneHostnameMapping() throws Exception { 18 | final Hosts hosts = new Hosts("10.0.0.1 foo-host"); 19 | 20 | assertThat(hosts, hasSize(1)); 21 | assertThat(hosts, hasItem("foo-host:10.0.0.1")); 22 | } 23 | 24 | @Test 25 | public void shouldReturnHostMappingForOneIpToMAnyHostnameMapping() throws Exception { 26 | final Hosts hosts = new Hosts("10.0.0.1 foo-host bar-host"); 27 | 28 | assertThat(hosts, hasSize(1)); 29 | assertThat(hosts, hasItem("foo-host bar-host:10.0.0.1")); 30 | } 31 | 32 | @Test 33 | public void shouldIgnoreEmptyLines() throws Exception { 34 | final Hosts hosts = new Hosts("10.0.0.1 foo-host\n\n\n 10.0.0.2 bar-host"); 35 | 36 | assertThat(hosts, hasSize(2)); 37 | assertThat(hosts, hasItem("foo-host:10.0.0.1")); 38 | assertThat(hosts, hasItem("bar-host:10.0.0.2")); 39 | } 40 | 41 | @Test 42 | public void shouldValidateHostConfig() throws Exception { 43 | final Hosts hosts = new Hosts("10.0.0.foo foo-host\n bar-host\n 10.0.0.1 baz-host"); 44 | 45 | assertThat(hosts, hasSize(1)); 46 | assertThat(hosts, hasItem("baz-host:10.0.0.1")); 47 | 48 | assertThat(hosts.getErrors(), hasSize(2)); 49 | assertThat(hosts.getErrors(), containsInAnyOrder( 50 | "'10.0.0.foo' is not an IP string literal.", 51 | "Host entry `bar-host` is invalid.") 52 | ); 53 | } 54 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/MemorySpecificationTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.Collections; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | 10 | public class MemorySpecificationTest { 11 | @Test 12 | public void memorySpecificationIsParsedCorrectly() { 13 | assertEquals(Long.valueOf(10L * 1024L * 1024L), new MemorySpecification("10M").getMemory()); 14 | assertEquals(Long.valueOf(25L * 1024L * 1024L * 1024L), new MemorySpecification("25G").getMemory()); 15 | assertEquals(Long.valueOf(138L * 1024L * 1024L * 1024L * 1024L), new MemorySpecification("138T").getMemory()); 16 | assertEquals(Long.valueOf(15L * 1024L * 1024L / 10L), new MemorySpecification("1.5M").getMemory()); 17 | } 18 | 19 | @Test 20 | public void memorySpecificationParsingErrors() { 21 | assertEquals(Collections.singletonList("Invalid size: 5K. Wrong size unit"), new MemorySpecification("5K").getErrors()); 22 | assertEquals(Collections.singletonList("Invalid size: 5"), new MemorySpecification("5").getErrors()); 23 | assertEquals(Collections.singletonList("Invalid size: A"), new MemorySpecification("A").getErrors()); 24 | assertEquals(Collections.singletonList("Invalid size: .3M"), new MemorySpecification(".3M").getErrors()); 25 | assertEquals(Collections.singletonList("Invalid size: 1,3M"), new MemorySpecification("1,3M").getErrors()); 26 | } 27 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/NetworksTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker; 2 | 3 | import com.spotify.docker.client.messages.Network; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import static org.mockito.Mockito.*; 8 | import static org.junit.jupiter.api.Assertions.*; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | public class NetworksTest { 15 | 16 | @Test 17 | public void shouldReturnNullWhenNetworkConfigIsNotProvided() { 18 | assertNull(Networks.firstMatching(null, Collections.emptyList())); 19 | assertNull(Networks.firstMatching("", Collections.emptyList())); 20 | } 21 | 22 | @Test 23 | public void shouldReturnFirstNetworkFromString() { 24 | Network network = mock(Network.class); 25 | when(network.name()).thenReturn("gocd-net"); 26 | when(network.id()).thenReturn("network-id-1"); 27 | 28 | List networks = new ArrayList<>(); 29 | networks.add(network); 30 | 31 | String result = Networks.firstMatching("gocd-net", networks); 32 | 33 | assertNotNull(result); 34 | assertEquals("gocd-net", result); 35 | } 36 | 37 | @Test 38 | public void shouldThrowExceptionWhenNetworkDoesNotExist() { 39 | RuntimeException exception = assertThrows(RuntimeException.class, () -> { 40 | Networks.firstMatching("gocd-net", Collections.emptyList()); 41 | }); 42 | 43 | assertEquals("Networks [gocd-net] do not exist.", exception.getMessage()); 44 | } 45 | 46 | @Test 47 | public void shouldReturnEmptyListForAdditionalNetworksWhenOnlyOneNetwork() { 48 | assertTrue(Networks.getAdditionalNetworks("single-network").isEmpty()); 49 | } 50 | 51 | @Test 52 | public void shouldReturnAdditionalNetworksWhenMultipleNetworksProvided() { 53 | String networkConfig = "network1\nnetwork2\nnetwork3"; 54 | var additionalNetworks = Networks.getAdditionalNetworks(networkConfig); 55 | 56 | assertEquals(2, additionalNetworks.size()); 57 | assertTrue(additionalNetworks.contains("network2")); 58 | assertTrue(additionalNetworks.contains("network3")); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/PluginRequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 20 | import com.thoughtworks.go.plugin.api.GoApplicationAccessor; 21 | import com.thoughtworks.go.plugin.api.response.DefaultGoApiResponse; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import static org.mockito.ArgumentMatchers.any; 25 | import static org.mockito.Mockito.mock; 26 | import static org.mockito.Mockito.when; 27 | 28 | public class PluginRequestTest { 29 | @Test 30 | public void shouldNotThrowAnExceptionIfConsoleLogAppenderCallFails() { 31 | final JobIdentifier jobIdentifier = new JobIdentifier("p1", 1L, "l1", "s1", "1", "j1", 1L); 32 | 33 | GoApplicationAccessor accessor = mock(GoApplicationAccessor.class); 34 | when(accessor.submit(any())).thenReturn(DefaultGoApiResponse.badRequest("Something went wrong")); 35 | 36 | final PluginRequest pluginRequest = new PluginRequest(accessor); 37 | pluginRequest.appendToConsoleLog(jobIdentifier, "text1"); 38 | } 39 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/SetupSemaphoreTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import java.util.LinkedHashMap; 22 | import java.util.Map; 23 | import java.util.concurrent.Semaphore; 24 | 25 | import static org.hamcrest.MatcherAssert.assertThat; 26 | import static org.hamcrest.Matchers.is; 27 | 28 | public class SetupSemaphoreTest { 29 | 30 | @Test 31 | public void shouldDrainSemaphoreCompletely_when_maxAllowedContainersIsLessThanCurrentContainerCount() throws Exception { 32 | Map instances = instancesWithSize(10); 33 | Semaphore semaphore = new Semaphore(9, true); 34 | 35 | new SetupSemaphore(5, instances, semaphore).run(); 36 | 37 | assertThat(semaphore.availablePermits(), is(0)); 38 | } 39 | 40 | @Test 41 | public void shouldDecreaseSemaphore_when_maxAllowedContainersIsMoreThanCurrentContainerCount_and_availablePermitInSemaphoreIsLessThanNumberOfInstances() throws Exception { 42 | Map instances = instancesWithSize(10); 43 | Semaphore semaphore = new Semaphore(11, true); 44 | 45 | new SetupSemaphore(15, instances, semaphore).run(); 46 | 47 | assertThat(semaphore.availablePermits(), is(5)); 48 | } 49 | 50 | @Test 51 | public void shouldIncreaseSemaphore_when_maxAllowedContainersIsMoreThanCurrentContainerCount_and_availablePermitInSemaphoreIsLessThanNumberOfInstances() throws Exception { 52 | Map instances = instancesWithSize(10); 53 | Semaphore semaphore = new Semaphore(1, true); 54 | 55 | new SetupSemaphore(15, instances, semaphore).run(); 56 | 57 | assertThat(semaphore.availablePermits(), is(5)); 58 | } 59 | 60 | private Map instancesWithSize(int size) { 61 | Map instances = new LinkedHashMap<>(); 62 | 63 | while (size != 0) { 64 | instances.put(size, size); 65 | size--; 66 | } 67 | 68 | return instances; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/ClusterStatusReportExecutorTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.executors; 2 | 3 | import cd.go.contrib.elasticagents.docker.ClusterProfileProperties; 4 | import cd.go.contrib.elasticagents.docker.DockerContainers; 5 | import cd.go.contrib.elasticagents.docker.models.StatusReport; 6 | import cd.go.contrib.elasticagents.docker.requests.ClusterStatusReportRequest; 7 | import cd.go.contrib.elasticagents.docker.views.ViewBuilder; 8 | import com.google.gson.JsonObject; 9 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 10 | import freemarker.template.Template; 11 | import org.junit.jupiter.api.Test; 12 | import org.junit.jupiter.api.extension.ExtendWith; 13 | import org.mockito.Mock; 14 | import org.mockito.junit.jupiter.MockitoExtension; 15 | import org.skyscreamer.jsonassert.JSONAssert; 16 | 17 | import java.util.ArrayList; 18 | 19 | import static org.hamcrest.MatcherAssert.assertThat; 20 | import static org.hamcrest.Matchers.is; 21 | import static org.mockito.Mockito.when; 22 | 23 | @ExtendWith(MockitoExtension.class) 24 | public class ClusterStatusReportExecutorTest { 25 | 26 | @Mock 27 | private ClusterStatusReportRequest clusterStatusReportRequest; 28 | 29 | @Mock 30 | private ClusterProfileProperties clusterProfile; 31 | 32 | @Mock 33 | private ViewBuilder viewBuilder; 34 | 35 | @Mock 36 | private DockerContainers dockerContainers; 37 | 38 | @Mock 39 | private Template template; 40 | 41 | @Test 42 | public void shouldGetStatusReport() throws Exception { 43 | StatusReport statusReport = aStatusReport(); 44 | when(clusterStatusReportRequest.getClusterProfile()).thenReturn(clusterProfile); 45 | when(dockerContainers.getStatusReport(clusterProfile)).thenReturn(statusReport); 46 | when(viewBuilder.getTemplate("status-report.template.ftlh")).thenReturn(template); 47 | when(viewBuilder.build(template, statusReport)).thenReturn("statusReportView"); 48 | ClusterStatusReportExecutor statusReportExecutor = new ClusterStatusReportExecutor(clusterStatusReportRequest, dockerContainers, viewBuilder); 49 | 50 | GoPluginApiResponse goPluginApiResponse = statusReportExecutor.execute(); 51 | 52 | JsonObject expectedResponseBody = new JsonObject(); 53 | expectedResponseBody.addProperty("view", "statusReportView"); 54 | assertThat(goPluginApiResponse.responseCode(), is(200)); 55 | JSONAssert.assertEquals(expectedResponseBody.toString(), goPluginApiResponse.responseBody(), true); 56 | } 57 | 58 | private StatusReport aStatusReport() { 59 | return new StatusReport("os", "x86_64", "0.1.2", 2, "100M", new ArrayList<>()); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/CreateAgentRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.*; 20 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 21 | import cd.go.contrib.elasticagents.docker.requests.CreateAgentRequest; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import java.util.HashMap; 25 | 26 | import static org.junit.jupiter.api.Assertions.fail; 27 | import static org.mockito.Mockito.*; 28 | 29 | public class CreateAgentRequestExecutorTest { 30 | @Test 31 | public void shouldAskDockerContainersToCreateAnAgent() throws Exception { 32 | final HashMap elasticAgentProfileProperties = new HashMap<>(); 33 | elasticAgentProfileProperties.put("Image", "image1"); 34 | final JobIdentifier jobIdentifier = new JobIdentifier("p1", 1L, "l1", "s1", "1", "j1", 1L); 35 | CreateAgentRequest request = new CreateAgentRequest("key1", elasticAgentProfileProperties, "env1", jobIdentifier, new HashMap<>()); 36 | 37 | AgentInstances agentInstances = mock(DockerContainers.class); 38 | PluginRequest pluginRequest = mock(PluginRequest.class); 39 | new CreateAgentRequestExecutor(request, agentInstances, pluginRequest).execute(); 40 | 41 | verify(agentInstances).create(eq(request), eq(pluginRequest), any(ConsoleLogAppender.class)); 42 | verify(pluginRequest).appendToConsoleLog(eq(jobIdentifier), contains("Received request to create a container of image1 at ")); 43 | } 44 | 45 | @Test 46 | public void shouldLogErrorMessageToConsoleIfAgentCreateFails() throws Exception { 47 | final HashMap elasticAgentProfileProperties = new HashMap<>(); 48 | elasticAgentProfileProperties.put("Image", "image1"); 49 | final JobIdentifier jobIdentifier = new JobIdentifier("p1", 1L, "l1", "s1", "1", "j1", 1L); 50 | CreateAgentRequest request = new CreateAgentRequest("key1", elasticAgentProfileProperties, "env1", jobIdentifier, new HashMap<>()); 51 | 52 | AgentInstances agentInstances = mock(DockerContainers.class); 53 | PluginRequest pluginRequest = mock(PluginRequest.class); 54 | when(agentInstances.create(eq(request), eq(pluginRequest), any(ConsoleLogAppender.class))).thenThrow(new RuntimeException("Ouch!")); 55 | 56 | try { 57 | new CreateAgentRequestExecutor(request, agentInstances, pluginRequest).execute(); 58 | fail("Should have thrown an exception"); 59 | } catch (RuntimeException e) { 60 | // expected 61 | } 62 | 63 | verify(pluginRequest).appendToConsoleLog(eq(jobIdentifier), contains("Received request to create a container of image1 at ")); 64 | verify(pluginRequest).appendToConsoleLog(eq(jobIdentifier), contains("Failed while creating container: Ouch")); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/GetCapabilitiesExecutorTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.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 | 11 | public class GetCapabilitiesExecutorTest { 12 | 13 | @Test 14 | public void shouldReturnResponse() throws Exception { 15 | GoPluginApiResponse response = new GetCapabilitiesExecutor().execute(); 16 | 17 | assertThat(response.responseCode(), is(200)); 18 | JSONObject expected = new JSONObject().put("supports_plugin_status_report", false); 19 | expected.put("supports_agent_status_report", true); 20 | expected.put("supports_cluster_status_report", true); 21 | JSONAssert.assertEquals(expected, new JSONObject(response.responseBody()), true); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/GetClusterProfileViewRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.reflect.TypeToken; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import java.lang.reflect.Type; 26 | import java.util.Map; 27 | 28 | import static org.hamcrest.MatcherAssert.assertThat; 29 | import static org.hamcrest.Matchers.*; 30 | 31 | public class GetClusterProfileViewRequestExecutorTest { 32 | 33 | @Test 34 | public void shouldRenderTheTemplateInJSON() throws Exception { 35 | GoPluginApiResponse response = new GetClusterProfileViewRequestExecutor().execute(); 36 | assertThat(response.responseCode(), is(200)); 37 | final Type type = new TypeToken>() { 38 | }.getType(); 39 | Map hashSet = new Gson().fromJson(response.responseBody(), type); 40 | assertThat(hashSet, hasEntry("template", Util.readResource("/plugin-settings.template.html"))); 41 | } 42 | 43 | @Test 44 | public void allFieldsShouldBePresentInView() throws Exception { 45 | String template = Util.readResource("/plugin-settings.template.html"); 46 | 47 | for (Metadata field : GetClusterProfileMetadataExecutor.FIELDS) { 48 | assertThat(template, containsString("ng-model=\"" + field.getKey() + "\"")); 49 | assertThat(template, containsString("{{GOINPUTNAME[" + field.getKey() + 51 | "].$error.server}}")); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/GetPluginSettingsIconExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.reflect.TypeToken; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.apache.commons.codec.binary.Base64; 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 GetPluginSettingsIconExecutorTest { 33 | 34 | @Test 35 | public void rendersIconInBase64() throws Exception { 36 | GoPluginApiResponse response = new GetPluginSettingsIconExecutor().execute(); 37 | final Type type = new TypeToken>() { 38 | }.getType(); 39 | final Map hashMap = new Gson().fromJson(response.responseBody(), type); 40 | 41 | assertThat(hashMap.size(), is(2)); 42 | assertThat(hashMap.get("content_type"), is("image/svg+xml")); 43 | assertThat(Util.readResourceBytes("/docker-plain.svg"), is(Base64.decodeBase64(hashMap.get("data")))); 44 | } 45 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/GetProfileViewExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.utils.Util; 20 | import com.google.gson.Gson; 21 | import com.google.gson.reflect.TypeToken; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import java.lang.reflect.Type; 26 | import java.util.Map; 27 | 28 | import static org.hamcrest.MatcherAssert.assertThat; 29 | import static org.hamcrest.Matchers.*; 30 | 31 | public class GetProfileViewExecutorTest { 32 | @Test 33 | public void shouldRenderTheTemplateInJSON() throws Exception { 34 | GoPluginApiResponse response = new GetProfileViewExecutor().execute(); 35 | assertThat(response.responseCode(), is(200)); 36 | final Type type = new TypeToken>() { 37 | }.getType(); 38 | final Map hashSet = new Gson().fromJson(response.responseBody(), type); 39 | assertThat(hashSet, hasEntry("template", Util.readResource("/profile.template.html"))); 40 | } 41 | 42 | @Test 43 | public void allFieldsShouldBePresentInView() throws Exception { 44 | String template = Util.readResource("/profile.template.html"); 45 | 46 | for (Metadata field : GetProfileMetadataExecutor.FIELDS) { 47 | assertThat(template, containsString("ng-model=\"" + field.getKey() + "\"")); 48 | assertThat(template, containsString("{{GOINPUTNAME[" + 51 | field.getKey() + "].$error.server}}")); 52 | } 53 | } 54 | 55 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/GoServerURLMetadataTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import static org.hamcrest.MatcherAssert.assertThat; 22 | import static org.hamcrest.Matchers.is; 23 | import static org.hamcrest.Matchers.nullValue; 24 | 25 | public class GoServerURLMetadataTest { 26 | private final GoServerURLMetadata goServerURLMetadata = new GoServerURLMetadata(); 27 | 28 | @Test 29 | public void shouldCheckBlankInput() { 30 | String result = goServerURLMetadata.doValidate(""); 31 | 32 | assertThat(result, is("Go Server URL must not be blank.")); 33 | } 34 | 35 | @Test 36 | public void shouldCheckIfStringIsValidUrl() { 37 | String result = goServerURLMetadata.doValidate("foobar"); 38 | 39 | assertThat(result, is("Go Server URL must be a valid URL (http://example.com:8153/go)")); 40 | } 41 | 42 | @Test 43 | public void shouldCheckIfSchemeIsValid() { 44 | String result = goServerURLMetadata.doValidate("example.com"); 45 | 46 | assertThat(result, is("Go Server URL must be a valid URL (http://example.com:8153/go)")); 47 | } 48 | 49 | @Test 50 | public void shouldCheckIfSchemeIsHTTPS() { 51 | String result = goServerURLMetadata.doValidate("ftp://example.com"); 52 | 53 | assertThat(result, is("Go Server URL must use http or https protocol")); 54 | } 55 | 56 | @Test 57 | public void shouldCheckForLocalhost() { 58 | String result = goServerURLMetadata.doValidate("https://localhost:8154/go"); 59 | 60 | assertThat(result, is("Go Server URL must not be localhost, since this gets resolved on the agents")); 61 | 62 | result = goServerURLMetadata.doValidate("https://127.0.0.1:8154/go"); 63 | 64 | assertThat(result, is("Go Server URL must not be localhost, since this gets resolved on the agents")); 65 | } 66 | 67 | @Test 68 | public void shouldCheckIfUrlEndsWithContextGo() { 69 | String result = goServerURLMetadata.doValidate("https://example.com:8154/"); 70 | assertThat(result, is("Go Server URL must be a valid URL ending with '/go' (http://example.com:8153/go)")); 71 | 72 | result = goServerURLMetadata.doValidate("http://example.com:8153/crimemastergogo"); 73 | assertThat(result, is("Go Server URL must be a valid URL ending with '/go' (http://example.com:8153/go)")); 74 | } 75 | 76 | @Test 77 | public void shouldReturnNullForValidUrls() { 78 | String result = goServerURLMetadata.doValidate("https://example.com:8154/go"); 79 | assertThat(result, is(nullValue())); 80 | 81 | result = goServerURLMetadata.doValidate("https://example.com:8154/go/"); 82 | assertThat(result, is(nullValue())); 83 | 84 | result = goServerURLMetadata.doValidate("https://example.com:8154/foo/go/"); 85 | assertThat(result, is(nullValue())); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/JobCompletionRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Thoughtworks, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.*; 20 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 21 | import cd.go.contrib.elasticagents.docker.requests.JobCompletionRequest; 22 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 23 | import org.junit.jupiter.api.BeforeEach; 24 | import org.junit.jupiter.api.Test; 25 | import org.mockito.ArgumentCaptor; 26 | import org.mockito.Captor; 27 | import org.mockito.InOrder; 28 | import org.mockito.Mock; 29 | 30 | import java.util.List; 31 | 32 | import static org.junit.jupiter.api.Assertions.assertEquals; 33 | import static org.junit.jupiter.api.Assertions.assertTrue; 34 | import static org.mockito.Mockito.inOrder; 35 | import static org.mockito.MockitoAnnotations.openMocks; 36 | 37 | public class JobCompletionRequestExecutorTest { 38 | @Mock 39 | private PluginRequest mockPluginRequest; 40 | @Mock 41 | private AgentInstances mockAgentInstances; 42 | @Captor 43 | private ArgumentCaptor> agentsArgumentCaptor; 44 | 45 | @BeforeEach 46 | public void setUp() { 47 | openMocks(this); 48 | } 49 | 50 | @Test 51 | public void shouldTerminateElasticAgentOnJobCompletion() throws Exception { 52 | JobIdentifier jobIdentifier = new JobIdentifier(100L); 53 | ClusterProfileProperties clusterProfileProperties = new ClusterProfileProperties(); 54 | String elasticAgentId = "agent-1"; 55 | JobCompletionRequest request = new JobCompletionRequest(elasticAgentId, jobIdentifier, null, clusterProfileProperties); 56 | JobCompletionRequestExecutor executor = new JobCompletionRequestExecutor(request, mockAgentInstances, mockPluginRequest); 57 | GoPluginApiResponse response = executor.execute(); 58 | 59 | InOrder inOrder = inOrder(mockPluginRequest, mockAgentInstances); 60 | inOrder.verify(mockPluginRequest).disableAgents(agentsArgumentCaptor.capture()); 61 | List agentsToDisabled = agentsArgumentCaptor.getValue(); 62 | assertEquals(1, agentsToDisabled.size()); 63 | assertEquals(elasticAgentId, agentsToDisabled.get(0).elasticAgentId()); 64 | inOrder.verify(mockAgentInstances).terminate(elasticAgentId, clusterProfileProperties); 65 | inOrder.verify(mockPluginRequest).deleteAgents(agentsArgumentCaptor.capture()); 66 | List agentsToDelete = agentsArgumentCaptor.getValue(); 67 | assertEquals(agentsToDisabled, agentsToDelete); 68 | assertEquals(200, response.responseCode()); 69 | assertTrue(response.responseBody().isEmpty()); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/ProfileValidateRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.requests.ProfileValidateRequest; 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 ProfileValidateRequestExecutorTest { 27 | @Test 28 | public void shouldBarfWhenUnknownKeysArePassed() throws Exception { 29 | ProfileValidateRequestExecutor executor = new ProfileValidateRequestExecutor(new ProfileValidateRequest(Collections.singletonMap("foo", "bar"))); 30 | String json = executor.execute().responseBody(); 31 | JSONAssert.assertEquals("[{\"message\":\"Image must not be blank.\",\"key\":\"Image\"},{\"key\":\"foo\",\"message\":\"Is an unknown property\"}]", json, JSONCompareMode.NON_EXTENSIBLE); 32 | } 33 | 34 | @Test 35 | public void shouldValidateMandatoryKeys() throws Exception { 36 | ProfileValidateRequestExecutor executor = new ProfileValidateRequestExecutor(new ProfileValidateRequest(Collections.emptyMap())); 37 | String json = executor.execute().responseBody(); 38 | JSONAssert.assertEquals("[{\"message\":\"Image must not be blank.\",\"key\":\"Image\"}]", json, JSONCompareMode.NON_EXTENSIBLE); 39 | } 40 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/executors/ShouldAssignWorkRequestExecutorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.executors; 18 | 19 | import cd.go.contrib.elasticagents.docker.*; 20 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 21 | import cd.go.contrib.elasticagents.docker.requests.CreateAgentRequest; 22 | import cd.go.contrib.elasticagents.docker.requests.ShouldAssignWorkRequest; 23 | import com.thoughtworks.go.plugin.api.response.GoPluginApiResponse; 24 | import org.junit.jupiter.api.BeforeEach; 25 | import org.junit.jupiter.api.Test; 26 | 27 | import java.util.HashMap; 28 | import java.util.Map; 29 | import java.util.UUID; 30 | 31 | import static org.hamcrest.MatcherAssert.assertThat; 32 | import static org.hamcrest.Matchers.is; 33 | import static org.mockito.Mockito.mock; 34 | 35 | public class ShouldAssignWorkRequestExecutorTest extends BaseTest { 36 | 37 | private DockerContainers agentInstances; 38 | private DockerContainer instance; 39 | private final String environment = "production"; 40 | private final JobIdentifier jobIdentifier = new JobIdentifier("up42", 2L, "foo", "stage", "1", "job", 1L); 41 | private Map properties = new HashMap<>(); 42 | 43 | @BeforeEach 44 | public void setUp() throws Exception { 45 | agentInstances = new DockerContainers(); 46 | properties.put("foo", "bar"); 47 | properties.put("Image", "alpine"); 48 | properties.put("Command", "/bin/sleep\n5"); 49 | ClusterProfileProperties clusterProfiles = createClusterProfiles(); 50 | PluginRequest pluginRequest = mock(PluginRequest.class); 51 | instance = agentInstances.create(new CreateAgentRequest(UUID.randomUUID().toString(), properties, environment, jobIdentifier, clusterProfiles), pluginRequest, mock(ConsoleLogAppender.class)); 52 | containers.add(instance.name()); 53 | } 54 | 55 | @Test 56 | public void shouldAssignWorkToContainerWithSameJobIdentifier() { 57 | ShouldAssignWorkRequest request = new ShouldAssignWorkRequest(new Agent(instance.name(), null, null, null), environment, jobIdentifier, null, null); 58 | GoPluginApiResponse response = new ShouldAssignWorkRequestExecutor(request, agentInstances).execute(); 59 | assertThat(response.responseCode(), is(200)); 60 | assertThat(response.responseBody(), is("true")); 61 | } 62 | 63 | @Test 64 | public void shouldNotAssignWorkToContainerWithDifferentJobIdentifier() { 65 | JobIdentifier otherJobId = new JobIdentifier("up42", 2L, "foo", "stage", "1", "job", 2L); 66 | ShouldAssignWorkRequest request = new ShouldAssignWorkRequest(new Agent(instance.name(), null, null, null), environment, otherJobId, null,null); 67 | GoPluginApiResponse response = new ShouldAssignWorkRequestExecutor(request, agentInstances).execute(); 68 | assertThat(response.responseCode(), is(200)); 69 | assertThat(response.responseBody(), is("false")); 70 | } 71 | 72 | @Test 73 | public void shouldNotAssignWorkIfInstanceIsNotFound() { 74 | ShouldAssignWorkRequest request = new ShouldAssignWorkRequest(new Agent("unknown-name", null, null, null), environment, jobIdentifier, null,null); 75 | GoPluginApiResponse response = new ShouldAssignWorkRequestExecutor(request, agentInstances).execute(); 76 | assertThat(response.responseCode(), is(200)); 77 | assertThat(response.responseBody(), is("false")); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/models/JobIdentifierTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.models; 2 | 3 | import cd.go.contrib.elasticagents.docker.utils.JobIdentifierMother; 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 JobIdentifierTest { 10 | 11 | @Test 12 | public void shouldDeserializeFromJson() { 13 | JobIdentifier jobIdentifier = JobIdentifier.fromJson(JobIdentifierMother.getJson().toString()); 14 | 15 | JobIdentifier expected = JobIdentifierMother.get(); 16 | assertThat(jobIdentifier, is(expected)); 17 | } 18 | 19 | @Test 20 | public void shouldGetRepresentation() { 21 | String representation = JobIdentifierMother.get().getRepresentation(); 22 | 23 | assertThat(representation, is("up42/1/stage/1/job1")); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/requests/AgentStatusReportRequestTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.requests; 2 | 3 | import cd.go.contrib.elasticagents.docker.utils.JobIdentifierMother; 4 | import com.google.gson.JsonObject; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.hamcrest.MatcherAssert.assertThat; 12 | 13 | public class AgentStatusReportRequestTest { 14 | 15 | @Test 16 | public void shouldDeserializeFromJSON() { 17 | JsonObject jobIdentifierJson = JobIdentifierMother.getJson(); 18 | JsonObject jsonObject = new JsonObject(); 19 | jsonObject.addProperty("elastic_agent_id", "some-id"); 20 | jsonObject.add("job_identifier", jobIdentifierJson); 21 | 22 | AgentStatusReportRequest agentStatusReportRequest = AgentStatusReportRequest.fromJSON(jsonObject.toString()); 23 | 24 | AgentStatusReportRequest expected = new AgentStatusReportRequest("some-id", JobIdentifierMother.get(), null); 25 | assertThat(agentStatusReportRequest, is(expected)); 26 | } 27 | 28 | @Test 29 | public void shouldDeserializeFromJSONWithClusterProfile() { 30 | JsonObject jobIdentifierJson = JobIdentifierMother.getJson(); 31 | JsonObject jsonObject = new JsonObject(); 32 | jsonObject.addProperty("elastic_agent_id", "some-id"); 33 | jsonObject.add("job_identifier", jobIdentifierJson); 34 | JsonObject clusterJSON = new JsonObject(); 35 | clusterJSON.addProperty("go_server_url", "https://foo.com/go"); 36 | clusterJSON.addProperty("docker_uri", "unix:///var/run/docker.sock"); 37 | jsonObject.add("cluster_profile_properties", clusterJSON); 38 | 39 | AgentStatusReportRequest agentStatusReportRequest = AgentStatusReportRequest.fromJSON(jsonObject.toString()); 40 | 41 | Map expectedClusterProfile = new HashMap<>(); 42 | expectedClusterProfile.put("go_server_url", "https://foo.com/go"); 43 | expectedClusterProfile.put("docker_uri", "unix:///var/run/docker.sock"); 44 | 45 | AgentStatusReportRequest expected = new AgentStatusReportRequest("some-id", JobIdentifierMother.get(), expectedClusterProfile); 46 | assertThat(agentStatusReportRequest, is(expected)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/requests/ClusterStatusReportRequestTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.requests; 2 | 3 | import com.google.gson.JsonObject; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Collections; 7 | 8 | import static org.hamcrest.CoreMatchers.is; 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | 11 | public class ClusterStatusReportRequestTest { 12 | 13 | @Test 14 | public void shouldDeserializeFromJSON() { 15 | JsonObject jsonObject = new JsonObject(); 16 | JsonObject clusterJSON = new JsonObject(); 17 | clusterJSON.addProperty("go_server_url", "https://go-server/go"); 18 | jsonObject.add("cluster_profile_properties", clusterJSON); 19 | 20 | ClusterStatusReportRequest clusterStatusReportRequest = ClusterStatusReportRequest.fromJSON(jsonObject.toString()); 21 | 22 | ClusterStatusReportRequest expected = new ClusterStatusReportRequest(Collections.singletonMap("go_server_url", "https://go-server/go")); 23 | assertThat(clusterStatusReportRequest, is(expected)); 24 | } 25 | } -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/requests/CreateAgentRequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.ClusterProfileProperties; 20 | import org.hamcrest.Matchers; 21 | import org.junit.jupiter.api.Test; 22 | 23 | import java.util.HashMap; 24 | import java.util.Map; 25 | 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | import static org.hamcrest.Matchers.equalTo; 28 | import static org.hamcrest.Matchers.is; 29 | 30 | public class CreateAgentRequestTest { 31 | 32 | @Test 33 | public void shouldDeserializeFromJSON() throws Exception { 34 | String json = "{\n" + 35 | " \"auto_register_key\": \"secret-key\",\n" + 36 | " \"elastic_agent_profile_properties\": {\n" + 37 | " \"key1\": \"value1\",\n" + 38 | " \"key2\": \"value2\"\n" + 39 | " },\n" + 40 | " \"cluster_profile_properties\": {\n" + 41 | " \"go_server_url\": \"https://foo.com/go\",\n" + 42 | " \"docker_uri\": \"unix:///var/run/docker.sock\"\n" + 43 | " },\n" + 44 | " \"environment\": \"prod\"\n" + 45 | "}"; 46 | 47 | CreateAgentRequest request = CreateAgentRequest.fromJSON(json); 48 | assertThat(request.autoRegisterKey(), equalTo("secret-key")); 49 | assertThat(request.environment(), equalTo("prod")); 50 | HashMap expectedProperties = new HashMap<>(); 51 | expectedProperties.put("key1", "value1"); 52 | expectedProperties.put("key2", "value2"); 53 | assertThat(request.properties(), Matchers.>equalTo(expectedProperties)); 54 | 55 | ClusterProfileProperties expectedClusterProfileProperties = new ClusterProfileProperties(); 56 | expectedClusterProfileProperties.setGoServerUrl("https://foo.com/go"); 57 | expectedClusterProfileProperties.setDockerURI("unix:///var/run/docker.sock"); 58 | 59 | assertThat(request.getClusterProfileProperties(), is(expectedClusterProfileProperties)); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/requests/JobCompletionRequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.ClusterProfileProperties; 20 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 21 | import org.hamcrest.Matchers; 22 | import org.junit.jupiter.api.Test; 23 | 24 | import java.util.HashMap; 25 | 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | import static org.hamcrest.Matchers.is; 28 | 29 | public class JobCompletionRequestTest { 30 | @Test 31 | public void shouldDeserializeFromJSON() throws Exception { 32 | String json = "{\n" + 33 | " \"elastic_agent_id\": \"ea1\",\n" + 34 | " \"elastic_agent_profile_properties\": {\n" + 35 | " \"Image\": \"alpine:latest\"\n" + 36 | " },\n" + 37 | " \"cluster_profile_properties\": {\n" + 38 | " \"go_server_url\": \"https://example.com/go\"\n" + 39 | " },\n" + 40 | " \"job_identifier\": {\n" + 41 | " \"pipeline_name\": \"test-pipeline\",\n" + 42 | " \"pipeline_counter\": 1,\n" + 43 | " \"pipeline_label\": \"Test Pipeline\",\n" + 44 | " \"stage_name\": \"test-stage\",\n" + 45 | " \"stage_counter\": \"1\",\n" + 46 | " \"job_name\": \"test-job\",\n" + 47 | " \"job_id\": 100\n" + 48 | " }\n" + 49 | "}"; 50 | 51 | JobCompletionRequest request = JobCompletionRequest.fromJSON(json); 52 | JobIdentifier expectedJobIdentifier = new JobIdentifier("test-pipeline", 1L, "Test Pipeline", "test-stage", "1", "test-job", 100L); 53 | JobIdentifier actualJobIdentifier = request.jobIdentifier(); 54 | assertThat(actualJobIdentifier, is(expectedJobIdentifier)); 55 | assertThat(request.getElasticAgentId(), is("ea1")); 56 | 57 | HashMap propertiesJson = new HashMap<>(); 58 | propertiesJson.put("Image", "alpine:latest"); 59 | assertThat(request.getProperties(), Matchers.equalTo(propertiesJson)); 60 | 61 | ClusterProfileProperties expectedClusterProfileProperties = new ClusterProfileProperties(); 62 | expectedClusterProfileProperties.setGoServerUrl("https://example.com/go"); 63 | assertThat(request.getClusterProfileProperties(), is(expectedClusterProfileProperties)); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/requests/ServerPingRequestTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.requests; 2 | 3 | 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | 9 | import static org.hamcrest.MatcherAssert.assertThat; 10 | import static org.hamcrest.Matchers.is; 11 | 12 | public class ServerPingRequestTest { 13 | @Test 14 | public void shouldDeserializeJSONBody() { 15 | String requestBody = "{\n " + 16 | " \"all_cluster_profile_properties\": [\n " + 17 | "{\n " + 18 | " \"go_server_url\": \"foo\",\n" + 19 | " \"max_docker_containers\": \"100\",\n" + 20 | " \"docker_uri\": \"dockerURI\",\n" + 21 | " \"auto_register_timeout\": \"1\",\n" + 22 | " \"private_registry_password\": \"foobar\",\n" + 23 | " \"enable_private_registry_authentication\": \"false\",\n" + 24 | " \"private_registry_custom_credentials\": \"true\",\n" + 25 | " \"pull_on_container_create\": \"false\"\n" + 26 | " }\n" + 27 | " ]" + 28 | "\n}"; 29 | 30 | HashMap configurations = new HashMap<>(); 31 | configurations.put("go_server_url", "foo"); 32 | configurations.put("max_docker_containers", "100"); 33 | configurations.put("docker_uri", "dockerURI"); 34 | configurations.put("auto_register_timeout", "1"); 35 | configurations.put("private_registry_password", "foobar"); 36 | configurations.put("enable_private_registry_authentication", "false"); 37 | configurations.put("private_registry_custom_credentials", "true"); 38 | configurations.put("pull_on_container_create", "false"); 39 | assertThat(ServerPingRequest.fromJSON(requestBody), is(new ServerPingRequest(Arrays.asList(configurations)))); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/requests/ShouldAssignWorkRequestTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.requests; 18 | 19 | import cd.go.contrib.elasticagents.docker.Agent; 20 | import cd.go.contrib.elasticagents.docker.utils.JobIdentifierMother; 21 | import com.google.gson.JsonObject; 22 | import org.hamcrest.Matchers; 23 | import org.junit.jupiter.api.Test; 24 | 25 | import java.util.HashMap; 26 | 27 | import static org.hamcrest.CoreMatchers.is; 28 | import static org.hamcrest.MatcherAssert.assertThat; 29 | import static org.hamcrest.Matchers.equalTo; 30 | 31 | public class ShouldAssignWorkRequestTest { 32 | 33 | @Test 34 | public void shouldDeserializeFromJSON() { 35 | JsonObject agentJson = new JsonObject(); 36 | agentJson.addProperty("agent_id", "42"); 37 | agentJson.addProperty("agent_state", "Idle"); 38 | agentJson.addProperty("build_state", "Idle"); 39 | agentJson.addProperty("config_state", "Enabled"); 40 | 41 | JsonObject propertiesJson = new JsonObject(); 42 | propertiesJson.addProperty("property_name", "property_value"); 43 | 44 | JsonObject json = new JsonObject(); 45 | json.addProperty("environment", "prod"); 46 | json.add("agent", agentJson); 47 | json.add("job_identifier", JobIdentifierMother.getJson()); 48 | json.add("properties", propertiesJson); 49 | 50 | ShouldAssignWorkRequest request = ShouldAssignWorkRequest.fromJSON(json.toString()); 51 | 52 | assertThat(request.environment(), equalTo("prod")); 53 | assertThat(request.agent(), equalTo(new Agent("42", Agent.AgentState.Idle, Agent.BuildState.Idle, Agent.ConfigState.Enabled))); 54 | HashMap expectedProperties = new HashMap<>(); 55 | expectedProperties.put("property_name", "property_value"); 56 | assertThat(request.properties(), Matchers.equalTo(expectedProperties)); 57 | assertThat(request.jobIdentifier(), is(JobIdentifierMother.get())); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/utils/JobIdentifierMother.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.utils; 2 | 3 | import cd.go.contrib.elasticagents.docker.models.JobIdentifier; 4 | import com.google.gson.JsonObject; 5 | 6 | public class JobIdentifierMother { 7 | 8 | public static JsonObject getJson() { 9 | JsonObject jobIdentifierJson = new JsonObject(); 10 | jobIdentifierJson.addProperty("pipeline_name", "up42"); 11 | jobIdentifierJson.addProperty("pipeline_counter", 1); 12 | jobIdentifierJson.addProperty("pipeline_label", "label"); 13 | jobIdentifierJson.addProperty("stage_name", "stage"); 14 | jobIdentifierJson.addProperty("stage_counter", "1"); 15 | jobIdentifierJson.addProperty("job_name", "job1"); 16 | jobIdentifierJson.addProperty("job_id", 1); 17 | return jobIdentifierJson; 18 | } 19 | 20 | public static JobIdentifier get() { 21 | return new JobIdentifier("up42", 1L, "label", "stage", "1", "job1", 1L); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/utils/UtilTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 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 cd.go.contrib.elasticagents.docker.utils; 18 | 19 | import org.junit.jupiter.api.Test; 20 | 21 | import java.util.Collection; 22 | 23 | import static org.hamcrest.CoreMatchers.hasItems; 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.hamcrest.MatcherAssert.assertThat; 26 | 27 | public class UtilTest { 28 | 29 | @Test 30 | public void shouldSplitIntoLinesAndTrimSpaces() throws Exception { 31 | Collection strings = Util.splitIntoLinesAndTrimSpaces("FOO=BAR\n" + 32 | " X=Y\n" + 33 | "\n" + 34 | " A=B\r\n" + 35 | "\n" + 36 | "W=1"); 37 | 38 | assertThat(strings.size(), is(4)); 39 | assertThat(strings, hasItems("FOO=BAR", "X=Y", "A=B", "W=1")); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/test/java/cd/go/contrib/elasticagents/docker/validator/MemorySettingsProfileValidatorTest.java: -------------------------------------------------------------------------------- 1 | package cd.go.contrib.elasticagents.docker.validator; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | import static cd.go.contrib.elasticagents.docker.executors.GetProfileMetadataExecutor.MAX_MEMORY; 9 | import static cd.go.contrib.elasticagents.docker.executors.GetProfileMetadataExecutor.RESERVED_MEMORY; 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | public class MemorySettingsProfileValidatorTest { 14 | @Test 15 | public void minimumMaxMemoryValue() { 16 | Map elasticProfile = new HashMap<>(); 17 | elasticProfile.put(MAX_MEMORY.getKey(), "3M"); 18 | 19 | Map errors = new MemorySettingsProfileValidator().validate(elasticProfile); 20 | 21 | assertEquals(MAX_MEMORY.getKey(), errors.get("key")); 22 | assertEquals("Minimum allowed value is 4M", errors.get("message")); 23 | } 24 | 25 | @Test 26 | public void bothMemorySettingsAreEmpty() { 27 | Map elasticProfile = new HashMap<>(); 28 | elasticProfile.put(MAX_MEMORY.getKey(), ""); 29 | elasticProfile.put(RESERVED_MEMORY.getKey(), ""); 30 | 31 | Map errors = new MemorySettingsProfileValidator().validate(elasticProfile); 32 | 33 | assertTrue(errors.isEmpty()); 34 | } 35 | 36 | @Test 37 | public void maxMemorySetReservedMemoryEmpty() { 38 | Map elasticProfile = new HashMap<>(); 39 | elasticProfile.put(MAX_MEMORY.getKey(), "2G"); 40 | elasticProfile.put(RESERVED_MEMORY.getKey(), ""); 41 | 42 | Map errors = new MemorySettingsProfileValidator().validate(elasticProfile); 43 | 44 | assertTrue(errors.isEmpty()); 45 | } 46 | 47 | @Test 48 | public void maxMemoryIsLowerThanReservedMemory() { 49 | Map elasticProfile = new HashMap<>(); 50 | elasticProfile.put(MAX_MEMORY.getKey(), "1G"); 51 | elasticProfile.put(RESERVED_MEMORY.getKey(), "2G"); 52 | 53 | Map errors = new MemorySettingsProfileValidator().validate(elasticProfile); 54 | 55 | assertEquals(MAX_MEMORY.getKey(), errors.get("key")); 56 | assertEquals("Max memory is lower than reserved memory", errors.get("message")); 57 | } 58 | } --------------------------------------------------------------------------------