From 96dabc818fa98551d3b99404e15044c733ef2a5e Mon Sep 17 00:00:00 2001 From: harryryu Date: Tue, 17 Sep 2024 13:17:53 -0700 Subject: [PATCH 1/7] Build Java Sample App with different language versions --- .../workflows/java-sample-app-ecr-deploy.yml | 121 ++++++++++--- .../workflows/java-sample-app-s3-deploy.yml | 117 +++++++++--- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +- gradlew.bat | 22 +-- .../springboot-main-service/build.gradle.kts | 70 ++++++++ .../com/amazon/sampleapp/FrontendService.java | 0 .../sampleapp/FrontendServiceController.java | 0 .../build.gradle.kts | 62 +++++++ .../com/amazon/sampleapp/RemoteService.java | 0 .../sampleapp/RemoteServiceController.java | 0 .../springboot-main-service}/build.gradle.kts | 9 +- .../com/amazon/sampleapp/FrontendService.java | 41 +++++ .../sampleapp/FrontendServiceController.java | 170 ++++++++++++++++++ .../build.gradle.kts | 9 +- .../com/amazon/sampleapp/RemoteService.java | 26 +++ .../sampleapp/RemoteServiceController.java | 30 ++++ settings.gradle.kts | 6 +- 19 files changed, 628 insertions(+), 64 deletions(-) create mode 100644 sample-apps/java/11+/springboot-main-service/build.gradle.kts rename sample-apps/{springboot => java/11+/springboot-main-service}/src/main/java/com/amazon/sampleapp/FrontendService.java (100%) rename sample-apps/{springboot => java/11+/springboot-main-service}/src/main/java/com/amazon/sampleapp/FrontendServiceController.java (100%) create mode 100644 sample-apps/java/11+/springboot-remote-service/build.gradle.kts rename sample-apps/{ => java/11+}/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java (100%) rename sample-apps/{ => java/11+}/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java (100%) rename sample-apps/{springboot => java/8/springboot-main-service}/build.gradle.kts (87%) create mode 100644 sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java create mode 100644 sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java rename sample-apps/{ => java/8}/springboot-remote-service/build.gradle.kts (85%) create mode 100644 sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java create mode 100644 sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java diff --git a/.github/workflows/java-sample-app-ecr-deploy.yml b/.github/workflows/java-sample-app-ecr-deploy.yml index 9a6f0e9f0..219676fe6 100644 --- a/.github/workflows/java-sample-app-ecr-deploy.yml +++ b/.github/workflows/java-sample-app-ecr-deploy.yml @@ -9,8 +9,12 @@ permissions: id-token: write contents: read +env: + E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} + E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} + jobs: - upload-main-service-image: + upload-java-main-service-image: strategy: fail-fast: false matrix: @@ -27,31 +31,29 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account uses: aws-actions/aws-secretsmanager-get-secrets@v1 with: - secret-ids: + secret-ids: | ACCOUNT_ID, region-account/${{ matrix.aws-region }} + JAVA_MAIN_SAMPLE_APP_IMAGE, e2e-test/java-main-sample-app-image - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Update ECR URI - working-directory: sample-apps/springboot + - name: Build and Upload Main Service Image v11 + working-directory: sample-apps/java/8/springboot-main-service run: | - sed -i 's#":"#"${{ env.ACCOUNT_ID }}.dkr.ecr.${{ matrix.aws-region }}.amazonaws.com/${{ secrets.APP_SIGNALS_E2E_FE_SA_IMG }}:latest"#g' build.gradle.kts - - - name: Upload Main Service Image - working-directory: sample-apps/springboot - run: gradle jib + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts + gradle jib -P javaVersion=11 - upload-remote-service-image: + upload-java-remote-service-image: strategy: fail-fast: false matrix: @@ -68,27 +70,104 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account uses: aws-actions/aws-secretsmanager-get-secrets@v1 with: - secret-ids: + secret-ids: | ACCOUNT_ID, region-account/${{ matrix.aws-region }} + JAVA_REMOTE_SAMPLE_APP_IMAGE, e2e-test/java-remote-sample-app-image - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Update ECR URI - working-directory: sample-apps/springboot-remote-service + - name: Build and Upload Remote Service Image v11 + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts + gradle jib -P javaVersion=11 + + upload-java-versions-main-service-image: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Retrieve account + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + JAVA_MAIN_SAMPLE_APP_IMAGE, e2e-test/java-main-sample-app-image + + - name: Build and Upload Main Service Image v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-main-service run: | - sed -i 's#":"#"${{ env.ACCOUNT_ID }}.dkr.ecr.${{ matrix.aws-region }}.amazonaws.com/${{ secrets.APP_SIGNALS_E2E_RE_SA_IMG }}:latest"#g' build.gradle.kts + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} - - name: Upload Remote Service Image - working-directory: sample-apps/springboot-remote-service - run: gradle jib + - name: Build and Upload Main Service Image v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-main-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} + + upload-java-versions-remote-service-image: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Retrieve account + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + JAVA_REMOTE_SAMPLE_APP_IMAGE, e2e-test/java-remote-sample-app-image + - name: Build and Upload Remote Service Image v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} + + - name: Build and Upload Remote Service Image v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-remote-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} diff --git a/.github/workflows/java-sample-app-s3-deploy.yml b/.github/workflows/java-sample-app-s3-deploy.yml index c5e1ad1a1..97e5e955a 100644 --- a/.github/workflows/java-sample-app-s3-deploy.yml +++ b/.github/workflows/java-sample-app-s3-deploy.yml @@ -9,15 +9,19 @@ permissions: id-token: write contents: read +env: + E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} + E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} + jobs: - upload-main-service-jar: + upload-java-v11-main-service-jar: strategy: fail-fast: false matrix: aws-region: [ 'af-south-1','ap-east-1','ap-northeast-1','ap-northeast-2','ap-northeast-3','ap-south-1','ap-south-2','ap-southeast-1', 'ap-southeast-2','ap-southeast-3','ap-southeast-4','ca-central-1','eu-central-1','eu-central-2','eu-north-1', 'eu-south-1','eu-south-2','eu-west-1','eu-west-2','eu-west-3','il-central-1','me-central-1','me-south-1', 'sa-east-1', - 'us-east-1','us-east-2', 'us-west-1', 'us-west-2' ] + 'us-east-1','us-east-2','us-west-1','us-west-2' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -27,7 +31,7 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account @@ -39,25 +43,23 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Build Main Jar - working-directory: sample-apps/springboot - run: gradle build - - - name: Upload to S3 - working-directory: sample-apps/springboot - run: aws s3api put-object --bucket ${{ secrets.APP_SIGNALS_E2E_EC2_JAR }}-prod-${{ matrix.aws-region }} --body ./build/libs/springboot-*-SNAPSHOT.jar --key main-service.jar + - name: Build and Upload Main Jar + working-directory: sample-apps/java/11+/springboot-main-service + run: | + gradle build -P javaVersion=11 + aws s3api put-object --bucket aws-appsignals-sample-app-prod-${{ matrix.aws-region }} --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v11.jar - upload-remote-service-jar: + upload-java-v11-remote-service-jar: strategy: fail-fast: false matrix: - aws-region: [ 'af-south-1','ap-east-1','ap-northeast-1','ap-northeast-2','ap-northeast-3','ap-south-1','ap-south-2','ap-southeast-1', - 'ap-southeast-2','ap-southeast-3','ap-southeast-4','ca-central-1','eu-central-1','eu-central-2','eu-north-1', - 'eu-south-1','eu-south-2','eu-west-1','eu-west-2','eu-west-3','il-central-1','me-central-1','me-south-1', 'sa-east-1', - 'us-east-1','us-east-2','us-west-1', 'us-west-2' ] + aws-region: [ 'af-south-1','ap-east-1','ap-northeast-1','ap-northeast-2','ap-northeast-3','ap-south-1','ap-south-2','ap-southeast-1', + 'ap-southeast-2','ap-southeast-3','ap-southeast-4','ca-central-1','eu-central-1','eu-central-2','eu-north-1', + 'eu-south-1','eu-south-2','eu-west-1','eu-west-2','eu-west-3','il-central-1','me-central-1','me-south-1', 'sa-east-1', + 'us-east-1','us-east-2','us-west-1','us-west-2' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -67,7 +69,7 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account @@ -79,14 +81,81 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Build Main Jar - working-directory: sample-apps/springboot-remote-service - run: gradle build + - name: Build and Upload Main Jar + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + gradle build -P javaVersion=11 + aws s3api put-object --bucket aws-appsignals-sample-app-prod-${{ matrix.aws-region }} --body build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v11.jar + + upload-java-versions-main-service-jar: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Build Main Jar v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-main-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v${{ matrix.java-version }}.jar + + - name: Build Main Jar v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-main-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v${{ matrix.java-version }}.jar + + upload-java-versions-remote-service-jar: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Build and Upload Remote Jar v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v${{ matrix.java-version }}.jar + + - name: Build and Upload Remote Jar v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-remote-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v${{ matrix.java-version }}.jar - - name: Upload to S3 - working-directory: sample-apps/springboot-remote-service - run: aws s3api put-object --bucket ${{ secrets.APP_SIGNALS_E2E_EC2_JAR }}-prod-${{ matrix.aws-region }} --body build/libs/springboot-remote-service-*-SNAPSHOT.jar --key remote-service.jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 43462 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930..0aaefbcaf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/sample-apps/java/11+/springboot-main-service/build.gradle.kts b/sample-apps/java/11+/springboot-main-service/build.gradle.kts new file mode 100644 index 000000000..d5904b19e --- /dev/null +++ b/sample-apps/java/11+/springboot-main-service/build.gradle.kts @@ -0,0 +1,70 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +val javaVersion = if (project.hasProperty("javaVersion")) { + JavaVersion.toVersion(project.property("javaVersion").toString()) +} else { + JavaVersion.VERSION_11 +} + +plugins { + java + application + id("org.springframework.boot") + id("io.spring.dependency-management") version "1.1.0" + id("com.google.cloud.tools.jib") + id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" +} + + +group = "com.amazon.sampleapp" +version = "0.0.1-SNAPSHOT" +java.sourceCompatibility = javaVersion +java.targetCompatibility = javaVersion + +repositories { + mavenCentral() +} + +dependencies { + implementation(platform("software.amazon.awssdk:bom:2.20.78")) + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-logging") + implementation("io.opentelemetry:opentelemetry-api:1.34.1") + implementation("software.amazon.awssdk:s3") + implementation("software.amazon.awssdk:sts") + implementation("com.mysql:mysql-connector-j:8.0.33") + implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20") + testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") +} + +jib { + from { + image = "openjdk:$javaVersion-jdk" + } + + to { + image = ":" + } + + container { + mainClass = "com.amazon.sampleapp.FrontendService" + ports = listOf("8080") + } +} + +application { + mainClass.set("com.amazon.sampleapp.FrontendService") +} diff --git a/sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendService.java b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java similarity index 100% rename from sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendService.java rename to sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java diff --git a/sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java similarity index 100% rename from sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendServiceController.java rename to sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java diff --git a/sample-apps/java/11+/springboot-remote-service/build.gradle.kts b/sample-apps/java/11+/springboot-remote-service/build.gradle.kts new file mode 100644 index 000000000..93eeace8c --- /dev/null +++ b/sample-apps/java/11+/springboot-remote-service/build.gradle.kts @@ -0,0 +1,62 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +val javaVersion = if (project.hasProperty("javaVersion")) { + JavaVersion.toVersion(project.property("javaVersion").toString()) +} else { + JavaVersion.VERSION_11 +} + +plugins { + java + application + id("org.springframework.boot") + id("io.spring.dependency-management") version "1.1.0" + id("com.google.cloud.tools.jib") +} + +group = "com.amazon.sampleapp" +version = "0.0.1-SNAPSHOT" +java.sourceCompatibility = javaVersion +java.targetCompatibility = javaVersion + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-logging") + implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20") + testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") +} + +jib { + from { + image = "openjdk:$javaVersion-jdk" + } + + to { + image = ":" + } + container { + mainClass = "com.amazon.sampleapp.RemoteService" + ports = listOf("8080") + } +} + +application { + mainClass.set("com.amazon.sampleapp.RemoteService") +} \ No newline at end of file diff --git a/sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java b/sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java similarity index 100% rename from sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java rename to sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java diff --git a/sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java b/sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java similarity index 100% rename from sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java rename to sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java diff --git a/sample-apps/springboot/build.gradle.kts b/sample-apps/java/8/springboot-main-service/build.gradle.kts similarity index 87% rename from sample-apps/springboot/build.gradle.kts rename to sample-apps/java/8/springboot-main-service/build.gradle.kts index 242e72135..f5700445f 100644 --- a/sample-apps/springboot/build.gradle.kts +++ b/sample-apps/java/8/springboot-main-service/build.gradle.kts @@ -23,8 +23,8 @@ plugins { group = "com.amazon.sampleapp" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_11 +java.sourceCompatibility = JavaVersion.VERSION_1_8 +java.targetCompatibility = JavaVersion.VERSION_1_8 repositories { mavenCentral() @@ -38,9 +38,14 @@ dependencies { implementation("software.amazon.awssdk:s3") implementation("software.amazon.awssdk:sts") implementation("com.mysql:mysql-connector-j:8.0.33") + implementation ("org.apache.httpcomponents:httpclient:4.5.13") } jib { + from { + image = "openjdk:8-jdk" + } + to { image = ":" } diff --git a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java new file mode 100644 index 000000000..d217dc403 --- /dev/null +++ b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import software.amazon.awssdk.services.s3.S3Client; + +@SpringBootApplication +public class FrontendService { + + @Bean + public CloseableHttpClient httpClient() { + return HttpClients.createDefault(); + } + + @Bean + public S3Client s3() { + return S3Client.builder().build(); + } + + public static void main(String[] args) { + SpringApplication.run(FrontendService.class, args); + } +} diff --git a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java new file mode 100644 index 000000000..a53431082 --- /dev/null +++ b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -0,0 +1,170 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import io.opentelemetry.api.trace.Span; +import java.net.URI; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Connection; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetBucketLocationRequest; + +@Controller +public class FrontendServiceController { + private static final Logger logger = LoggerFactory.getLogger(FrontendServiceController.class); + private final CloseableHttpClient httpClient; + private final S3Client s3; + private AtomicBoolean shouldSendLocalRootClientCall = new AtomicBoolean(false); + + @Bean + private void runLocalRootClientCallRecurringService() { // run the service + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + + Runnable runnableTask = + () -> { + if (shouldSendLocalRootClientCall.get()) { + shouldSendLocalRootClientCall.set(false); + HttpGet request = new HttpGet("http://local-root-client-call"); + try (CloseableHttpResponse response = httpClient.execute(request)) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + logger.info(EntityUtils.toString(entity)); + } + } catch (Exception e) { + logger.error("Error in recurring task: {}", e.getMessage()); + } + } + }; + // Run with initial 0.1s delay, every 1 second + executorService.scheduleAtFixedRate(runnableTask, 100, 1000, TimeUnit.MILLISECONDS); + } + + @Autowired + public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { + this.httpClient = httpClient; + this.s3 = s3; + } + + @GetMapping("/") + @ResponseBody + public String healthcheck() { + return "healthcheck"; + } + + // test aws calls instrumentation + @GetMapping("/aws-sdk-call") + @ResponseBody + public String awssdkCall(@RequestParam(name = "testingId", required = false) String testingId) { + String bucketName = "e2e-test-bucket-name"; + if (testingId != null) { + bucketName += "-" + testingId; + } + GetBucketLocationRequest bucketLocationRequest = + GetBucketLocationRequest.builder().bucket(bucketName).build(); + try { + s3.getBucketLocation(bucketLocationRequest); + } catch (Exception e) { + logger.error("Error occurred when trying to get bucket location of: " + bucketName, e); + } + return getXrayTraceId(); + } + + // test http instrumentation (Apache HttpClient for Java 8) + @GetMapping("/outgoing-http-call") + @ResponseBody + public String httpCall() { + HttpGet request = new HttpGet("https://www.amazon.com"); + try (CloseableHttpResponse response = httpClient.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + logger.info("outgoing-http-call status code: " + statusCode); + } catch (Exception e) { + logger.error("Could not complete HTTP request: {}", e.getMessage()); + } + return getXrayTraceId(); + } + + // RemoteService must also be deployed to use this API + @GetMapping("/remote-service") + @ResponseBody + public String downstreamService(@RequestParam("ip") String ip) { + ip = ip.replace("/", ""); + HttpGet request = new HttpGet("http://" + ip + ":8080/healthcheck"); + try (CloseableHttpResponse response = httpClient.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + logger.info("Remote service call status code: " + statusCode); + return getXrayTraceId(); + } catch (Exception e) { + logger.error("Could not complete HTTP request to remote service: {}", e.getMessage()); + } + return getXrayTraceId(); + } + + // Test Local Root Client Span generation + @GetMapping("/client-call") + @ResponseBody + public String asyncService() { + logger.info("Client-call received"); + shouldSendLocalRootClientCall.set(true); + return "{\"traceId\": \"1-00000000-000000000000000000000000\"}"; + } + + // Uses the /mysql endpoint to make an SQL call + @GetMapping("/mysql") + @ResponseBody + public String mysql() { + logger.info("mysql received"); + try { + Connection connection = DriverManager.getConnection( + System.getenv("RDS_MYSQL_CLUSTER_CONNECTION_URL"), + System.getenv("RDS_MYSQL_CLUSTER_USERNAME"), + System.getenv("RDS_MYSQL_CLUSTER_PASSWORD")); + Statement statement = connection.createStatement(); + statement.executeQuery("SELECT * FROM tables LIMIT 1;"); + } catch (SQLException e) { + logger.error("Could not complete SQL request: {}", e.getMessage()); + throw new RuntimeException(e); + } + return getXrayTraceId(); + } + + // get x-ray trace id + private String getXrayTraceId() { + String traceId = Span.current().getSpanContext().getTraceId(); + String xrayTraceId = "1-" + traceId.substring(0, 8) + "-" + traceId.substring(8); + return String.format("{\"traceId\": \"%s\"}", xrayTraceId); + } +} diff --git a/sample-apps/springboot-remote-service/build.gradle.kts b/sample-apps/java/8/springboot-remote-service/build.gradle.kts similarity index 85% rename from sample-apps/springboot-remote-service/build.gradle.kts rename to sample-apps/java/8/springboot-remote-service/build.gradle.kts index a652cc928..213bda263 100644 --- a/sample-apps/springboot-remote-service/build.gradle.kts +++ b/sample-apps/java/8/springboot-remote-service/build.gradle.kts @@ -23,8 +23,8 @@ plugins { group = "com.amazon.sampleapp" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_11 +java.sourceCompatibility = JavaVersion.VERSION_1_8 +java.targetCompatibility = JavaVersion.VERSION_1_8 repositories { mavenCentral() @@ -33,9 +33,14 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-logging") + implementation ("org.apache.httpcomponents:httpclient:4.5.13") } jib { + from { + image = "openjdk:8-jdk" + } + to { image = ":" } diff --git a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java new file mode 100644 index 000000000..01076f264 --- /dev/null +++ b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RemoteService { + public static void main(String[] args) { + SpringApplication.run(RemoteService.class, args); + } +} diff --git a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java new file mode 100644 index 000000000..cf71fbe4c --- /dev/null +++ b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class RemoteServiceController { + + @GetMapping("/healthcheck") + @ResponseBody + public String healthcheck() { + return "Remote service healthcheck"; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 87957e1d8..9cef7f2e7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,8 +26,10 @@ buildCache { // End to end tests include(":validator") -include(":sample-apps:springboot") -include(":sample-apps:springboot-remote-service") +include(":sample-apps:java:8:springboot-main-service") +include(":sample-apps:java:8:springboot-remote-service") +include(":sample-apps:java:11+:springboot-main-service") +include(":sample-apps:java:11+:springboot-remote-service") //id("com.diffplug.spotless") version "6.22.0" //id("com.github.ben-manes.versions") version "0.50.0" From b5c5748df16ce647d6f7dd63f7c6e53a895e5d6b Mon Sep 17 00:00:00 2001 From: harryryu Date: Tue, 17 Sep 2024 13:17:53 -0700 Subject: [PATCH 2/7] Build Java Sample App with different language versions --- .../workflows/java-sample-app-ecr-deploy.yml | 121 ++++++++++--- .../workflows/java-sample-app-s3-deploy.yml | 117 +++++++++--- gradle/wrapper/gradle-wrapper.jar | Bin 43462 -> 0 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 7 +- gradlew.bat | 22 +-- .../springboot-main-service/build.gradle.kts | 70 ++++++++ .../com/amazon/sampleapp/FrontendService.java | 0 .../sampleapp/FrontendServiceController.java | 0 .../build.gradle.kts | 62 +++++++ .../com/amazon/sampleapp/RemoteService.java | 0 .../sampleapp/RemoteServiceController.java | 0 .../springboot-main-service}/build.gradle.kts | 9 +- .../com/amazon/sampleapp/FrontendService.java | 41 +++++ .../sampleapp/FrontendServiceController.java | 170 ++++++++++++++++++ .../build.gradle.kts | 9 +- .../com/amazon/sampleapp/RemoteService.java | 26 +++ .../sampleapp/RemoteServiceController.java | 30 ++++ settings.gradle.kts | 6 +- 19 files changed, 628 insertions(+), 64 deletions(-) create mode 100644 sample-apps/java/11+/springboot-main-service/build.gradle.kts rename sample-apps/{springboot => java/11+/springboot-main-service}/src/main/java/com/amazon/sampleapp/FrontendService.java (100%) rename sample-apps/{springboot => java/11+/springboot-main-service}/src/main/java/com/amazon/sampleapp/FrontendServiceController.java (100%) create mode 100644 sample-apps/java/11+/springboot-remote-service/build.gradle.kts rename sample-apps/{ => java/11+}/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java (100%) rename sample-apps/{ => java/11+}/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java (100%) rename sample-apps/{springboot => java/8/springboot-main-service}/build.gradle.kts (87%) create mode 100644 sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java create mode 100644 sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java rename sample-apps/{ => java/8}/springboot-remote-service/build.gradle.kts (85%) create mode 100644 sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java create mode 100644 sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java diff --git a/.github/workflows/java-sample-app-ecr-deploy.yml b/.github/workflows/java-sample-app-ecr-deploy.yml index 9a6f0e9f0..219676fe6 100644 --- a/.github/workflows/java-sample-app-ecr-deploy.yml +++ b/.github/workflows/java-sample-app-ecr-deploy.yml @@ -9,8 +9,12 @@ permissions: id-token: write contents: read +env: + E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} + E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} + jobs: - upload-main-service-image: + upload-java-main-service-image: strategy: fail-fast: false matrix: @@ -27,31 +31,29 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account uses: aws-actions/aws-secretsmanager-get-secrets@v1 with: - secret-ids: + secret-ids: | ACCOUNT_ID, region-account/${{ matrix.aws-region }} + JAVA_MAIN_SAMPLE_APP_IMAGE, e2e-test/java-main-sample-app-image - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Update ECR URI - working-directory: sample-apps/springboot + - name: Build and Upload Main Service Image v11 + working-directory: sample-apps/java/8/springboot-main-service run: | - sed -i 's#":"#"${{ env.ACCOUNT_ID }}.dkr.ecr.${{ matrix.aws-region }}.amazonaws.com/${{ secrets.APP_SIGNALS_E2E_FE_SA_IMG }}:latest"#g' build.gradle.kts - - - name: Upload Main Service Image - working-directory: sample-apps/springboot - run: gradle jib + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts + gradle jib -P javaVersion=11 - upload-remote-service-image: + upload-java-remote-service-image: strategy: fail-fast: false matrix: @@ -68,27 +70,104 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account uses: aws-actions/aws-secretsmanager-get-secrets@v1 with: - secret-ids: + secret-ids: | ACCOUNT_ID, region-account/${{ matrix.aws-region }} + JAVA_REMOTE_SAMPLE_APP_IMAGE, e2e-test/java-remote-sample-app-image - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Update ECR URI - working-directory: sample-apps/springboot-remote-service + - name: Build and Upload Remote Service Image v11 + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts + gradle jib -P javaVersion=11 + + upload-java-versions-main-service-image: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Retrieve account + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + JAVA_MAIN_SAMPLE_APP_IMAGE, e2e-test/java-main-sample-app-image + + - name: Build and Upload Main Service Image v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-main-service run: | - sed -i 's#":"#"${{ env.ACCOUNT_ID }}.dkr.ecr.${{ matrix.aws-region }}.amazonaws.com/${{ secrets.APP_SIGNALS_E2E_RE_SA_IMG }}:latest"#g' build.gradle.kts + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} - - name: Upload Remote Service Image - working-directory: sample-apps/springboot-remote-service - run: gradle jib + - name: Build and Upload Main Service Image v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-main-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} + + upload-java-versions-remote-service-image: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Retrieve account + uses: aws-actions/aws-secretsmanager-get-secrets@v2 + with: + secret-ids: | + JAVA_REMOTE_SAMPLE_APP_IMAGE, e2e-test/java-remote-sample-app-image + - name: Build and Upload Remote Service Image v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} + + - name: Build and Upload Remote Service Image v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-remote-service + run: | + sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + gradle jib -P javaVersion=${{ matrix.java-version }} diff --git a/.github/workflows/java-sample-app-s3-deploy.yml b/.github/workflows/java-sample-app-s3-deploy.yml index c5e1ad1a1..97e5e955a 100644 --- a/.github/workflows/java-sample-app-s3-deploy.yml +++ b/.github/workflows/java-sample-app-s3-deploy.yml @@ -9,15 +9,19 @@ permissions: id-token: write contents: read +env: + E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} + E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} + jobs: - upload-main-service-jar: + upload-java-v11-main-service-jar: strategy: fail-fast: false matrix: aws-region: [ 'af-south-1','ap-east-1','ap-northeast-1','ap-northeast-2','ap-northeast-3','ap-south-1','ap-south-2','ap-southeast-1', 'ap-southeast-2','ap-southeast-3','ap-southeast-4','ca-central-1','eu-central-1','eu-central-2','eu-north-1', 'eu-south-1','eu-south-2','eu-west-1','eu-west-2','eu-west-3','il-central-1','me-central-1','me-south-1', 'sa-east-1', - 'us-east-1','us-east-2', 'us-west-1', 'us-west-2' ] + 'us-east-1','us-east-2','us-west-1','us-west-2' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -27,7 +31,7 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account @@ -39,25 +43,23 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Build Main Jar - working-directory: sample-apps/springboot - run: gradle build - - - name: Upload to S3 - working-directory: sample-apps/springboot - run: aws s3api put-object --bucket ${{ secrets.APP_SIGNALS_E2E_EC2_JAR }}-prod-${{ matrix.aws-region }} --body ./build/libs/springboot-*-SNAPSHOT.jar --key main-service.jar + - name: Build and Upload Main Jar + working-directory: sample-apps/java/11+/springboot-main-service + run: | + gradle build -P javaVersion=11 + aws s3api put-object --bucket aws-appsignals-sample-app-prod-${{ matrix.aws-region }} --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v11.jar - upload-remote-service-jar: + upload-java-v11-remote-service-jar: strategy: fail-fast: false matrix: - aws-region: [ 'af-south-1','ap-east-1','ap-northeast-1','ap-northeast-2','ap-northeast-3','ap-south-1','ap-south-2','ap-southeast-1', - 'ap-southeast-2','ap-southeast-3','ap-southeast-4','ca-central-1','eu-central-1','eu-central-2','eu-north-1', - 'eu-south-1','eu-south-2','eu-west-1','eu-west-2','eu-west-3','il-central-1','me-central-1','me-south-1', 'sa-east-1', - 'us-east-1','us-east-2','us-west-1', 'us-west-2' ] + aws-region: [ 'af-south-1','ap-east-1','ap-northeast-1','ap-northeast-2','ap-northeast-3','ap-south-1','ap-south-2','ap-southeast-1', + 'ap-southeast-2','ap-southeast-3','ap-southeast-4','ca-central-1','eu-central-1','eu-central-2','eu-north-1', + 'eu-south-1','eu-south-2','eu-west-1','eu-west-2','eu-west-3','il-central-1','me-central-1','me-south-1', 'sa-east-1', + 'us-east-1','us-east-2','us-west-1','us-west-2' ] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -67,7 +69,7 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: ${{ secrets.E2E_IAD_TEST_ACCOUNT_ARN }} + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - name: Retrieve account @@ -79,14 +81,81 @@ jobs: - name: Configure AWS Credentials uses: aws-actions/configure-aws-credentials@v4 with: - role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ secrets.E2E_TEST_ROLE_ARN }} + role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Build Main Jar - working-directory: sample-apps/springboot-remote-service - run: gradle build + - name: Build and Upload Main Jar + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + gradle build -P javaVersion=11 + aws s3api put-object --bucket aws-appsignals-sample-app-prod-${{ matrix.aws-region }} --body build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v11.jar + + upload-java-versions-main-service-jar: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Build Main Jar v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-main-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v${{ matrix.java-version }}.jar + + - name: Build Main Jar v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-main-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v${{ matrix.java-version }}.jar + + upload-java-versions-remote-service-jar: + strategy: + fail-fast: false + matrix: + java-version: [ '8', '17', '21', '22' ] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: ${{ matrix.java-version }} + check-latest: true + + - name: Configure AWS Credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} + aws-region: us-east-1 + + - name: Build and Upload Remote Jar v11+ + if: ${{ matrix.java-version != 8 }} + working-directory: sample-apps/java/11+/springboot-remote-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v${{ matrix.java-version }}.jar + + - name: Build and Upload Remote Jar v8 + if: ${{ matrix.java-version == 8 }} + working-directory: sample-apps/java/8/springboot-remote-service + run: | + gradle build -P javaVersion=${{ matrix.java-version }} + aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v${{ matrix.java-version }}.jar - - name: Upload to S3 - working-directory: sample-apps/springboot-remote-service - run: aws s3api put-object --bucket ${{ secrets.APP_SIGNALS_E2E_EC2_JAR }}-prod-${{ matrix.aws-region }} --body build/libs/springboot-remote-service-*-SNAPSHOT.jar --key remote-service.jar diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index d64cd4917707c1f8861d8cb53dd15194d4248596..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 100644 GIT binary patch literal 0 HcmV?d00001 literal 43462 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1af9e0930..0aaefbcaf 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 1aa94a426..f5feea6d6 100755 --- a/gradlew +++ b/gradlew @@ -15,6 +15,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # +# SPDX-License-Identifier: Apache-2.0 +# ############################################################################## # @@ -55,7 +57,7 @@ # Darwin, MinGW, and NonStop. # # (3) This script is generated from the Groovy template -# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt # within the Gradle project. # # You can find Gradle at https://github.com/gradle/gradle/. @@ -84,7 +86,8 @@ done # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) -APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..9d21a2183 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -13,6 +13,8 @@ @rem See the License for the specific language governing permissions and @rem limitations under the License. @rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem @if "%DEBUG%"=="" @echo off @rem ########################################################################## @@ -43,11 +45,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/sample-apps/java/11+/springboot-main-service/build.gradle.kts b/sample-apps/java/11+/springboot-main-service/build.gradle.kts new file mode 100644 index 000000000..d5904b19e --- /dev/null +++ b/sample-apps/java/11+/springboot-main-service/build.gradle.kts @@ -0,0 +1,70 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +val javaVersion = if (project.hasProperty("javaVersion")) { + JavaVersion.toVersion(project.property("javaVersion").toString()) +} else { + JavaVersion.VERSION_11 +} + +plugins { + java + application + id("org.springframework.boot") + id("io.spring.dependency-management") version "1.1.0" + id("com.google.cloud.tools.jib") + id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" +} + + +group = "com.amazon.sampleapp" +version = "0.0.1-SNAPSHOT" +java.sourceCompatibility = javaVersion +java.targetCompatibility = javaVersion + +repositories { + mavenCentral() +} + +dependencies { + implementation(platform("software.amazon.awssdk:bom:2.20.78")) + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-logging") + implementation("io.opentelemetry:opentelemetry-api:1.34.1") + implementation("software.amazon.awssdk:s3") + implementation("software.amazon.awssdk:sts") + implementation("com.mysql:mysql-connector-j:8.0.33") + implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20") + testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") +} + +jib { + from { + image = "openjdk:$javaVersion-jdk" + } + + to { + image = ":" + } + + container { + mainClass = "com.amazon.sampleapp.FrontendService" + ports = listOf("8080") + } +} + +application { + mainClass.set("com.amazon.sampleapp.FrontendService") +} diff --git a/sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendService.java b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java similarity index 100% rename from sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendService.java rename to sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java diff --git a/sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java similarity index 100% rename from sample-apps/springboot/src/main/java/com/amazon/sampleapp/FrontendServiceController.java rename to sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java diff --git a/sample-apps/java/11+/springboot-remote-service/build.gradle.kts b/sample-apps/java/11+/springboot-remote-service/build.gradle.kts new file mode 100644 index 000000000..93eeace8c --- /dev/null +++ b/sample-apps/java/11+/springboot-remote-service/build.gradle.kts @@ -0,0 +1,62 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +val javaVersion = if (project.hasProperty("javaVersion")) { + JavaVersion.toVersion(project.property("javaVersion").toString()) +} else { + JavaVersion.VERSION_11 +} + +plugins { + java + application + id("org.springframework.boot") + id("io.spring.dependency-management") version "1.1.0" + id("com.google.cloud.tools.jib") +} + +group = "com.amazon.sampleapp" +version = "0.0.1-SNAPSHOT" +java.sourceCompatibility = javaVersion +java.targetCompatibility = javaVersion + +repositories { + mavenCentral() +} + +dependencies { + implementation("org.springframework.boot:spring-boot-starter-web") + implementation("org.springframework.boot:spring-boot-starter-logging") + implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20") + testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") +} + +jib { + from { + image = "openjdk:$javaVersion-jdk" + } + + to { + image = ":" + } + container { + mainClass = "com.amazon.sampleapp.RemoteService" + ports = listOf("8080") + } +} + +application { + mainClass.set("com.amazon.sampleapp.RemoteService") +} \ No newline at end of file diff --git a/sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java b/sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java similarity index 100% rename from sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java rename to sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java diff --git a/sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java b/sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java similarity index 100% rename from sample-apps/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java rename to sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java diff --git a/sample-apps/springboot/build.gradle.kts b/sample-apps/java/8/springboot-main-service/build.gradle.kts similarity index 87% rename from sample-apps/springboot/build.gradle.kts rename to sample-apps/java/8/springboot-main-service/build.gradle.kts index 242e72135..f5700445f 100644 --- a/sample-apps/springboot/build.gradle.kts +++ b/sample-apps/java/8/springboot-main-service/build.gradle.kts @@ -23,8 +23,8 @@ plugins { group = "com.amazon.sampleapp" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_11 +java.sourceCompatibility = JavaVersion.VERSION_1_8 +java.targetCompatibility = JavaVersion.VERSION_1_8 repositories { mavenCentral() @@ -38,9 +38,14 @@ dependencies { implementation("software.amazon.awssdk:s3") implementation("software.amazon.awssdk:sts") implementation("com.mysql:mysql-connector-j:8.0.33") + implementation ("org.apache.httpcomponents:httpclient:4.5.13") } jib { + from { + image = "openjdk:8-jdk" + } + to { image = ":" } diff --git a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java new file mode 100644 index 000000000..d217dc403 --- /dev/null +++ b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java @@ -0,0 +1,41 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import software.amazon.awssdk.services.s3.S3Client; + +@SpringBootApplication +public class FrontendService { + + @Bean + public CloseableHttpClient httpClient() { + return HttpClients.createDefault(); + } + + @Bean + public S3Client s3() { + return S3Client.builder().build(); + } + + public static void main(String[] args) { + SpringApplication.run(FrontendService.class, args); + } +} diff --git a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java new file mode 100644 index 000000000..a53431082 --- /dev/null +++ b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -0,0 +1,170 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import io.opentelemetry.api.trace.Span; +import java.net.URI; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.sql.Connection; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.http.HttpEntity; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.GetBucketLocationRequest; + +@Controller +public class FrontendServiceController { + private static final Logger logger = LoggerFactory.getLogger(FrontendServiceController.class); + private final CloseableHttpClient httpClient; + private final S3Client s3; + private AtomicBoolean shouldSendLocalRootClientCall = new AtomicBoolean(false); + + @Bean + private void runLocalRootClientCallRecurringService() { // run the service + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + + Runnable runnableTask = + () -> { + if (shouldSendLocalRootClientCall.get()) { + shouldSendLocalRootClientCall.set(false); + HttpGet request = new HttpGet("http://local-root-client-call"); + try (CloseableHttpResponse response = httpClient.execute(request)) { + HttpEntity entity = response.getEntity(); + if (entity != null) { + logger.info(EntityUtils.toString(entity)); + } + } catch (Exception e) { + logger.error("Error in recurring task: {}", e.getMessage()); + } + } + }; + // Run with initial 0.1s delay, every 1 second + executorService.scheduleAtFixedRate(runnableTask, 100, 1000, TimeUnit.MILLISECONDS); + } + + @Autowired + public FrontendServiceController(CloseableHttpClient httpClient, S3Client s3) { + this.httpClient = httpClient; + this.s3 = s3; + } + + @GetMapping("/") + @ResponseBody + public String healthcheck() { + return "healthcheck"; + } + + // test aws calls instrumentation + @GetMapping("/aws-sdk-call") + @ResponseBody + public String awssdkCall(@RequestParam(name = "testingId", required = false) String testingId) { + String bucketName = "e2e-test-bucket-name"; + if (testingId != null) { + bucketName += "-" + testingId; + } + GetBucketLocationRequest bucketLocationRequest = + GetBucketLocationRequest.builder().bucket(bucketName).build(); + try { + s3.getBucketLocation(bucketLocationRequest); + } catch (Exception e) { + logger.error("Error occurred when trying to get bucket location of: " + bucketName, e); + } + return getXrayTraceId(); + } + + // test http instrumentation (Apache HttpClient for Java 8) + @GetMapping("/outgoing-http-call") + @ResponseBody + public String httpCall() { + HttpGet request = new HttpGet("https://www.amazon.com"); + try (CloseableHttpResponse response = httpClient.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + logger.info("outgoing-http-call status code: " + statusCode); + } catch (Exception e) { + logger.error("Could not complete HTTP request: {}", e.getMessage()); + } + return getXrayTraceId(); + } + + // RemoteService must also be deployed to use this API + @GetMapping("/remote-service") + @ResponseBody + public String downstreamService(@RequestParam("ip") String ip) { + ip = ip.replace("/", ""); + HttpGet request = new HttpGet("http://" + ip + ":8080/healthcheck"); + try (CloseableHttpResponse response = httpClient.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + logger.info("Remote service call status code: " + statusCode); + return getXrayTraceId(); + } catch (Exception e) { + logger.error("Could not complete HTTP request to remote service: {}", e.getMessage()); + } + return getXrayTraceId(); + } + + // Test Local Root Client Span generation + @GetMapping("/client-call") + @ResponseBody + public String asyncService() { + logger.info("Client-call received"); + shouldSendLocalRootClientCall.set(true); + return "{\"traceId\": \"1-00000000-000000000000000000000000\"}"; + } + + // Uses the /mysql endpoint to make an SQL call + @GetMapping("/mysql") + @ResponseBody + public String mysql() { + logger.info("mysql received"); + try { + Connection connection = DriverManager.getConnection( + System.getenv("RDS_MYSQL_CLUSTER_CONNECTION_URL"), + System.getenv("RDS_MYSQL_CLUSTER_USERNAME"), + System.getenv("RDS_MYSQL_CLUSTER_PASSWORD")); + Statement statement = connection.createStatement(); + statement.executeQuery("SELECT * FROM tables LIMIT 1;"); + } catch (SQLException e) { + logger.error("Could not complete SQL request: {}", e.getMessage()); + throw new RuntimeException(e); + } + return getXrayTraceId(); + } + + // get x-ray trace id + private String getXrayTraceId() { + String traceId = Span.current().getSpanContext().getTraceId(); + String xrayTraceId = "1-" + traceId.substring(0, 8) + "-" + traceId.substring(8); + return String.format("{\"traceId\": \"%s\"}", xrayTraceId); + } +} diff --git a/sample-apps/springboot-remote-service/build.gradle.kts b/sample-apps/java/8/springboot-remote-service/build.gradle.kts similarity index 85% rename from sample-apps/springboot-remote-service/build.gradle.kts rename to sample-apps/java/8/springboot-remote-service/build.gradle.kts index a652cc928..213bda263 100644 --- a/sample-apps/springboot-remote-service/build.gradle.kts +++ b/sample-apps/java/8/springboot-remote-service/build.gradle.kts @@ -23,8 +23,8 @@ plugins { group = "com.amazon.sampleapp" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_11 -java.targetCompatibility = JavaVersion.VERSION_11 +java.sourceCompatibility = JavaVersion.VERSION_1_8 +java.targetCompatibility = JavaVersion.VERSION_1_8 repositories { mavenCentral() @@ -33,9 +33,14 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-logging") + implementation ("org.apache.httpcomponents:httpclient:4.5.13") } jib { + from { + image = "openjdk:8-jdk" + } + to { image = ":" } diff --git a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java new file mode 100644 index 000000000..01076f264 --- /dev/null +++ b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java @@ -0,0 +1,26 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class RemoteService { + public static void main(String[] args) { + SpringApplication.run(RemoteService.class, args); + } +} diff --git a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java new file mode 100644 index 000000000..cf71fbe4c --- /dev/null +++ b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java @@ -0,0 +1,30 @@ +/* + * Copyright Amazon.com, Inc. or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazon.sampleapp; + +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; + +@Controller +public class RemoteServiceController { + + @GetMapping("/healthcheck") + @ResponseBody + public String healthcheck() { + return "Remote service healthcheck"; + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 87957e1d8..9cef7f2e7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,8 +26,10 @@ buildCache { // End to end tests include(":validator") -include(":sample-apps:springboot") -include(":sample-apps:springboot-remote-service") +include(":sample-apps:java:8:springboot-main-service") +include(":sample-apps:java:8:springboot-remote-service") +include(":sample-apps:java:11+:springboot-main-service") +include(":sample-apps:java:11+:springboot-remote-service") //id("com.diffplug.spotless") version "6.22.0" //id("com.github.ben-manes.versions") version "0.50.0" From 6756273a291071a9979089fa624dd451adb432b0 Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 23 Sep 2024 16:01:05 -0700 Subject: [PATCH 3/7] Add mysql changes --- .../java/11+/springboot-main-service/build.gradle.kts | 2 +- .../java/com/amazon/sampleapp/FrontendServiceController.java | 5 ++++- sample-apps/java/8/springboot-main-service/build.gradle.kts | 2 +- .../java/com/amazon/sampleapp/FrontendServiceController.java | 4 +++- 4 files changed, 9 insertions(+), 4 deletions(-) diff --git a/sample-apps/java/11+/springboot-main-service/build.gradle.kts b/sample-apps/java/11+/springboot-main-service/build.gradle.kts index d5904b19e..e444634a8 100644 --- a/sample-apps/java/11+/springboot-main-service/build.gradle.kts +++ b/sample-apps/java/11+/springboot-main-service/build.gradle.kts @@ -45,7 +45,7 @@ dependencies { implementation("io.opentelemetry:opentelemetry-api:1.34.1") implementation("software.amazon.awssdk:s3") implementation("software.amazon.awssdk:sts") - implementation("com.mysql:mysql-connector-j:8.0.33") + implementation("com.mysql:mysql-connector-j:8.4.0") implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20") testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") } diff --git a/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java index 5ab0b8fdf..3628755d0 100644 --- a/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java +++ b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -29,6 +29,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.tomcat.util.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -170,11 +171,13 @@ public String asyncService() { @ResponseBody public String mysql() { logger.info("mysql received"); + final String rdsMySQLClusterPassword = new String(new Base64().decode(System.getenv("RDS_MYSQL_CLUSTER_PASSWORD").getBytes())); + try { Connection connection = DriverManager.getConnection( System.getenv().get("RDS_MYSQL_CLUSTER_CONNECTION_URL"), System.getenv().get("RDS_MYSQL_CLUSTER_USERNAME"), - System.getenv().get("RDS_MYSQL_CLUSTER_PASSWORD")); + rdsMySQLClusterPassword); Statement statement = connection.createStatement(); statement.executeQuery("SELECT * FROM tables LIMIT 1;"); } catch (SQLException e) { diff --git a/sample-apps/java/8/springboot-main-service/build.gradle.kts b/sample-apps/java/8/springboot-main-service/build.gradle.kts index f5700445f..c42903cff 100644 --- a/sample-apps/java/8/springboot-main-service/build.gradle.kts +++ b/sample-apps/java/8/springboot-main-service/build.gradle.kts @@ -37,7 +37,7 @@ dependencies { implementation("io.opentelemetry:opentelemetry-api:1.34.1") implementation("software.amazon.awssdk:s3") implementation("software.amazon.awssdk:sts") - implementation("com.mysql:mysql-connector-j:8.0.33") + implementation("com.mysql:mysql-connector-j:8.4.0") implementation ("org.apache.httpcomponents:httpclient:4.5.13") } diff --git a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java index a53431082..4daa713fb 100644 --- a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java +++ b/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java @@ -32,6 +32,7 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; +import org.apache.tomcat.util.codec.binary.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -147,11 +148,12 @@ public String asyncService() { @ResponseBody public String mysql() { logger.info("mysql received"); + final String rdsMySQLClusterPassword = new String(new Base64().decode(System.getenv("RDS_MYSQL_CLUSTER_PASSWORD").getBytes())); try { Connection connection = DriverManager.getConnection( System.getenv("RDS_MYSQL_CLUSTER_CONNECTION_URL"), System.getenv("RDS_MYSQL_CLUSTER_USERNAME"), - System.getenv("RDS_MYSQL_CLUSTER_PASSWORD")); + rdsMySQLClusterPassword); Statement statement = connection.createStatement(); statement.executeQuery("SELECT * FROM tables LIMIT 1;"); } catch (SQLException e) { From 93179a1c07f467b1af8aa8aeb30210f7cc730bda Mon Sep 17 00:00:00 2001 From: Harry Date: Mon, 23 Sep 2024 16:35:42 -0700 Subject: [PATCH 4/7] Update gradle wrapper --- gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 43583 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..a4b76b9530d66f5e68d973ea569d8e19de379189 100644 GIT binary patch literal 43583 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 GZ~q4|mN%#X literal 0 HcmV?d00001 From d31375a810a3a13f3eb473c3d58bac82ffe0cecf Mon Sep 17 00:00:00 2001 From: Harry Date: Tue, 24 Sep 2024 13:38:32 -0700 Subject: [PATCH 5/7] Add Language Version Option For Java E2E EC2 --- .github/workflows/java-ec2-asg-test.yml | 11 +- .github/workflows/java-ec2-canary.yml | 2 + .github/workflows/java-ec2-default-retry.yml | 5 + .github/workflows/java-ec2-default-test.yml | 13 +- .../workflows/java-sample-app-ecr-deploy.yml | 50 ++--- .../workflows/java-sample-app-s3-deploy.yml | 50 ++--- .github/workflows/k8s-patch-os-jobs.yml | 180 +++++++-------- .github/workflows/k8s-patch-os-matrix.yml | 31 +-- .github/workflows/test-2.yml | 211 ++++++++++++++++++ .github/workflows/test.yml | 70 ++++++ .../com/amazon/sampleapp/FrontendService.java | 40 ---- .../sampleapp/FrontendServiceController.java | 198 ---------------- .../springboot-main-service/build.gradle.kts | 61 ----- .../build.gradle.kts | 55 ----- .../com/amazon/sampleapp/RemoteService.java | 26 --- .../sampleapp/RemoteServiceController.java | 30 --- sample-apps/java/settings.gradle.kts | 26 +++ .../springboot-main-service/build.gradle.kts | 19 +- .../com/amazon/sampleapp/FrontendService.java | 0 .../sampleapp/FrontendServiceController.java | 0 .../build.gradle.kts | 16 +- .../com/amazon/sampleapp/RemoteService.java | 0 .../sampleapp/RemoteServiceController.java | 0 settings.gradle.kts | 4 - terraform/java/ec2/asg/main.tf | 20 +- terraform/java/ec2/asg/variables.tf | 4 + terraform/java/ec2/default/main.tf | 23 +- terraform/java/ec2/default/variables.tf | 4 + 28 files changed, 538 insertions(+), 611 deletions(-) create mode 100644 .github/workflows/test-2.yml create mode 100644 .github/workflows/test.yml delete mode 100644 sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java delete mode 100644 sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java delete mode 100644 sample-apps/java/8/springboot-main-service/build.gradle.kts delete mode 100644 sample-apps/java/8/springboot-remote-service/build.gradle.kts delete mode 100644 sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java delete mode 100644 sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java create mode 100644 sample-apps/java/settings.gradle.kts rename sample-apps/java/{11+ => }/springboot-main-service/build.gradle.kts (79%) rename sample-apps/java/{8 => }/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java (100%) rename sample-apps/java/{8 => }/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java (100%) rename sample-apps/java/{11+ => }/springboot-remote-service/build.gradle.kts (78%) rename sample-apps/java/{11+ => }/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java (100%) rename sample-apps/java/{11+ => }/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java (100%) diff --git a/.github/workflows/java-ec2-asg-test.yml b/.github/workflows/java-ec2-asg-test.yml index 127eb3248..64edc2c9c 100644 --- a/.github/workflows/java-ec2-asg-test.yml +++ b/.github/workflows/java-ec2-asg-test.yml @@ -14,6 +14,11 @@ on: caller-workflow-name: required: true type: string + java-version: + description: "Currently support version 8, 11, 17, 21, 22" + required: false + type: string + default: '11' outputs: job-started: value: ${{ jobs.java-ec2-asg.outputs.job-started }} @@ -27,8 +32,9 @@ permissions: env: E2E_TEST_AWS_REGION: ${{ inputs.aws-region }} CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} - SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/main-service.jar - SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/remote-service.jar + JAVA_VERSION: ${{ inputs.java-version }} + SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/java-main-service-v${{ inputs.java-version }}.jar + SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/java-remote-service-v${{ inputs.java-version }}.jar E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} METRIC_NAMESPACE: ApplicationSignals @@ -141,6 +147,7 @@ jobs: -var="sample_remote_app_jar=${{ env.SAMPLE_APP_REMOTE_SERVICE_JAR }}" \ -var="get_cw_agent_rpm_command=${{ env.GET_CW_AGENT_RPM_COMMAND }}" \ -var="get_adot_jar_command=${{ env.GET_ADOT_JAR_COMMAND }}" \ + -var="language_version=${{ env.JAVA_VERSION }}" \ || deployment_failed=$? if [ $deployment_failed -eq 1 ]; then diff --git a/.github/workflows/java-ec2-canary.yml b/.github/workflows/java-ec2-canary.yml index 0818ae15c..07aa32b92 100644 --- a/.github/workflows/java-ec2-canary.yml +++ b/.github/workflows/java-ec2-canary.yml @@ -29,6 +29,7 @@ jobs: with: aws-region: ${{ matrix.aws-region }} caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '11' maven: uses: ./.github/workflows/java-ec2-default-retry.yml @@ -37,3 +38,4 @@ jobs: aws-region: 'us-east-1' caller-workflow-name: 'appsignals-e2e-ec2-maven-canary-test' otel-source: 'maven' + java-version: '11' diff --git a/.github/workflows/java-ec2-default-retry.yml b/.github/workflows/java-ec2-default-retry.yml index ceb9eb31d..c3ec8177c 100644 --- a/.github/workflows/java-ec2-default-retry.yml +++ b/.github/workflows/java-ec2-default-retry.yml @@ -14,6 +14,9 @@ on: caller-workflow-name: required: true type: string + java-version: + required: true + type: string otel-source: required: false type: string @@ -30,6 +33,7 @@ jobs: with: aws-region: ${{ inputs.aws-region }} caller-workflow-name: ${{ inputs.caller-workflow-name }} + java-version: ${{ inputs.java-version }} otel-source: ${{ inputs.otel-source }} java-ec2-default-attempt-2: @@ -40,6 +44,7 @@ jobs: with: aws-region: ${{ inputs.aws-region }} caller-workflow-name: ${{ inputs.caller-workflow-name }} + java-version: ${{ inputs.java-version }} otel-source: ${{ inputs.otel-source }} publish-metric-attempt-1: diff --git a/.github/workflows/java-ec2-default-test.yml b/.github/workflows/java-ec2-default-test.yml index db3e16d6c..0b8835a5c 100644 --- a/.github/workflows/java-ec2-default-test.yml +++ b/.github/workflows/java-ec2-default-test.yml @@ -14,6 +14,11 @@ on: caller-workflow-name: required: true type: string + java-version: + description: "Currently support version 8, 11, 17, 21, 22" + required: false + type: string + default: '11' otel-source: required: false type: string @@ -31,9 +36,10 @@ permissions: env: E2E_TEST_AWS_REGION: ${{ inputs.aws-region }} CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} + JAVA_VERSION: ${{ inputs.java-version }} OTEL_SOURCE: ${{ inputs.otel-source }} - SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/main-service.jar - SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/remote-service.jar + SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}-2/java-main-service-v${{ inputs.java-version }}.jar + SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}-2/java-remote-service-v${{ inputs.java-version }}.jar E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} METRIC_NAMESPACE: ApplicationSignals @@ -57,7 +63,7 @@ jobs: - uses: actions/checkout@v4 with: - repository: 'aws-observability/aws-application-signals-test-framework' + repository: 'harrryr/aws-application-signals-test-framework' ref: ${{ env.CALLER_WORKFLOW_NAME == 'main-build' && 'main' || github.ref }} fetch-depth: 0 @@ -150,6 +156,7 @@ jobs: -var="sample_remote_app_jar=${{ env.SAMPLE_APP_REMOTE_SERVICE_JAR }}" \ -var="get_cw_agent_rpm_command=${{ env.GET_CW_AGENT_RPM_COMMAND }}" \ -var="get_adot_jar_command=${{ env.GET_ADOT_JAR_COMMAND }}" \ + -var="language_version=${{ env.JAVA_VERSION }}" \ || deployment_failed=$? if [ $deployment_failed -eq 1 ]; then diff --git a/.github/workflows/java-sample-app-ecr-deploy.yml b/.github/workflows/java-sample-app-ecr-deploy.yml index 219676fe6..8806f4497 100644 --- a/.github/workflows/java-sample-app-ecr-deploy.yml +++ b/.github/workflows/java-sample-app-ecr-deploy.yml @@ -1,6 +1,8 @@ ## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ## SPDX-License-Identifier: Apache-2.0 +# This workflow is for building and uploading the Java sample application to ECR. Java 11 will be built and uploaded to all regions to be used by the canary while other versions (8, 17, 21, 22) +# will be uploaded to us-east-1 for the purpose of testing ADOT Java name: Sample App Deployment - Java ECR on: workflow_dispatch: # be able to run the workflow on demand @@ -14,7 +16,7 @@ env: E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} jobs: - upload-java-main-service-image: + java-v11-main: strategy: fail-fast: false matrix: @@ -47,13 +49,13 @@ jobs: role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Build and Upload Main Service Image v11 - working-directory: sample-apps/java/8/springboot-main-service + - name: Build and Upload Main Service Image + working-directory: sample-apps/java/springboot-main-service run: | - sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts + sed -i 's#"{{ECR_IMAGE_URI}}"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts gradle jib -P javaVersion=11 - upload-java-remote-service-image: + java-v11-remote: strategy: fail-fast: false matrix: @@ -86,13 +88,13 @@ jobs: role-to-assume: arn:aws:iam::${{ env.ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: ${{ matrix.aws-region }} - - name: Build and Upload Remote Service Image v11 - working-directory: sample-apps/java/11+/springboot-remote-service + - name: Build and Upload Remote Service Image + working-directory: sample-apps/java/springboot-remote-service run: | - sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts + sed -i 's#"{{ECR_IMAGE_URI}}"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v11"#g' build.gradle.kts gradle jib -P javaVersion=11 - upload-java-versions-main-service-image: + java-main: strategy: fail-fast: false matrix: @@ -118,21 +120,13 @@ jobs: secret-ids: | JAVA_MAIN_SAMPLE_APP_IMAGE, e2e-test/java-main-sample-app-image - - name: Build and Upload Main Service Image v11+ - if: ${{ matrix.java-version != 8 }} - working-directory: sample-apps/java/11+/springboot-main-service + - name: Build and Upload Main Service Image + working-directory: sample-apps/java/springboot-main-service run: | - sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + sed -i 's#"{{ECR_IMAGE_URI}}"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts gradle jib -P javaVersion=${{ matrix.java-version }} - - name: Build and Upload Main Service Image v8 - if: ${{ matrix.java-version == 8 }} - working-directory: sample-apps/java/8/springboot-main-service - run: | - sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts - gradle jib -P javaVersion=${{ matrix.java-version }} - - upload-java-versions-remote-service-image: + java-remote: strategy: fail-fast: false matrix: @@ -158,16 +152,8 @@ jobs: secret-ids: | JAVA_REMOTE_SAMPLE_APP_IMAGE, e2e-test/java-remote-sample-app-image - - name: Build and Upload Remote Service Image v11+ - if: ${{ matrix.java-version != 8 }} - working-directory: sample-apps/java/11+/springboot-remote-service - run: | - sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts - gradle jib -P javaVersion=${{ matrix.java-version }} - - - name: Build and Upload Remote Service Image v8 - if: ${{ matrix.java-version == 8 }} - working-directory: sample-apps/java/8/springboot-remote-service + - name: Build and Upload Remote Service Image + working-directory: sample-apps/java/springboot-remote-service run: | - sed -i 's#":"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts + sed -i 's#"{{ECR_IMAGE_URI}}"#"${{ env.E2E_TEST_ACCOUNT_ID }}.dkr.ecr.us-east-1.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ matrix.java-version }}"#g' build.gradle.kts gradle jib -P javaVersion=${{ matrix.java-version }} diff --git a/.github/workflows/java-sample-app-s3-deploy.yml b/.github/workflows/java-sample-app-s3-deploy.yml index 97e5e955a..0042d1f7c 100644 --- a/.github/workflows/java-sample-app-s3-deploy.yml +++ b/.github/workflows/java-sample-app-s3-deploy.yml @@ -1,6 +1,8 @@ ## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. ## SPDX-License-Identifier: Apache-2.0 +# This workflow is for building and uploading the Java sample application to S3 bucket. Java 11 will be built and uploaded to all regions to be used by the canary while other versions (8, 17, 21, 22) +# will be uploaded to us-east-1 for the purpose of testing ADOT Java name: Sample App Deployment - Java S3 on: workflow_dispatch: # be able to run the workflow on demand @@ -14,7 +16,7 @@ env: E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} jobs: - upload-java-v11-main-service-jar: + java-v11-main: strategy: fail-fast: false matrix: @@ -47,12 +49,12 @@ jobs: aws-region: ${{ matrix.aws-region }} - name: Build and Upload Main Jar - working-directory: sample-apps/java/11+/springboot-main-service + working-directory: sample-apps/java/springboot-main-service run: | gradle build -P javaVersion=11 aws s3api put-object --bucket aws-appsignals-sample-app-prod-${{ matrix.aws-region }} --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v11.jar - upload-java-v11-remote-service-jar: + java-v11-remote: strategy: fail-fast: false matrix: @@ -85,12 +87,12 @@ jobs: aws-region: ${{ matrix.aws-region }} - name: Build and Upload Main Jar - working-directory: sample-apps/java/11+/springboot-remote-service - run: | + working-directory: sample-apps/java/springboot-remote-service + run: | gradle build -P javaVersion=11 aws s3api put-object --bucket aws-appsignals-sample-app-prod-${{ matrix.aws-region }} --body build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v11.jar - upload-java-versions-main-service-jar: + java-main: strategy: fail-fast: false matrix: @@ -110,21 +112,18 @@ jobs: role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - - name: Build Main Jar v11+ - if: ${{ matrix.java-version != 8 }} - working-directory: sample-apps/java/11+/springboot-main-service - run: | - gradle build -P javaVersion=${{ matrix.java-version }} - aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v${{ matrix.java-version }}.jar - - - name: Build Main Jar v8 - if: ${{ matrix.java-version == 8 }} - working-directory: sample-apps/java/8/springboot-main-service + - name: Build and Upload Main Jar + working-directory: sample-apps/java/springboot-main-service run: | + if [ "${{ matrix.java-version }}" = "22" ]; then + sed -i 's/id("org.springframework.boot")/id("org.springframework.boot") version "3.3.4"/' build.gradle.kts + cat build.gradle.kts + fi + gradle build -P javaVersion=${{ matrix.java-version }} aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-*-SNAPSHOT.jar --key java-main-service-v${{ matrix.java-version }}.jar - upload-java-versions-remote-service-jar: + java-remote: strategy: fail-fast: false matrix: @@ -144,17 +143,14 @@ jobs: role-to-assume: arn:aws:iam::${{ env.E2E_TEST_ACCOUNT_ID }}:role/${{ env.E2E_TEST_ROLE_NAME }} aws-region: us-east-1 - - name: Build and Upload Remote Jar v11+ - if: ${{ matrix.java-version != 8 }} - working-directory: sample-apps/java/11+/springboot-remote-service - run: | - gradle build -P javaVersion=${{ matrix.java-version }} - aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v${{ matrix.java-version }}.jar - - - name: Build and Upload Remote Jar v8 - if: ${{ matrix.java-version == 8 }} - working-directory: sample-apps/java/8/springboot-remote-service + - name: Build and Upload Remote Jar + working-directory: sample-apps/java/springboot-remote-service run: | + if [ "${{ matrix.java-version }}" = "22" ]; then + sed -i 's/id("org.springframework.boot")/id("org.springframework.boot") version "3.3.4"/' build.gradle.kts + cat build.gradle.kts + fi + gradle build -P javaVersion=${{ matrix.java-version }} aws s3api put-object --bucket aws-appsignals-sample-app-prod-us-east-1 --body ./build/libs/springboot-remote-service-*-SNAPSHOT.jar --key java-remote-service-v${{ matrix.java-version }}.jar diff --git a/.github/workflows/k8s-patch-os-jobs.yml b/.github/workflows/k8s-patch-os-jobs.yml index d02745646..159bb6887 100644 --- a/.github/workflows/k8s-patch-os-jobs.yml +++ b/.github/workflows/k8s-patch-os-jobs.yml @@ -92,51 +92,51 @@ jobs: aws secretsmanager update-secret --secret-id "${SECRET_NAMES[0]}" --secret-string "$master_public_ip" aws secretsmanager update-secret --secret-id "${SECRET_NAMES[1]}" --secret-string "$private_key_content" - run-java-k8s-test: - if: ${{ inputs.LANGUAGE == 'java' }} - needs: create-k8s-on-ec2 - uses: ./.github/workflows/java-k8s-test.yml - secrets: inherit - with: - aws-region: 'us-east-1' - caller-workflow-name: 'k8s-os-patching' - caller-repository: ${{ inputs.repo_name }} - - run-python-k8s-test: - if: ${{ inputs.LANGUAGE == 'python' }} - needs: create-k8s-on-ec2 - uses: ./.github/workflows/python-k8s-test.yml - secrets: inherit - with: - aws-region: 'us-east-1' - caller-workflow-name: 'k8s-os-patching' - caller-repository: ${{ inputs.repo_name }} - - run-dotnet-k8s-test: - if: ${{ inputs.LANGUAGE == 'dotnet' }} - needs: create-k8s-on-ec2 - # TODO: Point to REAL K8s implementation after K8s Infra created. - uses: ./.github/workflows/dummy-k8s-test.yml - secrets: inherit - with: - aws-region: 'us-east-1' - caller-workflow-name: 'k8s-os-patching' - caller-repository: ${{ inputs.repo_name }} - - run-node-k8s-test: - if: ${{ inputs.LANGUAGE == 'node' }} - needs: create-k8s-on-ec2 - # TODO: Point to REAL K8s implementation after K8s Infra created. - uses: ./.github/workflows/dummy-k8s-test.yml - secrets: inherit - with: - aws-region: 'us-east-1' - caller-workflow-name: 'k8s-os-patching' - caller-repository: ${{ inputs.repo_name }} +# run-java-k8s-test: +# if: ${{ inputs.LANGUAGE == 'java' }} +# needs: create-k8s-on-ec2 +# uses: ./.github/workflows/java-k8s-test.yml +# secrets: inherit +# with: +# aws-region: 'us-east-1' +# caller-workflow-name: 'k8s-os-patching' +# caller-repository: ${{ inputs.repo_name }} +# +# run-python-k8s-test: +# if: ${{ inputs.LANGUAGE == 'python' }} +# needs: create-k8s-on-ec2 +# uses: ./.github/workflows/python-k8s-test.yml +# secrets: inherit +# with: +# aws-region: 'us-east-1' +# caller-workflow-name: 'k8s-os-patching' +# caller-repository: ${{ inputs.repo_name }} +# +# run-dotnet-k8s-test: +# if: ${{ inputs.LANGUAGE == 'dotnet' }} +# needs: create-k8s-on-ec2 +# # TODO: Point to REAL K8s implementation after K8s Infra created. +# uses: ./.github/workflows/dummy-k8s-test.yml +# secrets: inherit +# with: +# aws-region: 'us-east-1' +# caller-workflow-name: 'k8s-os-patching' +# caller-repository: ${{ inputs.repo_name }} +# +# run-node-k8s-test: +# if: ${{ inputs.LANGUAGE == 'node' }} +# needs: create-k8s-on-ec2 +# # TODO: Point to REAL K8s implementation after K8s Infra created. +# uses: ./.github/workflows/dummy-k8s-test.yml +# secrets: inherit +# with: +# aws-region: 'us-east-1' +# caller-workflow-name: 'k8s-os-patching' +# caller-repository: ${{ inputs.repo_name }} update-secrets: - needs: [ run-java-k8s-test, run-python-k8s-test, run-dotnet-k8s-test, run-node-k8s-test ] - if: ${{ always() && (needs.run-java-k8s-test.result == 'success' || needs.run-python-k8s-test.result == 'success' || needs.run-dotnet-k8s-test.result == 'success' || needs.run-node-k8s-test.result == 'success') }} + needs: create-k8s-on-ec2 + # if: ${{ always() && (needs.run-java-k8s-test.result == 'success' || needs.run-python-k8s-test.result == 'success' || needs.run-dotnet-k8s-test.result == 'success' || needs.run-node-k8s-test.result == 'success') }} runs-on: ubuntu-latest steps: - name: Configure AWS Credentials @@ -181,50 +181,50 @@ jobs: aws secretsmanager update-secret --secret-id "${CURRENT_SECRET_KEYS[0]}" --secret-string "$endpoint_secret" aws secretsmanager update-secret --secret-id "${CURRENT_SECRET_KEYS[1]}" --secret-string "$ssh_secret" - publish-metric: - needs: [ update-secrets ] - if: always() - uses: ./.github/workflows/enablement-test-publish-result.yml - secrets: inherit - with: - aws-region: 'us-east-1' - caller-workflow-name: 'enablement-test-k8s-patch-os' - validation-result: ${{ needs.update-secrets.result }} - - cleanup-if-failed: - needs: [ run-java-k8s-test, run-python-k8s-test, run-dotnet-k8s-test, run-node-k8s-test ] - if: ${{ always() && (needs.run-java-k8s-test.result != 'success' && needs.run-python-k8s-test.result != 'success' && needs.run-dotnet-k8s-test.result != 'success' && needs.run-node-k8s-test.result != 'success') }} - runs-on: ubuntu-latest - steps: - - name: Delete Key Pair and EC2 Instance - run: | - PENDING_SECRET_KEYS=( - "e2e-test/${{ matrix.instance.repo_name }}/${{ matrix.instance.language }}-k8s-master-node-endpoint-pending-value" - ) - endpoint_secret=$(aws secretsmanager get-secret-value --secret-id "${PENDING_SECRET_KEYS[0]}" --query SecretString --output text) - - instance_name=$(aws ec2 describe-instances \ - --filters "Name=ip-address,Values=$endpoint_secret" \ - --query "Reservations[*].Instances[*].{Name:Tags[?Key=='Name']|[0].Value}" \ - --output text) - - if [ -n "$instance_name" ]; then - prev_testing_id=$(echo $instance_name | awk -F'-' '{print $(NF-1)"-"$NF}') - - aws ec2 delete-key-pair --key-name "k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-key-pair-$prev_testing_id" - - main_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-master-$prev_testing_id - worker_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-worker-$prev_testing_id - main_instance_id=$(aws ec2 describe-instances \ - --filters "Name=tag:Name,Values=$main_instance_name" \ - --query "Reservations[*].Instances[*].InstanceId" \ - --output text) - - worker_instance_id=$(aws ec2 describe-instances \ - --filters "Name=tag:Name,Values=$worker_instance_name" \ - --query "Reservations[*].Instances[*].InstanceId" \ - --output text) - - aws ec2 terminate-instances --instance-ids $main_instance_id - aws ec2 terminate-instances --instance-ids $worker_instance_id - fi +# publish-metric: +# needs: [ update-secrets ] +# if: always() +# uses: ./.github/workflows/enablement-test-publish-result.yml +# secrets: inherit +# with: +# aws-region: 'us-east-1' +# caller-workflow-name: 'enablement-test-k8s-patch-os' +# validation-result: ${{ needs.update-secrets.result }} +# +# cleanup-if-failed: +# needs: [ run-java-k8s-test, run-python-k8s-test, run-dotnet-k8s-test, run-node-k8s-test ] +# if: ${{ always() && (needs.run-java-k8s-test.result != 'success' && needs.run-python-k8s-test.result != 'success' && needs.run-dotnet-k8s-test.result != 'success' && needs.run-node-k8s-test.result != 'success') }} +# runs-on: ubuntu-latest +# steps: +# - name: Delete Key Pair and EC2 Instance +# run: | +# PENDING_SECRET_KEYS=( +# "e2e-test/${{ matrix.instance.repo_name }}/${{ matrix.instance.language }}-k8s-master-node-endpoint-pending-value" +# ) +# endpoint_secret=$(aws secretsmanager get-secret-value --secret-id "${PENDING_SECRET_KEYS[0]}" --query SecretString --output text) +# +# instance_name=$(aws ec2 describe-instances \ +# --filters "Name=ip-address,Values=$endpoint_secret" \ +# --query "Reservations[*].Instances[*].{Name:Tags[?Key=='Name']|[0].Value}" \ +# --output text) +# +# if [ -n "$instance_name" ]; then +# prev_testing_id=$(echo $instance_name | awk -F'-' '{print $(NF-1)"-"$NF}') +# +# aws ec2 delete-key-pair --key-name "k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-key-pair-$prev_testing_id" +# +# main_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-master-$prev_testing_id +# worker_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-worker-$prev_testing_id +# main_instance_id=$(aws ec2 describe-instances \ +# --filters "Name=tag:Name,Values=$main_instance_name" \ +# --query "Reservations[*].Instances[*].InstanceId" \ +# --output text) +# +# worker_instance_id=$(aws ec2 describe-instances \ +# --filters "Name=tag:Name,Values=$worker_instance_name" \ +# --query "Reservations[*].Instances[*].InstanceId" \ +# --output text) +# +# aws ec2 terminate-instances --instance-ids $main_instance_id +# aws ec2 terminate-instances --instance-ids $worker_instance_id +# fi diff --git a/.github/workflows/k8s-patch-os-matrix.yml b/.github/workflows/k8s-patch-os-matrix.yml index 95254b869..e07026c8b 100644 --- a/.github/workflows/k8s-patch-os-matrix.yml +++ b/.github/workflows/k8s-patch-os-matrix.yml @@ -6,6 +6,7 @@ on: schedule: - cron: '0 0 1 * *' # run the workflow beginning of every month workflow_dispatch: # be able to run the workflow on demand + push: permissions: id-token: write @@ -17,22 +18,22 @@ jobs: fail-fast: false matrix: instance: [ - { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'java' }, - { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'python' }, - { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'dotnet' }, - { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'node' }, - { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'java' }, - { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'python' }, - { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'dotnet' }, - { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'node' }, - { repo_name: 'aws-otel-java-instrumentation', ec2_name: 'adot-java-release', language: 'java' }, - { repo_name: 'aws-otel-python-instrumentation', ec2_name: 'adot-python-release', language: 'python' }, - { repo_name: 'aws-otel-dotnet-instrumentation', ec2_name: 'adot-dotnet-release', language: 'dotnet' }, - { repo_name: 'aws-otel-js-instrumentation', ec2_name: 'adot-node-release', language: 'node' }, +# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'java' }, +# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'python' }, +# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'dotnet' }, +# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'node' }, +# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'java' }, +# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'python' }, +# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'dotnet' }, +# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'node' }, +# { repo_name: 'aws-otel-java-instrumentation', ec2_name: 'adot-java-release', language: 'java' }, +# { repo_name: 'aws-otel-python-instrumentation', ec2_name: 'adot-python-release', language: 'python' }, +# { repo_name: 'aws-otel-dotnet-instrumentation', ec2_name: 'adot-dotnet-release', language: 'dotnet' }, +# { repo_name: 'aws-otel-js-instrumentation', ec2_name: 'adot-node-release', language: 'node' }, { repo_name: 'aws-application-signals-test-framework', ec2_name: 'java-canary', language: 'java' }, - { repo_name: 'aws-application-signals-test-framework', ec2_name: 'python-canary', language: 'python' }, - { repo_name: 'aws-application-signals-test-framework', ec2_name: 'dotnet-canary', language: 'dotnet' }, - { repo_name: 'aws-application-signals-test-framework', ec2_name: 'node-canary', language: 'node' } ] + { repo_name: 'aws-application-signals-test-framework', ec2_name: 'python-canary', language: 'python' } ] +# { repo_name: 'aws-application-signals-test-framework', ec2_name: 'dotnet-canary', language: 'dotnet' }, +# { repo_name: 'aws-application-signals-test-framework', ec2_name: 'node-canary', language: 'node' } ] uses: ./.github/workflows/k8s-patch-os-jobs.yml secrets: inherit with: diff --git a/.github/workflows/test-2.yml b/.github/workflows/test-2.yml new file mode 100644 index 000000000..015be242d --- /dev/null +++ b/.github/workflows/test-2.yml @@ -0,0 +1,211 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +## This workflow aims to run the Application Signals end-to-end tests as a canary to +## test the artifacts for App Signals enablement. It will deploy a sample app and remote +## service onto an EKS cluster, call the APIs, and validate the generated telemetry, +## including logs, metrics, and traces. +name: Java EKS Enablement Canary Testing +on: +# push: + workflow_dispatch: + + +permissions: + id-token: write + contents: read + +jobs: + eks-8: + strategy: + fail-fast: false + matrix: + aws-region: [ 'us-east-1' ] + uses: ./.github/workflows/java-eks-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + test-cluster-name: 'e2e-canary-test' + caller-workflow-name: 'appsignals-e2e-eks-canary-test' + java-version: '8' + + eks-11: + needs: eks-8 + strategy: + fail-fast: false + matrix: + aws-region: [ 'us-east-1' ] + uses: ./.github/workflows/java-eks-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + test-cluster-name: 'e2e-canary-test' + caller-workflow-name: 'appsignals-e2e-eks-canary-test' + java-version: '11' + + eks-17: + needs: eks-11 + strategy: + fail-fast: false + matrix: + aws-region: [ 'us-east-1' ] + uses: ./.github/workflows/java-eks-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + test-cluster-name: 'e2e-canary-test' + caller-workflow-name: 'appsignals-e2e-eks-canary-test' + java-version: '17' + + eks-21: + needs: eks-17 + strategy: + fail-fast: false + matrix: + aws-region: [ 'us-east-1' ] + uses: ./.github/workflows/java-eks-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + test-cluster-name: 'e2e-canary-test' + caller-workflow-name: 'appsignals-e2e-eks-canary-test' + java-version: '21' + + eks-22: + needs: eks-21 + strategy: + fail-fast: false + matrix: + aws-region: [ 'us-east-1' ] + uses: ./.github/workflows/java-eks-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + test-cluster-name: 'e2e-canary-test' + caller-workflow-name: 'appsignals-e2e-eks-canary-test' + java-version: '22' + + github-8: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '8' + + github-11: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '11' + + github-17: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '17' + + github-21: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '21' + + github-22: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '22' + + maven-8: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '8' + otel-source: 'maven' + + maven-11: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '11' + otel-source: 'maven' + + maven-17: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '17' + otel-source: 'maven' + + maven-21: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '21' + otel-source: 'maven' + + maven-22: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '22' + otel-source: 'maven' \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..2f8379d60 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,70 @@ +## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +## SPDX-License-Identifier: Apache-2.0 + +## This workflow aims to run the Application Signals end-to-end tests as a canary to +## test the artifacts for App Signals enablement. It will deploy a sample app and remote +## service onto an EKS cluster, call the APIs, and validate the generated telemetry, +## including logs, metrics, and traces. +name: Java EKS Enablement Canary Testing +on: + push: + +permissions: + id-token: write + contents: read + +jobs: + eks: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-eks-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + test-cluster-name: 'e2e-canary-test' + caller-workflow-name: 'appsignals-e2e-eks-canary-test' + java-version: '11' + + k8s: + uses: ./.github/workflows/java-k8s-retry.yml + secrets: inherit + with: + # To run in more regions, a cluster must be provisioned manually on EC2 instances in that region + aws-region: 'us-east-1' + caller-workflow-name: 'appsignals-e2e-k8s-canary-test' + java-version: '11' + + ecs: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ecs-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-java-ecs-canary-test' + java-version: '11' + + github: + strategy: + fail-fast: false + matrix: + aws-region: ['us-east-1'] + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: ${{ matrix.aws-region }} + caller-workflow-name: 'appsignals-e2e-ec2-canary-test' + java-version: '11' + + maven: + uses: ./.github/workflows/java-ec2-default-retry.yml + secrets: inherit + with: + aws-region: 'us-east-1' + caller-workflow-name: 'appsignals-e2e-ec2-maven-canary-test' + otel-source: 'maven' + java-version: '11' \ No newline at end of file diff --git a/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java deleted file mode 100644 index 7cc24822c..000000000 --- a/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amazon.sampleapp; - -import java.net.http.HttpClient; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import software.amazon.awssdk.services.s3.S3Client; - -@SpringBootApplication -public class FrontendService { - - @Bean - public HttpClient httpClient() { - return HttpClient.newHttpClient(); - } - - @Bean - public S3Client s3() { - return S3Client.builder().build(); - } - - public static void main(String[] args) { - SpringApplication.run(FrontendService.class, args); - } -} diff --git a/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java deleted file mode 100644 index 3628755d0..000000000 --- a/sample-apps/java/11+/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amazon.sampleapp; - -import io.opentelemetry.api.trace.Span; -import java.net.URI; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; -import java.sql.DriverManager; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.sql.Connection; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.tomcat.util.codec.binary.Base64; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.annotation.Bean; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.ResponseBody; -import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.GetBucketLocationRequest; - -@Controller -public class FrontendServiceController { - private static final Logger logger = LoggerFactory.getLogger(FrontendServiceController.class); - private final HttpClient httpClient; - private final S3Client s3; - private AtomicBoolean shouldSendLocalRootClientCall = new AtomicBoolean(false); - - @Bean - private void runLocalRootClientCallRecurringService() { // run the service - ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - - Runnable runnableTask = - () -> { - if (shouldSendLocalRootClientCall.get()) { - shouldSendLocalRootClientCall.set(false); - HttpRequest request = - HttpRequest.newBuilder() - .uri(URI.create("http://local-root-client-call")) - .GET() - .build(); - try { - HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); - } catch (Exception e) { - } - } - }; - // Run with initial 0.1s delay, every 1 second - executorService.scheduleAtFixedRate(runnableTask, 100, 1000, TimeUnit.MILLISECONDS); - } - - @Autowired - public FrontendServiceController(HttpClient httpClient, S3Client s3) { - this.httpClient = httpClient; - this.s3 = s3; - } - - @GetMapping("/") - @ResponseBody - public String healthcheck() { - return "healthcheck"; - } - - // test aws calls instrumentation - @GetMapping("/aws-sdk-call") - @ResponseBody - public String awssdkCall(@RequestParam(name = "testingId", required = false) String testingId) { - String bucketName = "e2e-test-bucket-name"; - // Add a unique test ID to bucketname to associate buckets to specific test runs - if (testingId != null) { - bucketName += "-" + testingId; - } - GetBucketLocationRequest bucketLocationRequest = - GetBucketLocationRequest.builder().bucket(bucketName).build(); - try { - s3.getBucketLocation(bucketLocationRequest); - } catch (Exception e) { - // bucketName does not exist, so this is expected. - logger.error("Error occurred when trying to get bucket location of: " + bucketName); - logger.error("Could not retrieve http request:" + e.getLocalizedMessage()); - } - return getXrayTraceId(); - } - - // test http instrumentation (java client) - @GetMapping("/outgoing-http-call") - @ResponseBody - public String httpCall() { - HttpRequest request = - HttpRequest.newBuilder().uri(URI.create("https://www.amazon.com")).GET().build(); - - try { - HttpResponse response = - httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - int statusCode = response.statusCode(); - - logger.info("outgoing-http-call status code: " + statusCode); - } catch (Exception e) { - logger.error("Could not complete http request:" + e.getMessage()); - } - - return getXrayTraceId(); - } - - // RemoteService must also be deployed to use this API - @GetMapping("/remote-service") - @ResponseBody - public String downstreamService(@RequestParam("ip") String ip) { - // Ensure IP doesn't have extra slashes anywhere - ip = ip.replace("/", ""); - HttpRequest request = - HttpRequest.newBuilder() - .uri(URI.create("http://" + ip + ":8080/healthcheck")) - .GET() - .build(); - - try { - HttpResponse response = - httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - int statusCode = response.statusCode(); - - logger.info("Remote service call status code: " + statusCode); - return getXrayTraceId(); - } catch (Exception e) { - logger.error("Could not complete http request to remote service:" + e.getMessage()); - } - - return getXrayTraceId(); - } - - // Test Local Root Client Span generation - @GetMapping("/client-call") - @ResponseBody - public String asyncService() { - logger.info("Client-call received"); - shouldSendLocalRootClientCall.set(true); - // This API is used to trigger the http://local-root-client-call call on running on the executor - // recurring service, which will generate a local root client span. The E2E testing will attempt - // to validate the span - // generated by the /local-root-client-call, not this /client-call API call. Therefore, the - // traceId of this API call is not needed and we return an invalid traceId to indicate that the - // call was received but to not use this - // traceId. - return "{\"traceId\": \"1-00000000-000000000000000000000000\"}"; - } - - // Uses the /mysql endpoint to make an SQL call - @GetMapping("/mysql") - @ResponseBody - public String mysql() { - logger.info("mysql received"); - final String rdsMySQLClusterPassword = new String(new Base64().decode(System.getenv("RDS_MYSQL_CLUSTER_PASSWORD").getBytes())); - - try { - Connection connection = DriverManager.getConnection( - System.getenv().get("RDS_MYSQL_CLUSTER_CONNECTION_URL"), - System.getenv().get("RDS_MYSQL_CLUSTER_USERNAME"), - rdsMySQLClusterPassword); - Statement statement = connection.createStatement(); - statement.executeQuery("SELECT * FROM tables LIMIT 1;"); - } catch (SQLException e) { - logger.error("Could not complete SQL request:{}", e.getMessage()); - throw new RuntimeException(e); - } - - return getXrayTraceId(); - } - - // get x-ray trace id - private String getXrayTraceId() { - String traceId = Span.current().getSpanContext().getTraceId(); - String xrayTraceId = "1-" + traceId.substring(0, 8) + "-" + traceId.substring(8); - - return String.format("{\"traceId\": \"%s\"}", xrayTraceId); - } -} diff --git a/sample-apps/java/8/springboot-main-service/build.gradle.kts b/sample-apps/java/8/springboot-main-service/build.gradle.kts deleted file mode 100644 index c42903cff..000000000 --- a/sample-apps/java/8/springboot-main-service/build.gradle.kts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -plugins { - java - application - id("org.springframework.boot") - id("io.spring.dependency-management") version "1.1.0" - id("com.google.cloud.tools.jib") -} - -group = "com.amazon.sampleapp" -version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_1_8 -java.targetCompatibility = JavaVersion.VERSION_1_8 - -repositories { - mavenCentral() -} - -dependencies { - implementation(platform("software.amazon.awssdk:bom:2.20.78")) - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter-logging") - implementation("io.opentelemetry:opentelemetry-api:1.34.1") - implementation("software.amazon.awssdk:s3") - implementation("software.amazon.awssdk:sts") - implementation("com.mysql:mysql-connector-j:8.4.0") - implementation ("org.apache.httpcomponents:httpclient:4.5.13") -} - -jib { - from { - image = "openjdk:8-jdk" - } - - to { - image = ":" - } - - container { - mainClass = "com.amazon.sampleapp.FrontendService" - ports = listOf("8080") - } -} - -application { - mainClass.set("com.amazon.sampleapp.FrontendService") -} diff --git a/sample-apps/java/8/springboot-remote-service/build.gradle.kts b/sample-apps/java/8/springboot-remote-service/build.gradle.kts deleted file mode 100644 index 213bda263..000000000 --- a/sample-apps/java/8/springboot-remote-service/build.gradle.kts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -plugins { - java - application - id("org.springframework.boot") - id("io.spring.dependency-management") version "1.1.0" - id("com.google.cloud.tools.jib") -} - -group = "com.amazon.sampleapp" -version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = JavaVersion.VERSION_1_8 -java.targetCompatibility = JavaVersion.VERSION_1_8 - -repositories { - mavenCentral() -} - -dependencies { - implementation("org.springframework.boot:spring-boot-starter-web") - implementation("org.springframework.boot:spring-boot-starter-logging") - implementation ("org.apache.httpcomponents:httpclient:4.5.13") -} - -jib { - from { - image = "openjdk:8-jdk" - } - - to { - image = ":" - } - container { - mainClass = "com.amazon.sampleapp.RemoteService" - ports = listOf("8080") - } -} - -application { - mainClass.set("com.amazon.sampleapp.RemoteService") -} \ No newline at end of file diff --git a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java deleted file mode 100644 index 01076f264..000000000 --- a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amazon.sampleapp; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class RemoteService { - public static void main(String[] args) { - SpringApplication.run(RemoteService.class, args); - } -} diff --git a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java b/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java deleted file mode 100644 index cf71fbe4c..000000000 --- a/sample-apps/java/8/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Amazon.com, Inc. or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"). - * You may not use this file except in compliance with the License. - * A copy of the License is located at - * - * http://aws.amazon.com/apache2.0 - * - * or in the "license" file accompanying this file. This file is distributed - * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.amazon.sampleapp; - -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ResponseBody; - -@Controller -public class RemoteServiceController { - - @GetMapping("/healthcheck") - @ResponseBody - public String healthcheck() { - return "Remote service healthcheck"; - } -} diff --git a/sample-apps/java/settings.gradle.kts b/sample-apps/java/settings.gradle.kts new file mode 100644 index 000000000..7415fafcb --- /dev/null +++ b/sample-apps/java/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + plugins { + id("com.google.cloud.tools.jib") version "3.4.0" + id("org.springframework.boot") version "2.7.17" + } +} + +dependencyResolutionManagement { + repositories { + mavenCentral() + mavenLocal() + + maven { + setUrl("https://oss.sonatype.org/content/repositories/snapshots") + } + } +} + +buildCache { + local { + isEnabled = true + } +} + +include(":springboot-main-service") +include(":springboot-remote-service") \ No newline at end of file diff --git a/sample-apps/java/11+/springboot-main-service/build.gradle.kts b/sample-apps/java/springboot-main-service/build.gradle.kts similarity index 79% rename from sample-apps/java/11+/springboot-main-service/build.gradle.kts rename to sample-apps/java/springboot-main-service/build.gradle.kts index e444634a8..844098e6c 100644 --- a/sample-apps/java/11+/springboot-main-service/build.gradle.kts +++ b/sample-apps/java/springboot-main-service/build.gradle.kts @@ -14,10 +14,11 @@ */ val javaVersion = if (project.hasProperty("javaVersion")) { - JavaVersion.toVersion(project.property("javaVersion").toString()) + project.property("javaVersion").toString() } else { - JavaVersion.VERSION_11 + "11" } +val javaVersionRefactored = JavaVersion.toVersion(javaVersion) plugins { java @@ -25,14 +26,12 @@ plugins { id("org.springframework.boot") id("io.spring.dependency-management") version "1.1.0" id("com.google.cloud.tools.jib") - id("org.jetbrains.kotlin.plugin.compose") version "2.0.0" } - group = "com.amazon.sampleapp" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = javaVersion -java.targetCompatibility = javaVersion +java.sourceCompatibility = javaVersionRefactored +java.targetCompatibility = javaVersionRefactored repositories { mavenCentral() @@ -46,19 +45,17 @@ dependencies { implementation("software.amazon.awssdk:s3") implementation("software.amazon.awssdk:sts") implementation("com.mysql:mysql-connector-j:8.4.0") - implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20") - testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") + implementation ("org.apache.httpcomponents:httpclient:4.5.13") } jib { from { image = "openjdk:$javaVersion-jdk" } - + // Replace this value with the ECR Image URI to { - image = ":" + image = "{{ECR_IMAGE_URI}}" } - container { mainClass = "com.amazon.sampleapp.FrontendService" ports = listOf("8080") diff --git a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java similarity index 100% rename from sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java rename to sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendService.java diff --git a/sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java b/sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java similarity index 100% rename from sample-apps/java/8/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java rename to sample-apps/java/springboot-main-service/src/main/java/com/amazon/sampleapp/FrontendServiceController.java diff --git a/sample-apps/java/11+/springboot-remote-service/build.gradle.kts b/sample-apps/java/springboot-remote-service/build.gradle.kts similarity index 78% rename from sample-apps/java/11+/springboot-remote-service/build.gradle.kts rename to sample-apps/java/springboot-remote-service/build.gradle.kts index 93eeace8c..a5f45619c 100644 --- a/sample-apps/java/11+/springboot-remote-service/build.gradle.kts +++ b/sample-apps/java/springboot-remote-service/build.gradle.kts @@ -14,10 +14,11 @@ */ val javaVersion = if (project.hasProperty("javaVersion")) { - JavaVersion.toVersion(project.property("javaVersion").toString()) + project.property("javaVersion").toString() } else { - JavaVersion.VERSION_11 + "11" } +val javaVersionRefactored = JavaVersion.toVersion(javaVersion) plugins { java @@ -29,8 +30,8 @@ plugins { group = "com.amazon.sampleapp" version = "0.0.1-SNAPSHOT" -java.sourceCompatibility = javaVersion -java.targetCompatibility = javaVersion +java.sourceCompatibility = javaVersionRefactored +java.targetCompatibility = javaVersionRefactored repositories { mavenCentral() @@ -39,17 +40,16 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-logging") - implementation("org.jetbrains.kotlin:kotlin-stdlib:2.0.20") - testImplementation("org.jetbrains.kotlin:kotlin-test:2.0.20") + implementation ("org.apache.httpcomponents:httpclient:4.5.13") } jib { from { image = "openjdk:$javaVersion-jdk" } - + // Replace this value with the ECR Image URI to { - image = ":" + image = "{{ECR_IMAGE_URI}}" } container { mainClass = "com.amazon.sampleapp.RemoteService" diff --git a/sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java b/sample-apps/java/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java similarity index 100% rename from sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java rename to sample-apps/java/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteService.java diff --git a/sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java b/sample-apps/java/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java similarity index 100% rename from sample-apps/java/11+/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java rename to sample-apps/java/springboot-remote-service/src/main/java/com/amazon/sampleapp/RemoteServiceController.java diff --git a/settings.gradle.kts b/settings.gradle.kts index 9cef7f2e7..154781a6d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,10 +26,6 @@ buildCache { // End to end tests include(":validator") -include(":sample-apps:java:8:springboot-main-service") -include(":sample-apps:java:8:springboot-remote-service") -include(":sample-apps:java:11+:springboot-main-service") -include(":sample-apps:java:11+:springboot-remote-service") //id("com.diffplug.spotless") version "6.22.0" //id("com.github.ben-manes.versions") version "0.50.0" diff --git a/terraform/java/ec2/asg/main.tf b/terraform/java/ec2/asg/main.tf index d7559fcec..7380fa96f 100644 --- a/terraform/java/ec2/asg/main.tf +++ b/terraform/java/ec2/asg/main.tf @@ -96,8 +96,14 @@ resource "aws_launch_configuration" "launch_configuration" { #!/bin/bash # Make the Terraform fail if any step throws an error set -o errexit - # Install Java 11 and wget - sudo yum install wget java-11-amazon-corretto -y + # Install wget + sudo yum install wget -y + # Install Java + if [[ "${var.language_version}" == "8" ]]; then + sudo yum install java-1.8.0-amazon-corretto -y + else + sudo yum install java-${var.language_version}-amazon-corretto -y + fi # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' @@ -186,8 +192,14 @@ resource "null_resource" "remote_service_setup" { <<-EOF # Make the Terraform fail if any step throws an error set -o errexit - # Install Java 11 and wget - sudo yum install wget java-11-amazon-corretto -y + # Install wget + sudo yum install wget -y + # Install Java + if [[ "${var.language_version}" == "8" ]]; then + sudo yum install java-1.8.0-amazon-corretto -y + else + sudo yum install java-${var.language_version}-amazon-corretto -y + fi # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' diff --git a/terraform/java/ec2/asg/variables.tf b/terraform/java/ec2/asg/variables.tf index 65efa382c..f03723d32 100644 --- a/terraform/java/ec2/asg/variables.tf +++ b/terraform/java/ec2/asg/variables.tf @@ -43,4 +43,8 @@ variable "get_adot_jar_command" { variable "canary_type" { default = "java-ec2-asg" +} + +variable "language_version" { + default = "11" } \ No newline at end of file diff --git a/terraform/java/ec2/default/main.tf b/terraform/java/ec2/default/main.tf index ba667e631..068802de8 100644 --- a/terraform/java/ec2/default/main.tf +++ b/terraform/java/ec2/default/main.tf @@ -107,8 +107,15 @@ resource "null_resource" "main_service_setup" { <<-EOF # Make the Terraform fail if any step throws an error set -o errexit - # Install Java 11 and wget - sudo yum install wget java-11-amazon-corretto -y + # Install wget + sudo yum install wget -y + # Install Java + echo + if [[ "${var.language_version}" == "8" ]]; then + sudo yum install java-1.8.0-amazon-corretto -y + else + sudo yum install java-${var.language_version}-amazon-corretto -y + fi # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' @@ -190,8 +197,14 @@ resource "null_resource" "remote_service_setup" { <<-EOF # Make the Terraform fail if any step throws an error set -o errexit - # Install Java 11 and wget - sudo yum install wget java-11-amazon-corretto -y + # Install wget + sudo yum install wget -y + # Install Java + if [[ "${var.language_version}" == "8" ]]; then + sudo yum install java-1.8.0-amazon-corretto -y + else + sudo yum install java-${var.language_version}-amazon-corretto -y + fi # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' @@ -257,7 +270,7 @@ resource "null_resource" "traffic_generator_setup" { sudo yum install nodejs aws-cli unzip tmux -y # Bring in the traffic generator files to EC2 Instance - aws s3 cp s3://aws-appsignals-sample-app-prod-${var.aws_region}/traffic-generator.zip ./traffic-generator.zip + aws s3 cp s3://aws-appsignals-sample-app-prod-${var.aws_region}-2/traffic-generator.zip ./traffic-generator.zip unzip ./traffic-generator.zip -d ./ # Install the traffic generator dependencies diff --git a/terraform/java/ec2/default/variables.tf b/terraform/java/ec2/default/variables.tf index 8b2750bb3..a186815dc 100644 --- a/terraform/java/ec2/default/variables.tf +++ b/terraform/java/ec2/default/variables.tf @@ -43,4 +43,8 @@ variable "get_adot_jar_command" { variable "canary_type" { default = "java-ec2-default" +} + +variable "language_version" { + default = "11" } \ No newline at end of file From a986e5df85bc926fdd95643f3f6cf5af709d317c Mon Sep 17 00:00:00 2001 From: Harry Date: Wed, 25 Sep 2024 17:02:22 -0700 Subject: [PATCH 6/7] Add Language Version Option for Java E2E EKS --- .github/workflows/java-ec2-asg-test.yml | 22 +- .github/workflows/java-ec2-canary.yml | 4 +- .github/workflows/java-ec2-default-retry.yml | 9 +- .github/workflows/java-ec2-default-test.yml | 22 +- .github/workflows/java-eks-canary.yml | 1 + .github/workflows/java-eks-retry.yml | 5 + .github/workflows/java-eks-test.yml | 10 +- .github/workflows/k8s-patch-os-jobs.yml | 180 ++++++++-------- .github/workflows/k8s-patch-os-matrix.yml | 31 ++- .github/workflows/test-2.yml | 211 ------------------- .github/workflows/test.yml | 70 ------ terraform/java/ec2/asg/main.tf | 28 +-- terraform/java/ec2/asg/variables.tf | 4 +- terraform/java/ec2/default/main.tf | 31 +-- terraform/java/ec2/default/variables.tf | 4 +- 15 files changed, 173 insertions(+), 459 deletions(-) delete mode 100644 .github/workflows/test-2.yml delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/java-ec2-asg-test.yml b/.github/workflows/java-ec2-asg-test.yml index 64edc2c9c..267d050b8 100644 --- a/.github/workflows/java-ec2-asg-test.yml +++ b/.github/workflows/java-ec2-asg-test.yml @@ -14,11 +14,11 @@ on: caller-workflow-name: required: true type: string - java-version: - description: "Currently support version 8, 11, 17, 21, 22" + cpu-architecture: + description: "Permitted values: x86_64 or arm64" required: false type: string - default: '11' + default: "x86_64" outputs: job-started: value: ${{ jobs.java-ec2-asg.outputs.job-started }} @@ -32,9 +32,9 @@ permissions: env: E2E_TEST_AWS_REGION: ${{ inputs.aws-region }} CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} - JAVA_VERSION: ${{ inputs.java-version }} - SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/java-main-service-v${{ inputs.java-version }}.jar - SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/java-remote-service-v${{ inputs.java-version }}.jar + CPU_ARCHITECTURE: ${{ inputs.cpu-architecture }} + SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/main-service.jar + SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/remote-service.jar E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} METRIC_NAMESPACE: ApplicationSignals @@ -54,7 +54,7 @@ jobs: run: echo "job-started=true" >> $GITHUB_OUTPUT - name: Generate testing id - run: echo TESTING_ID="${{ github.job }}-${{ github.run_id }}-${{ github.run_number }}-${{ github.run_attempt }}" >> $GITHUB_ENV + run: echo TESTING_ID="${{ github.run_id }}-${{ github.run_number }}-${RANDOM}" >> $GITHUB_ENV - uses: actions/checkout@v4 with: @@ -109,7 +109,11 @@ jobs: # Reusing the adot-main-build-staging-jar bucket to store the python wheel file echo GET_CW_AGENT_RPM_COMMAND= "aws s3 cp s3://${{ secrets.S3_INTEGRATION_BUCKET }}/integration-test/binary/${{ github.sha }}/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm ./cw-agent.rpm" >> $GITHUB_ENV else - echo GET_CW_AGENT_RPM_COMMAND="wget -O cw-agent.rpm https://amazoncloudwatch-agent-${{ env.E2E_TEST_AWS_REGION }}.s3.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm" >> $GITHUB_ENV + if [ "${{ env.CPU_ARCHITECTURE }}" = "x86_64" ]; then + echo GET_CW_AGENT_RPM_COMMAND="wget -O cw-agent.rpm https://amazoncloudwatch-agent-${{ inputs.aws-region }}.s3.${{ inputs.aws-region }}.amazonaws.com/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm" >> $GITHUB_ENV + elif [ "${{ env.CPU_ARCHITECTURE }}" = "arm64" ]; then + echo GET_CW_AGENT_RPM_COMMAND="wget -O cw-agent.rpm https://amazoncloudwatch-agent-${{ inputs.aws-region }}.s3.${{ inputs.aws-region }}.amazonaws.com/amazon_linux/arm64/latest/amazon-cloudwatch-agent.rpm" >> $GITHUB_ENV + fi fi - name: Set up terraform @@ -147,7 +151,7 @@ jobs: -var="sample_remote_app_jar=${{ env.SAMPLE_APP_REMOTE_SERVICE_JAR }}" \ -var="get_cw_agent_rpm_command=${{ env.GET_CW_AGENT_RPM_COMMAND }}" \ -var="get_adot_jar_command=${{ env.GET_ADOT_JAR_COMMAND }}" \ - -var="language_version=${{ env.JAVA_VERSION }}" \ + -var="cpu_architecture=${{ env.CPU_ARCHITECTURE }}" \ || deployment_failed=$? if [ $deployment_failed -eq 1 ]; then diff --git a/.github/workflows/java-ec2-canary.yml b/.github/workflows/java-ec2-canary.yml index 07aa32b92..4ce2f4fcb 100644 --- a/.github/workflows/java-ec2-canary.yml +++ b/.github/workflows/java-ec2-canary.yml @@ -29,7 +29,7 @@ jobs: with: aws-region: ${{ matrix.aws-region }} caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '11' + cpu-architecture: 'x86_64' maven: uses: ./.github/workflows/java-ec2-default-retry.yml @@ -38,4 +38,4 @@ jobs: aws-region: 'us-east-1' caller-workflow-name: 'appsignals-e2e-ec2-maven-canary-test' otel-source: 'maven' - java-version: '11' + cpu-architecture: 'x86_64' diff --git a/.github/workflows/java-ec2-default-retry.yml b/.github/workflows/java-ec2-default-retry.yml index c3ec8177c..f0f5e85c4 100644 --- a/.github/workflows/java-ec2-default-retry.yml +++ b/.github/workflows/java-ec2-default-retry.yml @@ -14,9 +14,10 @@ on: caller-workflow-name: required: true type: string - java-version: - required: true + cpu-architecture: + required: false type: string + default: "x86_64" otel-source: required: false type: string @@ -33,8 +34,8 @@ jobs: with: aws-region: ${{ inputs.aws-region }} caller-workflow-name: ${{ inputs.caller-workflow-name }} - java-version: ${{ inputs.java-version }} otel-source: ${{ inputs.otel-source }} + cpu-architecture: ${{ inputs.cpu-architecture }} java-ec2-default-attempt-2: needs: [ java-ec2-default-attempt-1 ] @@ -44,8 +45,8 @@ jobs: with: aws-region: ${{ inputs.aws-region }} caller-workflow-name: ${{ inputs.caller-workflow-name }} - java-version: ${{ inputs.java-version }} otel-source: ${{ inputs.otel-source }} + cpu-architecture: ${{ inputs.cpu-architecture }} publish-metric-attempt-1: needs: [ java-ec2-default-attempt-1, java-ec2-default-attempt-2 ] diff --git a/.github/workflows/java-ec2-default-test.yml b/.github/workflows/java-ec2-default-test.yml index 0b8835a5c..811b1a3bf 100644 --- a/.github/workflows/java-ec2-default-test.yml +++ b/.github/workflows/java-ec2-default-test.yml @@ -14,11 +14,11 @@ on: caller-workflow-name: required: true type: string - java-version: - description: "Currently support version 8, 11, 17, 21, 22" + cpu-architecture: + description: "Permitted values: x86_64 or arm64" required: false type: string - default: '11' + default: "x86_64" otel-source: required: false type: string @@ -36,10 +36,10 @@ permissions: env: E2E_TEST_AWS_REGION: ${{ inputs.aws-region }} CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} - JAVA_VERSION: ${{ inputs.java-version }} OTEL_SOURCE: ${{ inputs.otel-source }} - SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}-2/java-main-service-v${{ inputs.java-version }}.jar - SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}-2/java-remote-service-v${{ inputs.java-version }}.jar + CPU_ARCHITECTURE: ${{ inputs.cpu-architecture }} + SAMPLE_APP_FRONTEND_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/main-service.jar + SAMPLE_APP_REMOTE_SERVICE_JAR: s3://aws-appsignals-sample-app-prod-${{ inputs.aws-region }}/remote-service.jar E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} E2E_TEST_ROLE_NAME: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ROLE_NAME }} METRIC_NAMESPACE: ApplicationSignals @@ -63,7 +63,7 @@ jobs: - uses: actions/checkout@v4 with: - repository: 'harrryr/aws-application-signals-test-framework' + repository: 'aws-observability/aws-application-signals-test-framework' ref: ${{ env.CALLER_WORKFLOW_NAME == 'main-build' && 'main' || github.ref }} fetch-depth: 0 @@ -118,7 +118,11 @@ jobs: # Reusing the adot-main-build-staging-jar bucket to store the python wheel file echo GET_CW_AGENT_RPM_COMMAND= "aws s3 cp s3://${{ secrets.S3_INTEGRATION_BUCKET }}/integration-test/binary/${{ github.sha }}/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm ./cw-agent.rpm" >> $GITHUB_ENV else - echo GET_CW_AGENT_RPM_COMMAND="wget -O cw-agent.rpm https://amazoncloudwatch-agent-${{ inputs.aws-region }}.s3.${{ inputs.aws-region }}.amazonaws.com/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm" >> $GITHUB_ENV + if [ "${{ env.CPU_ARCHITECTURE }}" = "x86_64" ]; then + echo GET_CW_AGENT_RPM_COMMAND="wget -O cw-agent.rpm https://amazoncloudwatch-agent-${{ inputs.aws-region }}.s3.${{ inputs.aws-region }}.amazonaws.com/amazon_linux/amd64/latest/amazon-cloudwatch-agent.rpm" >> $GITHUB_ENV + elif [ "${{ env.CPU_ARCHITECTURE }}" = "arm64" ]; then + echo GET_CW_AGENT_RPM_COMMAND="wget -O cw-agent.rpm https://amazoncloudwatch-agent-${{ inputs.aws-region }}.s3.${{ inputs.aws-region }}.amazonaws.com/amazon_linux/arm64/latest/amazon-cloudwatch-agent.rpm" >> $GITHUB_ENV + fi fi - name: Set up terraform @@ -156,7 +160,7 @@ jobs: -var="sample_remote_app_jar=${{ env.SAMPLE_APP_REMOTE_SERVICE_JAR }}" \ -var="get_cw_agent_rpm_command=${{ env.GET_CW_AGENT_RPM_COMMAND }}" \ -var="get_adot_jar_command=${{ env.GET_ADOT_JAR_COMMAND }}" \ - -var="language_version=${{ env.JAVA_VERSION }}" \ + -var="cpu_architecture=${{ env.CPU_ARCHITECTURE }}" \ || deployment_failed=$? if [ $deployment_failed -eq 1 ]; then diff --git a/.github/workflows/java-eks-canary.yml b/.github/workflows/java-eks-canary.yml index 059a682fa..8d9a4d0f9 100644 --- a/.github/workflows/java-eks-canary.yml +++ b/.github/workflows/java-eks-canary.yml @@ -30,3 +30,4 @@ jobs: aws-region: ${{ matrix.aws-region }} test-cluster-name: 'e2e-canary-test' caller-workflow-name: 'appsignals-e2e-eks-canary-test' + java-version: '11' diff --git a/.github/workflows/java-eks-retry.yml b/.github/workflows/java-eks-retry.yml index ac671a137..b7f910dab 100644 --- a/.github/workflows/java-eks-retry.yml +++ b/.github/workflows/java-eks-retry.yml @@ -17,6 +17,9 @@ on: caller-workflow-name: required: true type: string + java-version: + required: true + type: string concurrency: group: 'java-eks-${{ inputs.aws-region }}-${{ github.ref_name }}' @@ -34,6 +37,7 @@ jobs: aws-region: ${{ inputs.aws-region }} test-cluster-name: ${{ inputs.test-cluster-name }} caller-workflow-name: ${{ inputs.caller-workflow-name }} + java-version: '11' java-eks-attempt-2: needs: [ java-eks-attempt-1 ] @@ -44,6 +48,7 @@ jobs: aws-region: ${{ inputs.aws-region }} test-cluster-name: ${{ inputs.test-cluster-name }} caller-workflow-name: ${{ inputs.caller-workflow-name }} + java-version: '11' publish-metric-attempt-1: needs: [ java-eks-attempt-1, java-eks-attempt-2 ] diff --git a/.github/workflows/java-eks-test.yml b/.github/workflows/java-eks-test.yml index 9dcc0679f..71a2b04ff 100644 --- a/.github/workflows/java-eks-test.yml +++ b/.github/workflows/java-eks-test.yml @@ -17,6 +17,11 @@ on: caller-workflow-name: required: true type: string + java-version: + description: "Currently support version 8, 11, 17, 21, 22" + required: false + type: string + default: '11' adot-image-name: required: false type: string @@ -37,6 +42,7 @@ env: E2E_TEST_AWS_REGION: ${{ inputs.aws-region }} CLUSTER_NAME: ${{ inputs.test-cluster-name }} CALLER_WORKFLOW_NAME: ${{ inputs.caller-workflow-name }} + JAVA_VERSION: ${{ inputs.java-version }} ADOT_IMAGE_NAME: ${{ inputs.adot-image-name }} CW_AGENT_OPERATOR_TAG: ${{ inputs.cw-agent-operator-tag }} E2E_TEST_ACCOUNT_ID: ${{ secrets.APPLICATION_SIGNALS_E2E_TEST_ACCOUNT_ID }} @@ -222,8 +228,8 @@ jobs: - name: Set Sample App Image run: | - echo MAIN_SAMPLE_APP_IMAGE_ARN="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}" >> $GITHUB_ENV - echo REMOTE_SAMPLE_APP_IMAGE_ARN="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}" >> $GITHUB_ENV + echo MAIN_SAMPLE_APP_IMAGE_ARN="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.JAVA_MAIN_SAMPLE_APP_IMAGE }}:v${{ env.JAVA_VERSION }}" >> $GITHUB_ENV + echo REMOTE_SAMPLE_APP_IMAGE_ARN="${{ env.ACCOUNT_ID }}.dkr.ecr.${{ env.E2E_TEST_AWS_REGION }}.amazonaws.com/${{ env.JAVA_REMOTE_SAMPLE_APP_IMAGE }}:v${{ env.JAVA_VERSION }}" >> $GITHUB_ENV - name: Deploy sample app via terraform and wait for the endpoint to come online id: deploy-sample-app diff --git a/.github/workflows/k8s-patch-os-jobs.yml b/.github/workflows/k8s-patch-os-jobs.yml index 159bb6887..d02745646 100644 --- a/.github/workflows/k8s-patch-os-jobs.yml +++ b/.github/workflows/k8s-patch-os-jobs.yml @@ -92,51 +92,51 @@ jobs: aws secretsmanager update-secret --secret-id "${SECRET_NAMES[0]}" --secret-string "$master_public_ip" aws secretsmanager update-secret --secret-id "${SECRET_NAMES[1]}" --secret-string "$private_key_content" -# run-java-k8s-test: -# if: ${{ inputs.LANGUAGE == 'java' }} -# needs: create-k8s-on-ec2 -# uses: ./.github/workflows/java-k8s-test.yml -# secrets: inherit -# with: -# aws-region: 'us-east-1' -# caller-workflow-name: 'k8s-os-patching' -# caller-repository: ${{ inputs.repo_name }} -# -# run-python-k8s-test: -# if: ${{ inputs.LANGUAGE == 'python' }} -# needs: create-k8s-on-ec2 -# uses: ./.github/workflows/python-k8s-test.yml -# secrets: inherit -# with: -# aws-region: 'us-east-1' -# caller-workflow-name: 'k8s-os-patching' -# caller-repository: ${{ inputs.repo_name }} -# -# run-dotnet-k8s-test: -# if: ${{ inputs.LANGUAGE == 'dotnet' }} -# needs: create-k8s-on-ec2 -# # TODO: Point to REAL K8s implementation after K8s Infra created. -# uses: ./.github/workflows/dummy-k8s-test.yml -# secrets: inherit -# with: -# aws-region: 'us-east-1' -# caller-workflow-name: 'k8s-os-patching' -# caller-repository: ${{ inputs.repo_name }} -# -# run-node-k8s-test: -# if: ${{ inputs.LANGUAGE == 'node' }} -# needs: create-k8s-on-ec2 -# # TODO: Point to REAL K8s implementation after K8s Infra created. -# uses: ./.github/workflows/dummy-k8s-test.yml -# secrets: inherit -# with: -# aws-region: 'us-east-1' -# caller-workflow-name: 'k8s-os-patching' -# caller-repository: ${{ inputs.repo_name }} + run-java-k8s-test: + if: ${{ inputs.LANGUAGE == 'java' }} + needs: create-k8s-on-ec2 + uses: ./.github/workflows/java-k8s-test.yml + secrets: inherit + with: + aws-region: 'us-east-1' + caller-workflow-name: 'k8s-os-patching' + caller-repository: ${{ inputs.repo_name }} + + run-python-k8s-test: + if: ${{ inputs.LANGUAGE == 'python' }} + needs: create-k8s-on-ec2 + uses: ./.github/workflows/python-k8s-test.yml + secrets: inherit + with: + aws-region: 'us-east-1' + caller-workflow-name: 'k8s-os-patching' + caller-repository: ${{ inputs.repo_name }} + + run-dotnet-k8s-test: + if: ${{ inputs.LANGUAGE == 'dotnet' }} + needs: create-k8s-on-ec2 + # TODO: Point to REAL K8s implementation after K8s Infra created. + uses: ./.github/workflows/dummy-k8s-test.yml + secrets: inherit + with: + aws-region: 'us-east-1' + caller-workflow-name: 'k8s-os-patching' + caller-repository: ${{ inputs.repo_name }} + + run-node-k8s-test: + if: ${{ inputs.LANGUAGE == 'node' }} + needs: create-k8s-on-ec2 + # TODO: Point to REAL K8s implementation after K8s Infra created. + uses: ./.github/workflows/dummy-k8s-test.yml + secrets: inherit + with: + aws-region: 'us-east-1' + caller-workflow-name: 'k8s-os-patching' + caller-repository: ${{ inputs.repo_name }} update-secrets: - needs: create-k8s-on-ec2 - # if: ${{ always() && (needs.run-java-k8s-test.result == 'success' || needs.run-python-k8s-test.result == 'success' || needs.run-dotnet-k8s-test.result == 'success' || needs.run-node-k8s-test.result == 'success') }} + needs: [ run-java-k8s-test, run-python-k8s-test, run-dotnet-k8s-test, run-node-k8s-test ] + if: ${{ always() && (needs.run-java-k8s-test.result == 'success' || needs.run-python-k8s-test.result == 'success' || needs.run-dotnet-k8s-test.result == 'success' || needs.run-node-k8s-test.result == 'success') }} runs-on: ubuntu-latest steps: - name: Configure AWS Credentials @@ -181,50 +181,50 @@ jobs: aws secretsmanager update-secret --secret-id "${CURRENT_SECRET_KEYS[0]}" --secret-string "$endpoint_secret" aws secretsmanager update-secret --secret-id "${CURRENT_SECRET_KEYS[1]}" --secret-string "$ssh_secret" -# publish-metric: -# needs: [ update-secrets ] -# if: always() -# uses: ./.github/workflows/enablement-test-publish-result.yml -# secrets: inherit -# with: -# aws-region: 'us-east-1' -# caller-workflow-name: 'enablement-test-k8s-patch-os' -# validation-result: ${{ needs.update-secrets.result }} -# -# cleanup-if-failed: -# needs: [ run-java-k8s-test, run-python-k8s-test, run-dotnet-k8s-test, run-node-k8s-test ] -# if: ${{ always() && (needs.run-java-k8s-test.result != 'success' && needs.run-python-k8s-test.result != 'success' && needs.run-dotnet-k8s-test.result != 'success' && needs.run-node-k8s-test.result != 'success') }} -# runs-on: ubuntu-latest -# steps: -# - name: Delete Key Pair and EC2 Instance -# run: | -# PENDING_SECRET_KEYS=( -# "e2e-test/${{ matrix.instance.repo_name }}/${{ matrix.instance.language }}-k8s-master-node-endpoint-pending-value" -# ) -# endpoint_secret=$(aws secretsmanager get-secret-value --secret-id "${PENDING_SECRET_KEYS[0]}" --query SecretString --output text) -# -# instance_name=$(aws ec2 describe-instances \ -# --filters "Name=ip-address,Values=$endpoint_secret" \ -# --query "Reservations[*].Instances[*].{Name:Tags[?Key=='Name']|[0].Value}" \ -# --output text) -# -# if [ -n "$instance_name" ]; then -# prev_testing_id=$(echo $instance_name | awk -F'-' '{print $(NF-1)"-"$NF}') -# -# aws ec2 delete-key-pair --key-name "k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-key-pair-$prev_testing_id" -# -# main_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-master-$prev_testing_id -# worker_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-worker-$prev_testing_id -# main_instance_id=$(aws ec2 describe-instances \ -# --filters "Name=tag:Name,Values=$main_instance_name" \ -# --query "Reservations[*].Instances[*].InstanceId" \ -# --output text) -# -# worker_instance_id=$(aws ec2 describe-instances \ -# --filters "Name=tag:Name,Values=$worker_instance_name" \ -# --query "Reservations[*].Instances[*].InstanceId" \ -# --output text) -# -# aws ec2 terminate-instances --instance-ids $main_instance_id -# aws ec2 terminate-instances --instance-ids $worker_instance_id -# fi + publish-metric: + needs: [ update-secrets ] + if: always() + uses: ./.github/workflows/enablement-test-publish-result.yml + secrets: inherit + with: + aws-region: 'us-east-1' + caller-workflow-name: 'enablement-test-k8s-patch-os' + validation-result: ${{ needs.update-secrets.result }} + + cleanup-if-failed: + needs: [ run-java-k8s-test, run-python-k8s-test, run-dotnet-k8s-test, run-node-k8s-test ] + if: ${{ always() && (needs.run-java-k8s-test.result != 'success' && needs.run-python-k8s-test.result != 'success' && needs.run-dotnet-k8s-test.result != 'success' && needs.run-node-k8s-test.result != 'success') }} + runs-on: ubuntu-latest + steps: + - name: Delete Key Pair and EC2 Instance + run: | + PENDING_SECRET_KEYS=( + "e2e-test/${{ matrix.instance.repo_name }}/${{ matrix.instance.language }}-k8s-master-node-endpoint-pending-value" + ) + endpoint_secret=$(aws secretsmanager get-secret-value --secret-id "${PENDING_SECRET_KEYS[0]}" --query SecretString --output text) + + instance_name=$(aws ec2 describe-instances \ + --filters "Name=ip-address,Values=$endpoint_secret" \ + --query "Reservations[*].Instances[*].{Name:Tags[?Key=='Name']|[0].Value}" \ + --output text) + + if [ -n "$instance_name" ]; then + prev_testing_id=$(echo $instance_name | awk -F'-' '{print $(NF-1)"-"$NF}') + + aws ec2 delete-key-pair --key-name "k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-key-pair-$prev_testing_id" + + main_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-master-$prev_testing_id + worker_instance_name=k8s-on-ec2-${{ matrix.instance.ec2_name }}-${{ matrix.instance.language }}-worker-$prev_testing_id + main_instance_id=$(aws ec2 describe-instances \ + --filters "Name=tag:Name,Values=$main_instance_name" \ + --query "Reservations[*].Instances[*].InstanceId" \ + --output text) + + worker_instance_id=$(aws ec2 describe-instances \ + --filters "Name=tag:Name,Values=$worker_instance_name" \ + --query "Reservations[*].Instances[*].InstanceId" \ + --output text) + + aws ec2 terminate-instances --instance-ids $main_instance_id + aws ec2 terminate-instances --instance-ids $worker_instance_id + fi diff --git a/.github/workflows/k8s-patch-os-matrix.yml b/.github/workflows/k8s-patch-os-matrix.yml index e07026c8b..95254b869 100644 --- a/.github/workflows/k8s-patch-os-matrix.yml +++ b/.github/workflows/k8s-patch-os-matrix.yml @@ -6,7 +6,6 @@ on: schedule: - cron: '0 0 1 * *' # run the workflow beginning of every month workflow_dispatch: # be able to run the workflow on demand - push: permissions: id-token: write @@ -18,22 +17,22 @@ jobs: fail-fast: false matrix: instance: [ -# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'java' }, -# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'python' }, -# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'dotnet' }, -# { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'node' }, -# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'java' }, -# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'python' }, -# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'dotnet' }, -# { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'node' }, -# { repo_name: 'aws-otel-java-instrumentation', ec2_name: 'adot-java-release', language: 'java' }, -# { repo_name: 'aws-otel-python-instrumentation', ec2_name: 'adot-python-release', language: 'python' }, -# { repo_name: 'aws-otel-dotnet-instrumentation', ec2_name: 'adot-dotnet-release', language: 'dotnet' }, -# { repo_name: 'aws-otel-js-instrumentation', ec2_name: 'adot-node-release', language: 'node' }, + { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'java' }, + { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'python' }, + { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'dotnet' }, + { repo_name: 'amazon-cloudwatch-agent-operator', ec2_name: 'cw-agent-operator-release', language: 'node' }, + { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'java' }, + { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'python' }, + { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'dotnet' }, + { repo_name: 'amazon-cloudwatch-agent', ec2_name: 'cw-agent-release', language: 'node' }, + { repo_name: 'aws-otel-java-instrumentation', ec2_name: 'adot-java-release', language: 'java' }, + { repo_name: 'aws-otel-python-instrumentation', ec2_name: 'adot-python-release', language: 'python' }, + { repo_name: 'aws-otel-dotnet-instrumentation', ec2_name: 'adot-dotnet-release', language: 'dotnet' }, + { repo_name: 'aws-otel-js-instrumentation', ec2_name: 'adot-node-release', language: 'node' }, { repo_name: 'aws-application-signals-test-framework', ec2_name: 'java-canary', language: 'java' }, - { repo_name: 'aws-application-signals-test-framework', ec2_name: 'python-canary', language: 'python' } ] -# { repo_name: 'aws-application-signals-test-framework', ec2_name: 'dotnet-canary', language: 'dotnet' }, -# { repo_name: 'aws-application-signals-test-framework', ec2_name: 'node-canary', language: 'node' } ] + { repo_name: 'aws-application-signals-test-framework', ec2_name: 'python-canary', language: 'python' }, + { repo_name: 'aws-application-signals-test-framework', ec2_name: 'dotnet-canary', language: 'dotnet' }, + { repo_name: 'aws-application-signals-test-framework', ec2_name: 'node-canary', language: 'node' } ] uses: ./.github/workflows/k8s-patch-os-jobs.yml secrets: inherit with: diff --git a/.github/workflows/test-2.yml b/.github/workflows/test-2.yml deleted file mode 100644 index 015be242d..000000000 --- a/.github/workflows/test-2.yml +++ /dev/null @@ -1,211 +0,0 @@ -## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -## SPDX-License-Identifier: Apache-2.0 - -## This workflow aims to run the Application Signals end-to-end tests as a canary to -## test the artifacts for App Signals enablement. It will deploy a sample app and remote -## service onto an EKS cluster, call the APIs, and validate the generated telemetry, -## including logs, metrics, and traces. -name: Java EKS Enablement Canary Testing -on: -# push: - workflow_dispatch: - - -permissions: - id-token: write - contents: read - -jobs: - eks-8: - strategy: - fail-fast: false - matrix: - aws-region: [ 'us-east-1' ] - uses: ./.github/workflows/java-eks-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - test-cluster-name: 'e2e-canary-test' - caller-workflow-name: 'appsignals-e2e-eks-canary-test' - java-version: '8' - - eks-11: - needs: eks-8 - strategy: - fail-fast: false - matrix: - aws-region: [ 'us-east-1' ] - uses: ./.github/workflows/java-eks-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - test-cluster-name: 'e2e-canary-test' - caller-workflow-name: 'appsignals-e2e-eks-canary-test' - java-version: '11' - - eks-17: - needs: eks-11 - strategy: - fail-fast: false - matrix: - aws-region: [ 'us-east-1' ] - uses: ./.github/workflows/java-eks-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - test-cluster-name: 'e2e-canary-test' - caller-workflow-name: 'appsignals-e2e-eks-canary-test' - java-version: '17' - - eks-21: - needs: eks-17 - strategy: - fail-fast: false - matrix: - aws-region: [ 'us-east-1' ] - uses: ./.github/workflows/java-eks-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - test-cluster-name: 'e2e-canary-test' - caller-workflow-name: 'appsignals-e2e-eks-canary-test' - java-version: '21' - - eks-22: - needs: eks-21 - strategy: - fail-fast: false - matrix: - aws-region: [ 'us-east-1' ] - uses: ./.github/workflows/java-eks-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - test-cluster-name: 'e2e-canary-test' - caller-workflow-name: 'appsignals-e2e-eks-canary-test' - java-version: '22' - - github-8: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '8' - - github-11: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '11' - - github-17: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '17' - - github-21: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '21' - - github-22: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '22' - - maven-8: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '8' - otel-source: 'maven' - - maven-11: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '11' - otel-source: 'maven' - - maven-17: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '17' - otel-source: 'maven' - - maven-21: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '21' - otel-source: 'maven' - - maven-22: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '22' - otel-source: 'maven' \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index 2f8379d60..000000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,70 +0,0 @@ -## Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. -## SPDX-License-Identifier: Apache-2.0 - -## This workflow aims to run the Application Signals end-to-end tests as a canary to -## test the artifacts for App Signals enablement. It will deploy a sample app and remote -## service onto an EKS cluster, call the APIs, and validate the generated telemetry, -## including logs, metrics, and traces. -name: Java EKS Enablement Canary Testing -on: - push: - -permissions: - id-token: write - contents: read - -jobs: - eks: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-eks-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - test-cluster-name: 'e2e-canary-test' - caller-workflow-name: 'appsignals-e2e-eks-canary-test' - java-version: '11' - - k8s: - uses: ./.github/workflows/java-k8s-retry.yml - secrets: inherit - with: - # To run in more regions, a cluster must be provisioned manually on EC2 instances in that region - aws-region: 'us-east-1' - caller-workflow-name: 'appsignals-e2e-k8s-canary-test' - java-version: '11' - - ecs: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ecs-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-java-ecs-canary-test' - java-version: '11' - - github: - strategy: - fail-fast: false - matrix: - aws-region: ['us-east-1'] - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: ${{ matrix.aws-region }} - caller-workflow-name: 'appsignals-e2e-ec2-canary-test' - java-version: '11' - - maven: - uses: ./.github/workflows/java-ec2-default-retry.yml - secrets: inherit - with: - aws-region: 'us-east-1' - caller-workflow-name: 'appsignals-e2e-ec2-maven-canary-test' - otel-source: 'maven' - java-version: '11' \ No newline at end of file diff --git a/terraform/java/ec2/asg/main.tf b/terraform/java/ec2/asg/main.tf index 7380fa96f..b930d51b4 100644 --- a/terraform/java/ec2/asg/main.tf +++ b/terraform/java/ec2/asg/main.tf @@ -53,7 +53,7 @@ data "aws_ami" "ami" { most_recent = true filter { name = "name" - values = ["al20*-ami-minimal-*-x86_64"] + values = ["al20*-ami-minimal-*-${var.cpu_architecture}"] } filter { name = "state" @@ -61,7 +61,7 @@ data "aws_ami" "ami" { } filter { name = "architecture" - values = ["x86_64"] + values = [var.cpu_architecture] } filter { name = "image-type" @@ -86,7 +86,7 @@ data "aws_ami" "ami" { resource "aws_launch_configuration" "launch_configuration" { image_id = data.aws_ami.ami.id - instance_type = "t3.micro" + instance_type = var.cpu_architecture == "x86_64" ? "t3.micro" : "t4g.micro" key_name = local.ssh_key_name associate_public_ip_address = true iam_instance_profile = "APP_SIGNALS_EC2_TEST_ROLE" @@ -96,14 +96,8 @@ resource "aws_launch_configuration" "launch_configuration" { #!/bin/bash # Make the Terraform fail if any step throws an error set -o errexit - # Install wget - sudo yum install wget -y - # Install Java - if [[ "${var.language_version}" == "8" ]]; then - sudo yum install java-1.8.0-amazon-corretto -y - else - sudo yum install java-${var.language_version}-amazon-corretto -y - fi + # Install Java 11 and wget + sudo yum install wget java-11-amazon-corretto -y # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' @@ -164,7 +158,7 @@ resource "aws_autoscaling_group" "asg" { resource "aws_instance" "remote_service_instance" { ami = data.aws_ami.ami.id # Amazon Linux 2 (free tier) - instance_type = "t3.micro" + instance_type = var.cpu_architecture == "x86_64" ? "t3.micro" : "t4g.micro" key_name = local.ssh_key_name iam_instance_profile = "APP_SIGNALS_EC2_TEST_ROLE" vpc_security_group_ids = [aws_default_vpc.default.default_security_group_id] @@ -192,14 +186,8 @@ resource "null_resource" "remote_service_setup" { <<-EOF # Make the Terraform fail if any step throws an error set -o errexit - # Install wget - sudo yum install wget -y - # Install Java - if [[ "${var.language_version}" == "8" ]]; then - sudo yum install java-1.8.0-amazon-corretto -y - else - sudo yum install java-${var.language_version}-amazon-corretto -y - fi + # Install Java 11 and wget + sudo yum install wget java-11-amazon-corretto -y # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' diff --git a/terraform/java/ec2/asg/variables.tf b/terraform/java/ec2/asg/variables.tf index f03723d32..72b70243d 100644 --- a/terraform/java/ec2/asg/variables.tf +++ b/terraform/java/ec2/asg/variables.tf @@ -45,6 +45,6 @@ variable "canary_type" { default = "java-ec2-asg" } -variable "language_version" { - default = "11" +variable "cpu_architecture" { + default = "x86_64" } \ No newline at end of file diff --git a/terraform/java/ec2/default/main.tf b/terraform/java/ec2/default/main.tf index 068802de8..f432b8154 100644 --- a/terraform/java/ec2/default/main.tf +++ b/terraform/java/ec2/default/main.tf @@ -46,7 +46,7 @@ data "aws_ami" "ami" { most_recent = true filter { name = "name" - values = ["al20*-ami-minimal-*-x86_64"] + values = ["al20*-ami-minimal-*-${var.cpu_architecture}"] } filter { name = "state" @@ -54,7 +54,7 @@ data "aws_ami" "ami" { } filter { name = "architecture" - values = ["x86_64"] + values = [var.cpu_architecture] } filter { name = "image-type" @@ -79,7 +79,7 @@ data "aws_ami" "ami" { resource "aws_instance" "main_service_instance" { ami = data.aws_ami.ami.id # Amazon Linux 2 (free tier) - instance_type = "t3.micro" + instance_type = var.cpu_architecture == "x86_64" ? "t3.micro" : "t4g.micro" key_name = local.ssh_key_name iam_instance_profile = "APP_SIGNALS_EC2_TEST_ROLE" vpc_security_group_ids = [aws_default_vpc.default.default_security_group_id] @@ -107,15 +107,8 @@ resource "null_resource" "main_service_setup" { <<-EOF # Make the Terraform fail if any step throws an error set -o errexit - # Install wget - sudo yum install wget -y - # Install Java - echo - if [[ "${var.language_version}" == "8" ]]; then - sudo yum install java-1.8.0-amazon-corretto -y - else - sudo yum install java-${var.language_version}-amazon-corretto -y - fi + # Install Java 11 and wget + sudo yum install wget java-11-amazon-corretto -y # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' @@ -169,7 +162,7 @@ resource "null_resource" "main_service_setup" { resource "aws_instance" "remote_service_instance" { ami = data.aws_ami.ami.id # Amazon Linux 2 (free tier) - instance_type = "t3.micro" + instance_type = var.cpu_architecture == "x86_64" ? "t3.micro" : "t4g.micro" key_name = local.ssh_key_name iam_instance_profile = "APP_SIGNALS_EC2_TEST_ROLE" vpc_security_group_ids = [aws_default_vpc.default.default_security_group_id] @@ -197,14 +190,8 @@ resource "null_resource" "remote_service_setup" { <<-EOF # Make the Terraform fail if any step throws an error set -o errexit - # Install wget - sudo yum install wget -y - # Install Java - if [[ "${var.language_version}" == "8" ]]; then - sudo yum install java-1.8.0-amazon-corretto -y - else - sudo yum install java-${var.language_version}-amazon-corretto -y - fi + # Install Java 11 and wget + sudo yum install wget java-11-amazon-corretto -y # Copy in CW Agent configuration agent_config='${replace(replace(file("./amazon-cloudwatch-agent.json"), "/\\s+/", ""), "$REGION", var.aws_region)}' @@ -270,7 +257,7 @@ resource "null_resource" "traffic_generator_setup" { sudo yum install nodejs aws-cli unzip tmux -y # Bring in the traffic generator files to EC2 Instance - aws s3 cp s3://aws-appsignals-sample-app-prod-${var.aws_region}-2/traffic-generator.zip ./traffic-generator.zip + aws s3 cp s3://aws-appsignals-sample-app-prod-${var.aws_region}/traffic-generator.zip ./traffic-generator.zip unzip ./traffic-generator.zip -d ./ # Install the traffic generator dependencies diff --git a/terraform/java/ec2/default/variables.tf b/terraform/java/ec2/default/variables.tf index a186815dc..89c446c19 100644 --- a/terraform/java/ec2/default/variables.tf +++ b/terraform/java/ec2/default/variables.tf @@ -45,6 +45,6 @@ variable "canary_type" { default = "java-ec2-default" } -variable "language_version" { - default = "11" +variable "cpu_architecture" { + default = "x86_64" } \ No newline at end of file From d5a681c1aac7c606c1c749e8700b6540831812c7 Mon Sep 17 00:00:00 2001 From: Harry Date: Fri, 27 Sep 2024 09:19:40 -0700 Subject: [PATCH 7/7] Switch to inputs for 11 --- .github/workflows/java-eks-retry.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/java-eks-retry.yml b/.github/workflows/java-eks-retry.yml index b7f910dab..c5b35c398 100644 --- a/.github/workflows/java-eks-retry.yml +++ b/.github/workflows/java-eks-retry.yml @@ -37,7 +37,7 @@ jobs: aws-region: ${{ inputs.aws-region }} test-cluster-name: ${{ inputs.test-cluster-name }} caller-workflow-name: ${{ inputs.caller-workflow-name }} - java-version: '11' + java-version: ${{ inputs.java-version }} java-eks-attempt-2: needs: [ java-eks-attempt-1 ] @@ -48,7 +48,7 @@ jobs: aws-region: ${{ inputs.aws-region }} test-cluster-name: ${{ inputs.test-cluster-name }} caller-workflow-name: ${{ inputs.caller-workflow-name }} - java-version: '11' + java-version: ${{ inputs.java-version }} publish-metric-attempt-1: needs: [ java-eks-attempt-1, java-eks-attempt-2 ]