Skip to content

Commit 94002f2

Browse files
authored
Add nightly dependency bump workflow (#1219)
*Issue #, if available:* *Description of changes:* Add a workflow `nightly-build.yml` with following actions: - creates or checks out existing `nightly-dependency-updates` branch - checks for newest versions of dependencies in [opentelemetry-java-instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases) and [opentelemetry-java-contrib](https://github.com/open-telemetry/opentelemetry-java-contrib/releases) and bumps versions if applicable - search for releases with breaking changes in release notes - runs main build checks on the branch, publishes a failure metric based on the outcome. - creates a PR with the build result and breaking changes. Example nightly build run (failure expected due to breaking changes): https://github.com/aws-observability/aws-otel-java-instrumentation/actions/runs/20176308379 PR: #1266 By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
1 parent c4bc215 commit 94002f2

File tree

5 files changed

+495
-1
lines changed

5 files changed

+495
-1
lines changed

.github/workflows/main-build.yml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ on:
55
- main
66
- "release/v*"
77
workflow_dispatch: # be able to run the workflow on demand
8+
workflow_call:
9+
inputs:
10+
ref:
11+
description: 'The branch, tag or SHA to checkout'
12+
required: false
13+
type: string
814
env:
915
AWS_DEFAULT_REGION: us-east-1
1016
STAGING_ECR_REGISTRY: 611364707713.dkr.ecr.us-west-2.amazonaws.com
@@ -24,6 +30,8 @@ jobs:
2430
runs-on: aws-otel-java-instrumentation_ubuntu-latest_32-core
2531
steps:
2632
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
33+
with:
34+
ref: ${{ inputs.ref || github.sha }}
2735
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0
2836
with:
2937
java-version-file: .java-version
@@ -58,6 +66,7 @@ jobs:
5866
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
5967
with:
6068
fetch-depth: 0
69+
ref: ${{ inputs.ref || github.sha }}
6170
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0
6271
with:
6372
java-version-file: .java-version
@@ -193,6 +202,7 @@ jobs:
193202
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
194203
with:
195204
fetch-depth: 0
205+
ref: ${{ inputs.ref || github.sha }}
196206
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0
197207
with:
198208
java-version: 23
@@ -233,6 +243,7 @@ jobs:
233243
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0
234244
with:
235245
fetch-depth: 0
246+
ref: ${{ inputs.ref || github.sha }}
236247
- uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 #v5.0.0
237248
with:
238249
java-version-file: .java-version
@@ -268,7 +279,7 @@ jobs:
268279
name: "Publish Main Build Status"
269280
needs: [ build, e2e-test, contract-tests, application-signals-lambda-layer-build, application-signals-e2e-test ]
270281
runs-on: ubuntu-latest
271-
if: always()
282+
if: always() && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/v'))
272283
steps:
273284
- name: Configure AWS Credentials for emitting metrics
274285
uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
name: Nightly Upstream Snapshot Build
2+
description: This workflow checks for new upstream versions of OpenTelemetry dependencies, creates a PR to update them, and builds and tests the new changes.
3+
4+
on:
5+
schedule:
6+
- cron: "21 3 * * *"
7+
workflow_dispatch:
8+
9+
env:
10+
AWS_DEFAULT_REGION: us-east-1
11+
BRANCH_NAME: nightly-dependency-updates
12+
13+
permissions:
14+
id-token: write
15+
contents: write
16+
pull-requests: write
17+
18+
jobs:
19+
update-dependencies:
20+
runs-on: ubuntu-latest
21+
outputs:
22+
has_changes: ${{ steps.check_changes.outputs.has_changes }}
23+
otel_java_instrumentation_version: ${{ steps.get_versions.outputs.otel_java_instrumentation_version }}
24+
otel_java_contrib_version: ${{ steps.get_versions.outputs.otel_java_contrib_version }}
25+
breaking_changes_info: ${{ steps.breaking_changes.outputs.breaking_changes_info }}
26+
27+
steps:
28+
- name: Checkout repository
29+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #5.0.0
30+
with:
31+
fetch-depth: 0
32+
token: ${{ secrets.GITHUB_TOKEN }}
33+
34+
- name: Set up Python
35+
uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c #v6.0.0
36+
with:
37+
python-version: '3.x'
38+
39+
- name: Install build tools
40+
run: |
41+
python -m pip install --upgrade pip
42+
pip install requests packaging
43+
44+
- name: Get latest upstream versions
45+
id: get_versions
46+
run: python scripts/get_upstream_versions.py
47+
48+
- name: Check for breaking changes
49+
id: breaking_changes
50+
env:
51+
OTEL_JAVA_INSTRUMENTATION_VERSION: ${{ steps.get_versions.outputs.otel_java_instrumentation_version }}
52+
OTEL_JAVA_CONTRIB_VERSION: ${{ steps.get_versions.outputs.otel_java_contrib_version }}
53+
run: python scripts/find_breaking_changes.py
54+
55+
- name: Setup Git
56+
run: |
57+
git config user.name "github-actions"
58+
git config user.email "github-actions@github.com"
59+
60+
- name: Check out dependency update branch
61+
run: |
62+
if git ls-remote --exit-code --heads origin "$BRANCH_NAME"; then
63+
echo "Branch $BRANCH_NAME already exists, checking out..."
64+
git checkout "$BRANCH_NAME"
65+
else
66+
echo "Branch $BRANCH_NAME does not exist, creating new branch..."
67+
git checkout -b "$BRANCH_NAME"
68+
fi
69+
70+
- name: Update dependencies
71+
env:
72+
OTEL_JAVA_INSTRUMENTATION_VERSION: ${{ steps.get_versions.outputs.otel_java_instrumentation_version }}
73+
OTEL_JAVA_CONTRIB_VERSION: ${{ steps.get_versions.outputs.otel_java_contrib_version }}
74+
run: python scripts/update_dependencies.py
75+
76+
- name: Check for changes and commit
77+
id: check_changes
78+
run: |
79+
if git diff --quiet; then
80+
echo "No dependency updates needed"
81+
echo "has_changes=false" >> $GITHUB_OUTPUT
82+
else
83+
echo "Dependencies were updated"
84+
echo "has_changes=true" >> $GITHUB_OUTPUT
85+
86+
git add .
87+
git commit -m "chore: update OpenTelemetry dependencies to ${{ steps.get_versions.outputs.otel_java_instrumentation_version }}/${{ steps.get_versions.outputs.otel_java_contrib_version }}"
88+
git push origin "$BRANCH_NAME"
89+
fi
90+
91+
build-and-test:
92+
needs: update-dependencies
93+
if: needs.update-dependencies.outputs.has_changes == 'true'
94+
uses: ./.github/workflows/main-build.yml
95+
secrets: inherit
96+
permissions:
97+
id-token: write
98+
contents: read
99+
with:
100+
ref: nightly-dependency-updates
101+
102+
create-pr:
103+
needs: [update-dependencies, build-and-test]
104+
if: always() && needs.update-dependencies.outputs.has_changes == 'true'
105+
runs-on: ubuntu-latest
106+
steps:
107+
- name: Checkout repository
108+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #5.0.0
109+
with:
110+
token: ${{ secrets.GITHUB_TOKEN }}
111+
112+
- name: Create or update PR
113+
run: |
114+
BUILD_STATUS="${{ needs.build-and-test.result }}"
115+
BUILD_EMOJI="${{ needs.build-and-test.result == 'success' && '✅' || '❌' }}"
116+
BUILD_LINK="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
117+
118+
PR_BODY="Automated update of OpenTelemetry dependencies.
119+
120+
**Build Status:** ${BUILD_EMOJI} [${BUILD_STATUS}](${BUILD_LINK})
121+
122+
**Updated versions:**
123+
- [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 }}
124+
- [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 }}
125+
126+
**Upstream releases with breaking changes:**
127+
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.
128+
129+
${{ needs.update-dependencies.outputs.breaking_changes_info }}"
130+
131+
if gh pr view "$BRANCH_NAME" --json state --jq '.state' 2>/dev/null | grep -q "OPEN"; then
132+
echo "Open PR already exists, updating description..."
133+
gh pr edit "$BRANCH_NAME" --body "$PR_BODY"
134+
else
135+
echo "Creating new PR..."
136+
gh pr create \
137+
--title "Nightly dependency update: OpenTelemetry ${{ needs.update-dependencies.outputs.otel_java_instrumentation_version }}/${{ needs.update-dependencies.outputs.otel_java_contrib_version }}" \
138+
--body "$PR_BODY" \
139+
--base main \
140+
--head "$BRANCH_NAME"
141+
fi
142+
env:
143+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
144+
145+
publish-nightly-build-status:
146+
name: "Publish Nightly Build Status"
147+
needs: [update-dependencies, build-and-test, create-pr]
148+
runs-on: ubuntu-latest
149+
if: always()
150+
steps:
151+
- name: Configure AWS Credentials for emitting metrics
152+
uses: aws-actions/configure-aws-credentials@a03048d87541d1d9fcf2ecf528a4a65ba9bd7838 #v5.0.0
153+
with:
154+
role-to-assume: ${{ secrets.METRICS_ROLE_ARN }}
155+
aws-region: ${{ env.AWS_DEFAULT_REGION }}
156+
157+
- name: Publish nightly build status
158+
run: |
159+
if [[ "${{ needs.build-and-test.result }}" == "skipped" ]]; then
160+
echo "Build was skipped (no changes)"
161+
value="0.0"
162+
else
163+
value="${{ (needs.build-and-test.result == 'success' && needs.create-pr.result == 'success') && '0.0' || '1.0'}}"
164+
fi
165+
166+
aws cloudwatch put-metric-data --namespace 'ADOT/GitHubActions' \
167+
--metric-name Failure \
168+
--dimensions repository=${{ github.repository }},branch=${{ github.ref_name }},workflow=nightly_build \
169+
--value $value

