diff --git a/.github/patches/versions b/.github/patches/versions index 36dfee165f..b728dc5ebb 100644 --- a/.github/patches/versions +++ b/.github/patches/versions @@ -1,2 +1,2 @@ -OTEL_JAVA_INSTRUMENTATION_VERSION=v2.20.1 -OTEL_JAVA_CONTRIB_VERSION=v1.48.0 \ No newline at end of file +OTEL_JAVA_INSTRUMENTATION_VERSION=v2.21.0 +OTEL_JAVA_CONTRIB_VERSION=v1.51.0 \ No newline at end of file diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 622cba16e3..4da7bcd32c 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -5,6 +5,12 @@ on: - main - "release/v*" workflow_dispatch: # be able to run the workflow on demand + workflow_call: + inputs: + ref: + description: 'The branch, tag or SHA to checkout' + required: false + type: string env: AWS_DEFAULT_REGION: us-east-1 STAGING_ECR_REGISTRY: 611364707713.dkr.ecr.us-west-2.amazonaws.com @@ -24,6 +30,8 @@ jobs: runs-on: aws-otel-java-instrumentation_ubuntu-latest_32-core steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 + with: + ref: ${{ inputs.ref || github.sha }} - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0 with: java-version-file: .java-version @@ -58,6 +66,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: fetch-depth: 0 + ref: ${{ inputs.ref || github.sha }} - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0 with: java-version-file: .java-version @@ -193,6 +202,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: fetch-depth: 0 + ref: ${{ inputs.ref || github.sha }} - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0 with: java-version: 23 @@ -233,6 +243,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 with: fetch-depth: 0 + ref: ${{ inputs.ref || github.sha }} - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0 with: java-version-file: .java-version @@ -268,7 +279,7 @@ jobs: name: "Publish Main Build Status" needs: [ build, e2e-test, contract-tests, application-signals-lambda-layer-build, application-signals-e2e-test ] runs-on: ubuntu-latest - if: always() + if: always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/v')) steps: - name: Configure AWS Credentials for emitting metrics uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml new file mode 100644 index 0000000000..27cc1833a4 --- /dev/null +++ b/.github/workflows/nightly-build.yml @@ -0,0 +1,172 @@ +name: Nightly Upstream Snapshot Build +description: This workflow checks for new upstream versions of OpenTelemetry dependencies, creates a PR to update them, and builds and tests the new changes. + +on: + schedule: + - cron: "21 3 * * *" + workflow_dispatch: + push: + branches: + - zhaez/nightly-build + +env: + AWS_DEFAULT_REGION: us-east-1 + BRANCH_NAME: nightly-dependency-updates + +permissions: + id-token: write + contents: write + pull-requests: write + +jobs: + update-dependencies: + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check_changes.outputs.has_changes }} + otel_java_instrumentation_version: ${{ steps.get_versions.outputs.otel_java_instrumentation_version }} + otel_java_contrib_version: ${{ steps.get_versions.outputs.otel_java_contrib_version }} + breaking_changes_info: ${{ steps.breaking_changes.outputs.breaking_changes_info }} + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #5.0.0 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up Python + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c #v6.0.0 + with: + python-version: '3.x' + + - name: Install build tools + run: | + python -m pip install --upgrade pip + pip install requests packaging + + - name: Get latest upstream versions + id: get_versions + run: python scripts/get_upstream_versions.py + + - name: Check for breaking changes + id: breaking_changes + env: + OTEL_JAVA_INSTRUMENTATION_VERSION: ${{ steps.get_versions.outputs.otel_java_instrumentation_version }} + OTEL_JAVA_CONTRIB_VERSION: ${{ steps.get_versions.outputs.otel_java_contrib_version }} + run: python scripts/find_breaking_changes.py + + - name: Setup Git + run: | + git config user.name "github-actions" + git config user.email "github-actions@github.com" + + - name: Check out dependency update branch + run: | + if git ls-remote --exit-code --heads origin "$BRANCH_NAME"; then + echo "Branch $BRANCH_NAME already exists, checking out..." + git checkout "$BRANCH_NAME" + else + echo "Branch $BRANCH_NAME does not exist, creating new branch..." + git checkout -b "$BRANCH_NAME" + fi + + - name: Update dependencies + env: + OTEL_JAVA_INSTRUMENTATION_VERSION: ${{ steps.get_versions.outputs.otel_java_instrumentation_version }} + OTEL_JAVA_CONTRIB_VERSION: ${{ steps.get_versions.outputs.otel_java_contrib_version }} + run: python scripts/update_dependencies.py + + - name: Check for changes and commit + id: check_changes + run: | + if git diff --quiet; then + echo "No dependency updates needed" + echo "has_changes=false" >> $GITHUB_OUTPUT + else + echo "Dependencies were updated" + echo "has_changes=true" >> $GITHUB_OUTPUT + + git add . + git commit -m "chore: update OpenTelemetry dependencies to ${{ steps.get_versions.outputs.otel_java_instrumentation_version }}/${{ steps.get_versions.outputs.otel_java_contrib_version }}" + git push origin "$BRANCH_NAME" + fi + + build-and-test: + needs: update-dependencies + if: needs.update-dependencies.outputs.has_changes == 'true' + uses: ./.github/workflows/main-build.yml + secrets: inherit + permissions: + id-token: write + contents: read + with: + ref: nightly-dependency-updates + + create-pr: + needs: [update-dependencies, build-and-test] + if: always() && needs.update-dependencies.outputs.has_changes == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #5.0.0 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Create or update PR + run: | + BUILD_STATUS="${{ needs.build-and-test.result }}" + BUILD_EMOJI="${{ needs.build-and-test.result == 'success' && '✅' || '❌' }}" + BUILD_LINK="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" + + PR_BODY="Automated update of OpenTelemetry dependencies. + + **Build Status:** ${BUILD_EMOJI} [${BUILD_STATUS}](${BUILD_LINK}) + + **Updated versions:** + - [OpenTelemetry Java Instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/tag/v${{ needs.update-dependencies.outputs.otel_java_instrumentation_version }}): ${{ needs.update-dependencies.outputs.otel_java_instrumentation_version }} + - [OpenTelemetry Java Contrib](https://github.com/open-telemetry/opentelemetry-java-contrib/releases/tag/v${{ needs.update-dependencies.outputs.otel_java_contrib_version }}): ${{ needs.update-dependencies.outputs.otel_java_contrib_version }} + + **Upstream releases with breaking changes:** + Note: the mechanism to detect upstream breaking changes is not perfect. Be sure to check all new releases and understand if any additional changes need to be addressed. + + ${{ needs.update-dependencies.outputs.breaking_changes_info }}" + + if gh pr view "$BRANCH_NAME" --json state --jq '.state' 2>/dev/null | grep -q "OPEN"; then + echo "Open PR already exists, updating description..." + gh pr edit "$BRANCH_NAME" --body "$PR_BODY" + else + echo "Creating new PR..." + gh pr create \ + --title "Nightly dependency update: OpenTelemetry ${{ needs.update-dependencies.outputs.otel_java_instrumentation_version }}/${{ needs.update-dependencies.outputs.otel_java_contrib_version }}" \ + --body "$PR_BODY" \ + --base main \ + --head "$BRANCH_NAME" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + publish-nightly-build-status: + name: "Publish Nightly Build Status" + needs: [update-dependencies, build-and-test, create-pr] + runs-on: ubuntu-latest + if: always() + steps: + - name: Configure AWS Credentials for emitting metrics + uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0 + with: + role-to-assume: ${{ secrets.METRICS_ROLE_ARN }} + aws-region: ${{ env.AWS_DEFAULT_REGION }} + + - name: Publish nightly build status + run: | + if [[ "${{ needs.build-and-test.result }}" == "skipped" ]]; then + echo "Build was skipped (no changes)" + value="0.0" + else + value="${{ (needs.build-and-test.result == 'success' && needs.create-pr.result == 'success') && '0.0' || '1.0'}}" + fi + + aws cloudwatch put-metric-data --namespace 'ADOT/GitHubActions' \ + --metric-name Failure \ + --dimensions repository=${{ github.repository }},branch=${{ github.ref_name }},workflow=nightly_build \ + --value $value diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 6ca4ff5591..96b51d8a72 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -27,8 +27,8 @@ data class DependencySet(val group: String, val version: String, val modules: Li val testSnapshots = rootProject.findProperty("testUpstreamSnapshots") == "true" // This is the version of the upstream instrumentation BOM -val otelVersion = "2.20.1-adot1" -val otelSnapshotVersion = "2.21.0" +val otelVersion = "2.21.0-adot1" +val otelSnapshotVersion = "2.22.0" val otelAlphaVersion = if (!testSnapshots) "$otelVersion-alpha" else "$otelSnapshotVersion-alpha-SNAPSHOT" val otelJavaAgentVersion = if (!testSnapshots) otelVersion else "$otelSnapshotVersion-SNAPSHOT" // All versions below are only used in testing and do not affect the released artifact. @@ -77,8 +77,8 @@ val dependencyLists = listOf( "commons-logging:commons-logging:1.2", "com.sparkjava:spark-core:2.9.4", "com.squareup.okhttp3:okhttp:4.12.0", - "io.opentelemetry.contrib:opentelemetry-aws-xray:1.48.0-adot1", - "io.opentelemetry.contrib:opentelemetry-aws-resources:1.48.0-alpha", + "io.opentelemetry.contrib:opentelemetry-aws-xray:1.51.0-adot1", + "io.opentelemetry.contrib:opentelemetry-aws-resources:1.51.0-alpha", "io.opentelemetry.proto:opentelemetry-proto:1.0.0-alpha", "io.opentelemetry.javaagent:opentelemetry-javaagent:$otelJavaAgentVersion", "io.opentelemetry:opentelemetry-extension-aws:1.20.1", diff --git a/scripts/find_breaking_changes.py b/scripts/find_breaking_changes.py new file mode 100644 index 0000000000..3a8e9d49e2 --- /dev/null +++ b/scripts/find_breaking_changes.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0 + +import os +import re +import sys + +import requests +from packaging import version + + +def get_current_version_from_gradle(): + """Extract current OpenTelemetry versions from build.gradle.kts.""" + try: + with open("dependencyManagement/build.gradle.kts", "r", encoding="utf-8") as file: + content = file.read() + + # Extract otelVersion (instrumentation version) and strip -adot1 suffix + otel_version_match = re.search(r'val otelVersion = "([^"]*)"', content) + current_instrumentation_version = otel_version_match.group(1) if otel_version_match else None + if current_instrumentation_version and current_instrumentation_version.endswith("-adot1"): + current_instrumentation_version = current_instrumentation_version[:-6] # Remove -adot1 + + # Extract contrib version from dependency line and strip -adot1 suffix + contrib_match = re.search(r'"io\.opentelemetry\.contrib:opentelemetry-aws-xray:([^"]*)",', content) + current_contrib_version = contrib_match.group(1) if contrib_match else None + if current_contrib_version and current_contrib_version.endswith("-adot1"): + current_contrib_version = current_contrib_version[:-6] # Remove -adot1 + + return current_instrumentation_version, current_contrib_version + + except (OSError, IOError) as error: + print(f"Error reading current versions: {error}") + return None, None + + +def get_releases_with_breaking_changes(repo, current_version, new_version): + """Get releases between current and new version that mention breaking changes.""" + try: + response = requests.get(f"https://api.github.com/repos/open-telemetry/{repo}/releases", timeout=30) + response.raise_for_status() + releases = response.json() + + breaking_releases = [] + + for release in releases: + try: + tag_name = release["tag_name"] + release_version = tag_name.lstrip("v") + + # Check if this release is between current and new version + if ( + version.parse(current_version) + < version.parse(release_version) + <= version.parse(new_version) + ): + + # Check if release notes have breaking changes header or bold text + body = release.get("body", "") + if re.search(r"^\s*(#+|\*\*).*breaking changes", body, re.MULTILINE | re.IGNORECASE): + breaking_releases.append( + { + "version": release_version, + "name": release["name"], + "url": release["html_url"], + "body": release.get("body", ""), + } + ) + except (ValueError, KeyError) as parse_error: + print(f"Warning: Skipping release {release.get('name', 'unknown')} due to error: {parse_error}") + continue + + return breaking_releases + + except requests.RequestException as request_error: + print(f"Warning: Could not get releases for {repo}: {request_error}") + return [] + + +def main(): + new_instrumentation_version = os.environ.get("OTEL_JAVA_INSTRUMENTATION_VERSION") + new_contrib_version = os.environ.get("OTEL_JAVA_CONTRIB_VERSION") + + if not new_instrumentation_version or not new_contrib_version: + print("Error: OTEL_JAVA_INSTRUMENTATION_VERSION and OTEL_JAVA_CONTRIB_VERSION environment variables required") + sys.exit(1) + + current_instrumentation_version, current_contrib_version = get_current_version_from_gradle() + + if not current_instrumentation_version: + print("Could not determine current versions") + sys.exit(1) + + print("Checking for breaking changes:") + print(f"Instrumentation: {current_instrumentation_version} → {new_instrumentation_version}") + print(f"Contrib: {current_contrib_version or 'unknown'} → {new_contrib_version}") + + # Check both repos for breaking changes + instrumentation_breaking = get_releases_with_breaking_changes( + "opentelemetry-java-instrumentation", current_instrumentation_version, new_instrumentation_version + ) + contrib_breaking = [] + if current_contrib_version: + contrib_breaking = get_releases_with_breaking_changes( + "opentelemetry-java-contrib", current_contrib_version, new_contrib_version + ) + + # Output for GitHub Actions + breaking_info = "" + + if instrumentation_breaking: + breaking_info += "**opentelemetry-java-instrumentation:**\n" + for release in instrumentation_breaking: + breaking_info += f"- [{release['name']}]({release['url']})\n" + + if contrib_breaking: + breaking_info += "\n**opentelemetry-java-contrib:**\n" + for release in contrib_breaking: + breaking_info += f"- [{release['name']}]({release['url']})\n" + + # Set GitHub output + if os.environ.get("GITHUB_OUTPUT"): + with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output_file: + output_file.write(f"breaking_changes_info<= 2: + next_minor = f"{version_parts[0]}.{int(version_parts[1]) + 1}.0" + otel_snapshot_pattern = r'val otelSnapshotVersion = "[^"]*"' + otel_snapshot_replacement = f'val otelSnapshotVersion = "{next_minor}"' + if re.search(otel_snapshot_pattern, content): + new_content = re.sub(otel_snapshot_pattern, otel_snapshot_replacement, content) + if new_content != content: + content = new_content + updated = True + print(f"Updated otelSnapshotVersion to {next_minor}") + + # Update opentelemetry-aws-xray with -adot1 suffix + xray_pattern = r'"io\.opentelemetry\.contrib:opentelemetry-aws-xray:[^"]*"' + xray_version = f"{otel_contrib_version}-adot1" + xray_replacement = f'"io.opentelemetry.contrib:opentelemetry-aws-xray:{xray_version}"' + if re.search(xray_pattern, content): + new_content = re.sub(xray_pattern, xray_replacement, content) + if new_content != content: + content = new_content + updated = True + print(f"Updated opentelemetry-aws-xray to {xray_version}") + + # Update opentelemetry-aws-resources with -alpha suffix + resources_pattern = r'"io\.opentelemetry\.contrib:opentelemetry-aws-resources:[^"]*"' + resources_version = f"{otel_contrib_version}-alpha" + resources_replacement = f'"io.opentelemetry.contrib:opentelemetry-aws-resources:{resources_version}"' + if re.search(resources_pattern, content): + new_content = re.sub(resources_pattern, resources_replacement, content) + if new_content != content: + content = new_content + updated = True + print(f"Updated opentelemetry-aws-resources to {resources_version}") + + # Update .github/patches/versions file + if file_path == ".github/patches/versions": + # Update OTEL_JAVA_INSTRUMENTATION_VERSION + instrumentation_pattern = r'OTEL_JAVA_INSTRUMENTATION_VERSION=v[^\n]*' + instrumentation_replacement = f'OTEL_JAVA_INSTRUMENTATION_VERSION=v{otel_instrumentation_version}' + if re.search(instrumentation_pattern, content): + new_content = re.sub(instrumentation_pattern, instrumentation_replacement, content) + if new_content != content: + content = new_content + updated = True + print(f"Updated OTEL_JAVA_INSTRUMENTATION_VERSION to v{otel_instrumentation_version}") + + # Update OTEL_JAVA_CONTRIB_VERSION + contrib_pattern = r'OTEL_JAVA_CONTRIB_VERSION=v[^\n]*' + contrib_replacement = f'OTEL_JAVA_CONTRIB_VERSION=v{otel_contrib_version}' + if re.search(contrib_pattern, content): + new_content = re.sub(contrib_pattern, contrib_replacement, content) + if new_content != content: + content = new_content + updated = True + print(f"Updated OTEL_JAVA_CONTRIB_VERSION to v{otel_contrib_version}") + + if updated: + with open(file_path, "w", encoding="utf-8") as output_file: + output_file.write(content) + print(f"Updated {file_path}") + + return updated + except (OSError, IOError) as file_error: + print(f"Error updating {file_path}: {file_error}") + return False + +def main(): + otel_instrumentation_version = os.environ.get("OTEL_JAVA_INSTRUMENTATION_VERSION") + otel_contrib_version = os.environ.get("OTEL_JAVA_CONTRIB_VERSION") + + if not otel_instrumentation_version or not otel_contrib_version: + print("Error: OTEL_JAVA_INSTRUMENTATION_VERSION and OTEL_JAVA_CONTRIB_VERSION environment variables required") + sys.exit(1) + + # Files to update + files_to_update = [ + "dependencyManagement/build.gradle.kts", + ".github/patches/versions", + ] + + any_updated = False + for file_path in files_to_update: + if update_file_dependencies(file_path, otel_instrumentation_version, otel_contrib_version): + any_updated = True + + if any_updated: + print(f"Dependencies updated to Instrumentation {otel_instrumentation_version}-adot1 / Contrib {otel_contrib_version} (with appropriate suffixes)") + else: + print("No OpenTelemetry dependencies found to update") + +if __name__ == "__main__": + main()