From 02b61282f391047c7f832215ba6ecc8115449bcb Mon Sep 17 00:00:00 2001 From: Ewelina Fiedorowicz Date: Wed, 25 Jan 2023 12:09:44 +0100 Subject: [PATCH] JPERF-831 Migrate CI from CircleCI to Github Actions. Change the pipeline config to use IAM role and OIDC token for assuming an identity on AWS. This change allows getting rid of the access key and secret. While applying changes I ran into a problem where some ubuntu images were not available on a particular AWS region. To fix that I had to bump both aws-resources and aws-infrastructure and some other required dependencies. --- .circleci/config.yml | 79 -------------- .github/workflows/build-and-test.yml | 100 ++++++++++++++++++ README.md | 11 +- build.gradle.kts | 2 +- gradle/dependency-locks/testCompile.lockfile | 22 ++-- .../testCompileClasspath.lockfile | 22 ++-- gradle/dependency-locks/testRuntime.lockfile | 22 ++-- .../testRuntimeClasspath.lockfile | 22 ++-- .../tools/hardware/IntegrationTestRuntime.kt | 39 +++---- .../tools/lib/infrastructure/CopiedDocker.kt | 63 ++++++++--- 10 files changed, 219 insertions(+), 163 deletions(-) delete mode 100644 .circleci/config.yml create mode 100644 .github/workflows/build-and-test.yml diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 3c678cc2..00000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,79 +0,0 @@ -version: 2 -jobs: - build: - docker: - - image: circleci/openjdk:8u181-jdk - working_directory: ~/repo - environment: - TERM: dumb - steps: - - checkout - - restore_cache: - keys: - - v1-dependencies-{{ checksum "build.gradle.kts" }} - - v1-dependencies- - - run: ./gradlew compileTestKotlin - - save_cache: - paths: - - ~/.gradle - key: v1-dependencies-{{ checksum "build.gradle.kts" }} - - run: - command: ./gradlew build - - store_artifacts: - path: ./build/reports/tests - destination: test-reports - - store_test_results: - path: ./build/test-results - test_integration: - docker: - - image: circleci/openjdk:8u181-jdk - working_directory: ~/repo - environment: - TERM: dumb - steps: - - checkout - - restore_cache: - keys: - - v1-dependencies-{{ checksum "build.gradle.kts" }} - - v1-dependencies- - - run: ./gradlew compileTestKotlin - - save_cache: - paths: - - ~/.gradle - key: v1-dependencies-{{ checksum "build.gradle.kts" }} - - run: echo ${JIRA_LICENSE} > jira-license.txt - - run: - command: USER=$CIRCLE_BUILD_URL ./gradlew testIntegration - shell: /bin/sh # skip -e, expect it to sometimes flake - no_output_timeout: 70m - - run: # rerun for flakiness - command: USER=$CIRCLE_BUILD_URL ./gradlew testIntegration # if the last run was green, this should be quick - shell: /bin/sh -e # expect the rerun to be green - - store_artifacts: - path: ./build/reports/tests - destination: test-reports - - store_test_results: - path: ./build/test-results -workflows: - version: 2 - quick: - jobs: - - build - wide: - jobs: - - trigger: - type: approval - - test_integration: - requires: - - trigger - regular: - triggers: - - schedule: - cron: "0 0 * * 0" - filters: - branches: - only: - - master - jobs: - - test_integration - diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml new file mode 100644 index 00000000..f2d94e5f --- /dev/null +++ b/.github/workflows/build-and-test.yml @@ -0,0 +1,100 @@ +name: Build and test + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Compile + uses: gradle/gradle-build-action@v2 + with: + gradle-version: wrapper + arguments: compileTestKotlin + - name: Build + uses: gradle/gradle-build-action@v2 + with: + arguments: build + - name: Upload test reports + uses: actions/upload-artifact@v3 + if: always() + with: + name: test-reports + path: ./build/reports/tests + - name: Upload test results + uses: actions/upload-artifact@v3 + if: always() + with: + name: test-results + path: ./build/test-results + build-check: + runs-on: ubuntu-latest + needs: build + steps: + - run: echo "All build jobs successful." + test-integration: + runs-on: ubuntu-latest + needs: build-check + env: + JIRA_LICENSE: '${{ secrets.JIRA_LICENSE }}' + TERM: dumb + permissions: + id-token: write + contents: read + steps: + - name: Checkout + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Prepare Jira license + run: echo $JIRA_LICENSE > jira-license.txt + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: eu-west-1 + - name: Generate workflow URL + run: echo "WORKFLOW_URL=$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" >> $GITHUB_ENV + - name: Test integration + uses: gradle/gradle-build-action@v2 + continue-on-error: true # expect it to sometimes flake + # timeout-minutes: 70 + with: + gradle-version: wrapper + arguments: testIntegration + env: + USER: ${{ env.WORKFLOW_URL }} + - name: Refresh AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + role-to-assume: ${{ secrets.AWS_ROLE_TO_ASSUME }} + aws-region: eu-west-1 + - name: Test integration # if the last run was green, this should be quick, expect the rerun to be green + uses: gradle/gradle-build-action@v2 + with: + arguments: testIntegration + env: + USER: ${{ env.WORKFLOW_URL }} + - name: Upload test reports + uses: actions/upload-artifact@v3 + if: always() + with: + name: test-reports + path: ./build/reports/tests + - name: Upload test results + uses: actions/upload-artifact@v3 + if: always() + with: + name: test-results + path: ./build/test-results diff --git a/README.md b/README.md index 6773d435..ec5404fa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ -[![CircleCI](https://circleci.com/gh/atlassian/jira-hardware-exploration.svg?style=svg)](https://circleci.com/gh/atlassian/jira-hardware-exploration) - +[![Build and test](https://github.com/atlassian/jira-hardware-exploration/actions/workflows/build-and-test.yml/badge.svg)](https://github.com/atlassian/jira-hardware-exploration/actions/workflows/build-and-test.yml) ### Automated Jira hardware recommendations This runs the entire hardware recommendation. @@ -22,9 +21,9 @@ If the Bamboo agent goes offline after 12 hours, rerun the plan. This can take 3 2. Set up AWS credentials according to [default AWS credentials] 3. Create a `jira-license.txt` file and fill it with a Jira license 4. Run `recommendHardware` Gradle task - * From terminal: `./gradlew recommendHardware` - * Or in short: `./gradlew recHar` - * Or from IntelliJ 2019+: `Run anything` (e.g. double tap `Ctrl`) and type `gradle recommendHardware` + * From terminal: `./gradlew recommendHardware` + * Or in short: `./gradlew recHar` + * Or from IntelliJ 2019+: `Run anything` (e.g. double tap `Ctrl`) and type `gradle recommendHardware` 5. Read the logs and look for results in `build/jpt-workspace` ### Caching @@ -32,7 +31,7 @@ If the Bamboo agent goes offline after 12 hours, rerun the plan. This can take 3 At the beginning of the run, the results from S3 cache (if any) is downloaded and merged with local results. Then the local results are reused. Only the results that are missing will be run. Use this to your advantage. If the build flakes, rerun to just fill in the missing subresults. -Naturally, [Bamboo] does not have any local results so it will always download the entire S3 cache. +Naturally, [Bamboo] does not have any local results so it will always download the entire S3 cache. The S3 cache requires read/write permissions to the S3 bucket, so either match AWS creds to the bucket or change the bucket in [test source]. diff --git a/build.gradle.kts b/build.gradle.kts index 149be140..73dd30b0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -70,7 +70,7 @@ dependencies { testCompile(project(":virtual-users")) testCompile("com.atlassian.performance.tools:jira-performance-tests:[3.3.0,4.0.0)") testCompile("com.atlassian.performance.tools:infrastructure:[4.14.0,5.0.0)") - testCompile("com.atlassian.performance.tools:virtual-users:[3.6.2,4.0.0)") + testCompile("com.atlassian.performance.tools:virtual-users:[3.6.2,3.12.0)") testCompile("com.atlassian.performance.tools:jira-software-actions:[1.1.0,2.0.0]") testCompile("com.atlassian.performance.tools:aws-infrastructure:[2.15.0,3.0.0)") testCompile("com.atlassian.performance.tools:aws-resources:[1.3.4,2.0.0)") diff --git a/gradle/dependency-locks/testCompile.lockfile b/gradle/dependency-locks/testCompile.lockfile index e6736a7f..d1cbe40e 100644 --- a/gradle/dependency-locks/testCompile.lockfile +++ b/gradle/dependency-locks/testCompile.lockfile @@ -13,19 +13,19 @@ com.amazonaws:aws-java-sdk-sts:1.11.817 com.amazonaws:aws-java-sdk-support:1.11.817 com.amazonaws:jmespath-java:1.11.817 com.atlassian.data:random-data:1.4.3 -com.atlassian.performance.tools:aws-infrastructure:2.21.4 -com.atlassian.performance.tools:aws-resources:1.6.1 -com.atlassian.performance.tools:concurrency:1.1.0 -com.atlassian.performance.tools:infrastructure:4.17.1 +com.atlassian.performance.tools:aws-infrastructure:2.25.8 +com.atlassian.performance.tools:aws-resources:1.9.1 +com.atlassian.performance.tools:concurrency:1.1.2 +com.atlassian.performance.tools:infrastructure:4.22.2 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.13.3 -com.atlassian.performance.tools:jira-performance-tests:3.6.0 -com.atlassian.performance.tools:jira-software-actions:1.3.3 -com.atlassian.performance.tools:jvm-tasks:1.1.0 -com.atlassian.performance.tools:report:3.8.2 -com.atlassian.performance.tools:ssh:2.3.1 +com.atlassian.performance.tools:jira-actions:3.18.0 +com.atlassian.performance.tools:jira-performance-tests:3.6.1 +com.atlassian.performance.tools:jira-software-actions:1.4.2 +com.atlassian.performance.tools:jvm-tasks:1.2.3 +com.atlassian.performance.tools:report:3.13.0 +com.atlassian.performance.tools:ssh:2.4.3 com.atlassian.performance.tools:virtual-users:3.11.1 -com.atlassian.performance.tools:workspace:2.0.1 +com.atlassian.performance.tools:workspace:2.0.2 com.atlassian.performance:selenium-js:1.0.1 com.fasterxml.jackson.core:jackson-annotations:2.6.0 com.fasterxml.jackson.core:jackson-core:2.9.4 diff --git a/gradle/dependency-locks/testCompileClasspath.lockfile b/gradle/dependency-locks/testCompileClasspath.lockfile index ec7d9a64..bb241796 100644 --- a/gradle/dependency-locks/testCompileClasspath.lockfile +++ b/gradle/dependency-locks/testCompileClasspath.lockfile @@ -12,19 +12,19 @@ com.amazonaws:aws-java-sdk-s3:1.11.817 com.amazonaws:aws-java-sdk-sts:1.11.817 com.amazonaws:aws-java-sdk-support:1.11.817 com.amazonaws:jmespath-java:1.11.817 -com.atlassian.performance.tools:aws-infrastructure:2.21.4 -com.atlassian.performance.tools:aws-resources:1.6.1 -com.atlassian.performance.tools:concurrency:1.1.0 -com.atlassian.performance.tools:infrastructure:4.17.1 +com.atlassian.performance.tools:aws-infrastructure:2.25.8 +com.atlassian.performance.tools:aws-resources:1.9.1 +com.atlassian.performance.tools:concurrency:1.1.2 +com.atlassian.performance.tools:infrastructure:4.22.2 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.13.3 -com.atlassian.performance.tools:jira-performance-tests:3.6.0 -com.atlassian.performance.tools:jira-software-actions:1.3.3 -com.atlassian.performance.tools:jvm-tasks:1.1.0 -com.atlassian.performance.tools:report:3.8.2 -com.atlassian.performance.tools:ssh:2.3.1 +com.atlassian.performance.tools:jira-actions:3.18.0 +com.atlassian.performance.tools:jira-performance-tests:3.6.1 +com.atlassian.performance.tools:jira-software-actions:1.4.2 +com.atlassian.performance.tools:jvm-tasks:1.2.3 +com.atlassian.performance.tools:report:3.13.0 +com.atlassian.performance.tools:ssh:2.4.3 com.atlassian.performance.tools:virtual-users:3.11.1 -com.atlassian.performance.tools:workspace:2.0.1 +com.atlassian.performance.tools:workspace:2.0.2 com.atlassian.performance:selenium-js:1.0.1 com.fasterxml.jackson.core:jackson-annotations:2.6.0 com.fasterxml.jackson.core:jackson-core:2.9.4 diff --git a/gradle/dependency-locks/testRuntime.lockfile b/gradle/dependency-locks/testRuntime.lockfile index e6736a7f..d1cbe40e 100644 --- a/gradle/dependency-locks/testRuntime.lockfile +++ b/gradle/dependency-locks/testRuntime.lockfile @@ -13,19 +13,19 @@ com.amazonaws:aws-java-sdk-sts:1.11.817 com.amazonaws:aws-java-sdk-support:1.11.817 com.amazonaws:jmespath-java:1.11.817 com.atlassian.data:random-data:1.4.3 -com.atlassian.performance.tools:aws-infrastructure:2.21.4 -com.atlassian.performance.tools:aws-resources:1.6.1 -com.atlassian.performance.tools:concurrency:1.1.0 -com.atlassian.performance.tools:infrastructure:4.17.1 +com.atlassian.performance.tools:aws-infrastructure:2.25.8 +com.atlassian.performance.tools:aws-resources:1.9.1 +com.atlassian.performance.tools:concurrency:1.1.2 +com.atlassian.performance.tools:infrastructure:4.22.2 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.13.3 -com.atlassian.performance.tools:jira-performance-tests:3.6.0 -com.atlassian.performance.tools:jira-software-actions:1.3.3 -com.atlassian.performance.tools:jvm-tasks:1.1.0 -com.atlassian.performance.tools:report:3.8.2 -com.atlassian.performance.tools:ssh:2.3.1 +com.atlassian.performance.tools:jira-actions:3.18.0 +com.atlassian.performance.tools:jira-performance-tests:3.6.1 +com.atlassian.performance.tools:jira-software-actions:1.4.2 +com.atlassian.performance.tools:jvm-tasks:1.2.3 +com.atlassian.performance.tools:report:3.13.0 +com.atlassian.performance.tools:ssh:2.4.3 com.atlassian.performance.tools:virtual-users:3.11.1 -com.atlassian.performance.tools:workspace:2.0.1 +com.atlassian.performance.tools:workspace:2.0.2 com.atlassian.performance:selenium-js:1.0.1 com.fasterxml.jackson.core:jackson-annotations:2.6.0 com.fasterxml.jackson.core:jackson-core:2.9.4 diff --git a/gradle/dependency-locks/testRuntimeClasspath.lockfile b/gradle/dependency-locks/testRuntimeClasspath.lockfile index e6736a7f..d1cbe40e 100644 --- a/gradle/dependency-locks/testRuntimeClasspath.lockfile +++ b/gradle/dependency-locks/testRuntimeClasspath.lockfile @@ -13,19 +13,19 @@ com.amazonaws:aws-java-sdk-sts:1.11.817 com.amazonaws:aws-java-sdk-support:1.11.817 com.amazonaws:jmespath-java:1.11.817 com.atlassian.data:random-data:1.4.3 -com.atlassian.performance.tools:aws-infrastructure:2.21.4 -com.atlassian.performance.tools:aws-resources:1.6.1 -com.atlassian.performance.tools:concurrency:1.1.0 -com.atlassian.performance.tools:infrastructure:4.17.1 +com.atlassian.performance.tools:aws-infrastructure:2.25.8 +com.atlassian.performance.tools:aws-resources:1.9.1 +com.atlassian.performance.tools:concurrency:1.1.2 +com.atlassian.performance.tools:infrastructure:4.22.2 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.13.3 -com.atlassian.performance.tools:jira-performance-tests:3.6.0 -com.atlassian.performance.tools:jira-software-actions:1.3.3 -com.atlassian.performance.tools:jvm-tasks:1.1.0 -com.atlassian.performance.tools:report:3.8.2 -com.atlassian.performance.tools:ssh:2.3.1 +com.atlassian.performance.tools:jira-actions:3.18.0 +com.atlassian.performance.tools:jira-performance-tests:3.6.1 +com.atlassian.performance.tools:jira-software-actions:1.4.2 +com.atlassian.performance.tools:jvm-tasks:1.2.3 +com.atlassian.performance.tools:report:3.13.0 +com.atlassian.performance.tools:ssh:2.4.3 com.atlassian.performance.tools:virtual-users:3.11.1 -com.atlassian.performance.tools:workspace:2.0.1 +com.atlassian.performance.tools:workspace:2.0.2 com.atlassian.performance:selenium-js:1.0.1 com.fasterxml.jackson.core:jackson-annotations:2.6.0 com.fasterxml.jackson.core:jackson-core:2.9.4 diff --git a/src/test/kotlin/com/atlassian/performance/tools/hardware/IntegrationTestRuntime.kt b/src/test/kotlin/com/atlassian/performance/tools/hardware/IntegrationTestRuntime.kt index 1d8add5c..31efa422 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/hardware/IntegrationTestRuntime.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/hardware/IntegrationTestRuntime.kt @@ -16,23 +16,24 @@ object IntegrationTestRuntime { private const val roleArn: String = "arn:aws:iam::695067801333:role/server-gdn-bamboo" private val region = Regions.EU_WEST_1 - fun prepareAws() = Aws( - credentialsProvider = AWSCredentialsProviderChain( - STSAssumeRoleSessionCredentialsProvider.Builder( - roleArn, - UUID.randomUUID().toString() - ).build(), - ProfileCredentialsProvider("jpt-dev"), - EC2ContainerCredentialsProviderWrapper(), - WebIdentityTokenCredentialsProvider.builder() - .roleArn(roleArn) - .roleSessionName(UUID.randomUUID().toString()) - .build(), - DefaultAWSCredentialsProviderChain() - ), - region = region, - regionsWithHousekeeping = listOf(Regions.EU_WEST_1), - capacity = TextCapacityMediator(region), - batchingCloudformationRefreshPeriod = Duration.ofSeconds(20) - ) + fun prepareAws() = Aws.Builder(region) + .credentialsProvider( + AWSCredentialsProviderChain( + STSAssumeRoleSessionCredentialsProvider.Builder( + roleArn, + UUID.randomUUID().toString() + ).build(), + ProfileCredentialsProvider("jpt-dev"), + EC2ContainerCredentialsProviderWrapper(), + WebIdentityTokenCredentialsProvider.builder() + .roleArn(roleArn) + .roleSessionName(UUID.randomUUID().toString()) + .build(), + DefaultAWSCredentialsProviderChain() + ) + ) + .regionsWithHousekeeping(listOf(Regions.EU_WEST_1)) + .capacity(TextCapacityMediator(region)) + .batchingCloudformationRefreshPeriod(Duration.ofSeconds(20)) + .build() } diff --git a/src/test/kotlin/com/atlassian/performance/tools/lib/infrastructure/CopiedDocker.kt b/src/test/kotlin/com/atlassian/performance/tools/lib/infrastructure/CopiedDocker.kt index 673aeaf2..3f5e1ee9 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/lib/infrastructure/CopiedDocker.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/lib/infrastructure/CopiedDocker.kt @@ -9,30 +9,65 @@ internal class CopiedDocker { private val ubuntu = Ubuntu() /** - * See the [official guide](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-docker-ce). + * See the [official guide](https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository). */ fun install( ssh: SshConnection ) { + allowAptToUseHttps(ssh) + addDockerOfficialGpgKey(ssh) + setUpTheRepository(ssh) + installDockerEngine(ssh) + startDockerIfNecessary(ssh) + } + + private fun allowAptToUseHttps(ssh: SshConnection) { + val extraPackages = when (ubuntu.getDistributionCodename(ssh)) { + "xenial", "focal" -> setOf("apt-transport-https") + else -> emptySet() + } ubuntu.install( ssh = ssh, - packages = listOf( - "apt-transport-https", - "ca-certificates", - "curl", - "software-properties-common" - ), + packages = listOf("ca-certificates", "curl", "gnupg", "lsb-release") + extraPackages, timeout = Duration.ofMinutes(2) ) - val release = ssh.execute("lsb_release -cs").output - val repository = "deb [arch=amd64] https://download.docker.com/linux/ubuntu $release stable" - ssh.execute("sudo add-apt-repository \"$repository\"") - ssh.execute("curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -") - val version = "17.09.0~ce-0~ubuntu" + } + + private fun addDockerOfficialGpgKey(ssh: SshConnection) { + ssh.execute("sudo mkdir -p /etc/apt/keyrings") + val gpgFix = "--batch" // avoids `gpg: cannot open '/dev/tty'`, Docker instructions don't have this + val gpg = "sudo gpg $gpgFix --dearmor -o /etc/apt/keyrings/docker.gpg" + val addGpg = "curl -fsSL https://download.docker.com/linux/ubuntu/gpg | $gpg" + if (ssh.safeExecute(addGpg).isSuccessful().not()) { // Docker instructions warn about this possible fail + ssh.execute("sudo chmod a+r /etc/apt/keyrings/docker.gpg") + ssh.safeExecute(addGpg) + } + } + + private fun setUpTheRepository(ssh: SshConnection) { + val arch = "arch=$(dpkg --print-architecture)" + val signed = "signed-by=/etc/apt/keyrings/docker.gpg" + val release = "$(lsb_release -cs) stable" + val source = "deb [$arch $signed] https://download.docker.com/linux/ubuntu $release" + ssh.execute("echo \"$source\" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null") + } + + private fun installDockerEngine(ssh: SshConnection) { + val missingPackages = when (ubuntu.getDistributionCodename(ssh)) { + "xenial", "focal" -> setOf("docker-compose-plugin") + else -> emptySet() + } ubuntu.install( ssh = ssh, - packages = listOf("docker-ce=$version"), + packages = listOf("docker-ce", "docker-ce-cli", "containerd.io") - missingPackages, timeout = Duration.ofMinutes(5) ) } -} \ No newline at end of file + + /** + * Sometimes it's already running. We don't want stderr spam in that case. + */ + private fun startDockerIfNecessary(ssh: SshConnection) { + ssh.execute("sudo service docker status || sudo service docker start") + } +}