scripts/find_breaking_changes.py

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
#!/usr/bin/env python3
2+
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
import os
6+
import re
7+
import sys
8+
9+
import requests
10+
from packaging import version
11+
12+
13+
def get_current_version_from_gradle():
14+
"""Extract current OpenTelemetry versions from build.gradle.kts."""
15+
try:
16+
with open("dependencyManagement/build.gradle.kts", "r", encoding="utf-8") as file:
17+
content = file.read()
18+
19+
# Extract otelVersion (instrumentation version) and strip -adot1 suffix
20+
otel_version_match = re.search(r'val otelVersion = "([^"]*)"', content)
21+
current_instrumentation_version = otel_version_match.group(1) if otel_version_match else None
22+
if current_instrumentation_version and current_instrumentation_version.endswith("-adot1"):
23+
current_instrumentation_version = current_instrumentation_version[:-6] # Remove -adot1
24+
25+
# Extract contrib version from dependency line and strip -adot1 suffix
26+
contrib_match = re.search(r'"io\.opentelemetry\.contrib:opentelemetry-aws-xray:([^"]*)",', content)
27+
current_contrib_version = contrib_match.group(1) if contrib_match else None
28+
if current_contrib_version and current_contrib_version.endswith("-adot1"):
29+
current_contrib_version = current_contrib_version[:-6] # Remove -adot1
30+
31+
return current_instrumentation_version, current_contrib_version
32+
33+
except (OSError, IOError) as error:
34+
print(f"Error reading current versions: {error}")
35+
return None, None
36+
37+
38+
def get_releases_with_breaking_changes(repo, current_version, new_version):
39+
"""Get releases between current and new version that mention breaking changes."""
40+
try:
41+
response = requests.get(f"https://api.github.com/repos/open-telemetry/{repo}/releases", timeout=30)
42+
response.raise_for_status()
43+
releases = response.json()
44+
45+
breaking_releases = []
46+
47+
for release in releases:
48+
try:
49+
tag_name = release["tag_name"]
50+
release_version = tag_name.lstrip("v")
51+
52+
# Check if this release is between current and new version
53+
if (
54+
version.parse(current_version)
55+
< version.parse(release_version)
56+
<= version.parse(new_version)
57+
):
58+
59+
# Check if release notes have breaking changes header or bold text
60+
body = release.get("body", "")
61+
if re.search(r"^\s*(#+|\*\*).*breaking changes", body, re.MULTILINE | re.IGNORECASE):
62+
breaking_releases.append(
63+
{
64+
"version": release_version,
65+
"name": release["name"],
66+
"url": release["html_url"],
67+
"body": release.get("body", ""),
68+
}
69+
)
70+
except (ValueError, KeyError) as parse_error:
71+
print(f"Warning: Skipping release {release.get('name', 'unknown')} due to error: {parse_error}")
72+
continue
73+
74+
return breaking_releases
75+
76+
except requests.RequestException as request_error:
77+
print(f"Warning: Could not get releases for {repo}: {request_error}")
78+
return []
79+
80+
81+
def main():
82+
new_instrumentation_version = os.environ.get("OTEL_JAVA_INSTRUMENTATION_VERSION")
83+
new_contrib_version = os.environ.get("OTEL_JAVA_CONTRIB_VERSION")
84+
85+
if not new_instrumentation_version or not new_contrib_version:
86+
print("Error: OTEL_JAVA_INSTRUMENTATION_VERSION and OTEL_JAVA_CONTRIB_VERSION environment variables required")
87+
sys.exit(1)
88+
89+
current_instrumentation_version, current_contrib_version = get_current_version_from_gradle()
90+
91+
if not current_instrumentation_version:
92+
print("Could not determine current versions")
93+
sys.exit(1)
94+
95+
print("Checking for breaking changes:")
96+
print(f"Instrumentation: {current_instrumentation_version}{new_instrumentation_version}")
97+
print(f"Contrib: {current_contrib_version or 'unknown'}{new_contrib_version}")
98+
99+
# Check both repos for breaking changes
100+
instrumentation_breaking = get_releases_with_breaking_changes(
101+
"opentelemetry-java-instrumentation", current_instrumentation_version, new_instrumentation_version
102+
)
103+
contrib_breaking = []
104+
if current_contrib_version:
105+
contrib_breaking = get_releases_with_breaking_changes(
106+
"opentelemetry-java-contrib", current_contrib_version, new_contrib_version
107+
)
108+
109+
# Output for GitHub Actions
110+
breaking_info = ""
111+
112+
if instrumentation_breaking:
113+
breaking_info += "**opentelemetry-java-instrumentation:**\n"
114+
for release in instrumentation_breaking:
115+
breaking_info += f"- [{release['name']}]({release['url']})\n"
116+
117+
if contrib_breaking:
118+
breaking_info += "\n**opentelemetry-java-contrib:**\n"
119+
for release in contrib_breaking:
120+
breaking_info += f"- [{release['name']}]({release['url']})\n"
121+
122+
# Set GitHub output
123+
if os.environ.get("GITHUB_OUTPUT"):
124+
with open(os.environ["GITHUB_OUTPUT"], "a", encoding="utf-8") as output_file:
125+
output_file.write(f"breaking_changes_info<<EOF\n{breaking_info}EOF\n")
126+
127+
128+
if __name__ == "__main__":
129+
main()

0 commit comments

Comments
 (0)