diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 7e1df8fef..39e0caa79 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -7,6 +7,12 @@ on: - "release/v*" - ci-workflow workflow_dispatch: # be able to run the workflow on demand + workflow_call: + inputs: + ref: + description: 'Git ref to checkout' + required: false + type: string env: AWS_DEFAULT_REGION: us-east-1 STAGING_ECR_REGISTRY: 637423224110.dkr.ecr.us-east-1.amazonaws.com @@ -34,6 +40,8 @@ jobs: steps: - name: Checkout Repo @ SHA - ${{ github.sha }} uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #5.0.0 + with: + ref: ${{ inputs.ref || github.sha }} - name: Get Python Distro Output id: python_output @@ -114,7 +122,7 @@ jobs: name: "Publish Main Build Status" needs: [ build, application-signals-e2e-test ] runs-on: ubuntu-latest - if: always() + if: always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) steps: - name: Configure AWS Credentials for emitting metrics uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #5.0.0 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml new file mode 100644 index 000000000..8eec923bc --- /dev/null +++ b/.github/workflows/nightly-build.yml @@ -0,0 +1,147 @@ +name: Nightly Upstream Snapshot Build + +on: + schedule: + - cron: "21 3 * * *" + workflow_dispatch: + +env: + AWS_DEFAULT_REGION: us-east-1 + BRANCH_NAME: nightly-dependency-updates + +permissions: + id-token: write + contents: write + pull-requests: write + +jobs: + update-and-create-pr: + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check_changes.outputs.has_changes }} + otel_python_version: ${{ steps.get_versions.outputs.otel_python_version }} + otel_contrib_version: ${{ steps.get_versions.outputs.otel_contrib_version }} + + 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.11' + + - name: Install build tools + run: | + python -m pip install --upgrade pip + pip install toml 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_PYTHON_VERSION: ${{ steps.get_versions.outputs.otel_python_version }} + OTEL_CONTRIB_VERSION: ${{ steps.get_versions.outputs.otel_contrib_version }} + run: python scripts/find_breaking_changes.py + + - name: Configure git and create branch + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + - 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_PYTHON_VERSION: ${{ steps.get_versions.outputs.otel_python_version }} + OTEL_CONTRIB_VERSION: ${{ steps.get_versions.outputs.otel_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 aws-opentelemetry-distro/pyproject.toml + git commit -m "chore: update OpenTelemetry dependencies to ${{ steps.get_versions.outputs.otel_python_version }}/${{ steps.get_versions.outputs.otel_contrib_version }}" + git push origin "$BRANCH_NAME" + fi + + - name: Create or update PR + run: | + PR_BODY="Automated update of OpenTelemetry dependencies. + + **Updated versions:** + - OpenTelemetry Python: ${{ steps.get_versions.outputs.otel_python_version }} + - OpenTelemetry Contrib: ${{ steps.get_versions.outputs.otel_contrib_version }} + + **Upstream releases with breaking changes:** + ${{ steps.breaking_changes.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 ${{ steps.get_versions.outputs.otel_python_version }}/${{ steps.get_versions.outputs.otel_contrib_version }}" \ + --body "$PR_BODY" \ + --base main \ + --head "$BRANCH_NAME" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + build-and-test: + needs: update-and-create-pr + if: needs.update-and-create-pr.outputs.has_changes == 'true' + uses: ./.github/workflows/main-build.yml + secrets: inherit + permissions: + id-token: write + contents: read + with: + ref: nightly-dependency-updates + + publish-nightly-build-status: + name: "Publish Nightly Build Status" + needs: [ update-and-create-pr, build-and-test ] + runs-on: ubuntu-latest + if: always() + steps: + - name: Configure AWS Credentials for emitting metrics + uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #5.0.0 + with: + role-to-assume: ${{ secrets.MONITORING_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), not publishing metric" + else + value="${{ needs.build-and-test.result == 'success' && '0.0' || '1.0'}}" + aws cloudwatch put-metric-data --namespace 'ADOT/GitHubActions' \ + --metric-name Failure \ + --dimensions repository=${{ github.repository }},branch=${{ github.ref_name }},workflow=nightly_build \ + --value $value + fi \ No newline at end of file diff --git a/scripts/find_breaking_changes.py b/scripts/find_breaking_changes.py new file mode 100644 index 000000000..1d06707fe --- /dev/null +++ b/scripts/find_breaking_changes.py @@ -0,0 +1,120 @@ +#!/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_pyproject(): + """Extract current OpenTelemetry versions from pyproject.toml.""" + try: + with open("aws-opentelemetry-distro/pyproject.toml", "r", encoding="utf-8") as file: + content = file.read() + + # Find first opentelemetry-api version (core version) + api_match = re.search(r'"opentelemetry-api == ([^"]*)"', content) + current_core_version = api_match.group(1) if api_match else None + + # Find first opentelemetry-distro version (contrib version) + distro_match = re.search(r'"opentelemetry-distro == ([^"]*)"', content) + current_contrib_version = distro_match.group(1) if distro_match else None + + return current_core_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: + release_version = release["tag_name"].lstrip("v") + + # Check if this release is between current and new version + try: + if version.parse(release_version) > version.parse(current_version) and version.parse( + release_version + ) <= version.parse(new_version): + + # Check if release notes mention breaking changes + body = release.get("body", "").lower() + if any( + keyword in body for keyword in ["breaking change", "breaking changes", "breaking:", "breaking"] + ): + breaking_releases.append( + { + "version": release_version, + "name": release["name"], + "url": release["html_url"], + "body": release.get("body", ""), + } + ) + except (ValueError, KeyError): + # Skip releases with invalid version formats or missing data + 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_core_version = os.environ.get("OTEL_PYTHON_VERSION") + new_contrib_version = os.environ.get("OTEL_CONTRIB_VERSION") + + if not new_core_version or not new_contrib_version: + print("Error: OTEL_PYTHON_VERSION and OTEL_CONTRIB_VERSION environment variables required") + sys.exit(1) + + current_core_version, current_contrib_version = get_current_version_from_pyproject() + + if not current_core_version or not current_contrib_version: + print("Could not determine current versions") + sys.exit(1) + + print("Checking for breaking changes:") + print(f"Core: {current_core_version} → {new_core_version}") + print(f"Contrib: {current_contrib_version} → {new_contrib_version}") + + # Check both repos for breaking changes + core_breaking = get_releases_with_breaking_changes("opentelemetry-python", current_core_version, new_core_version) + contrib_breaking = get_releases_with_breaking_changes( + "opentelemetry-python-contrib", current_contrib_version, new_contrib_version + ) + + # Output for GitHub Actions + breaking_info = "" + + if core_breaking: + breaking_info += "**opentelemetry-python:**\n" + for release in core_breaking: + breaking_info += f"- [{release['name']}]({release['url']})\n" + + if contrib_breaking: + breaking_info += "\n**opentelemetry-python-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<