diff --git a/.github/workflows/main-build.yml b/.github/workflows/main-build.yml index 1b8715f4..66cc33fb 100644 --- a/.github/workflows/main-build.yml +++ b/.github/workflows/main-build.yml @@ -14,6 +14,7 @@ on: branches: - main - "release/v*" + - nightly-dependency-updates permissions: id-token: write @@ -184,7 +185,7 @@ jobs: name: "Publish Main Build Status" needs: [ build, application-signals-e2e-test ] runs-on: ubuntu-latest - if: always() + if: 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 #v5.0.0 diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml new file mode 100644 index 00000000..bc838e51 --- /dev/null +++ b/.github/workflows/nightly-build.yml @@ -0,0 +1,70 @@ +name: Nightly Upstream Snapshot Build + +on: + schedule: + - cron: "21 3 * * *" + workflow_dispatch: + +env: + BRANCH_NAME: nightly-dependency-updates + +jobs: + update-and-create-pr: + runs-on: ubuntu-latest + outputs: + has_changes: ${{ steps.check_changes.outputs.has_changes }} + + steps: + - name: Checkout repository + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 #v5.0.0 + with: + fetch-depth: 0 + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Check if nightly branch already exists + run: | + if git ls-remote --exit-code --heads origin "$BRANCH_NAME"; then + echo "Branch $BRANCH_NAME already exists. Skipping run to avoid conflicts." + echo "Please merge or close the existing PR before the next nightly run." + exit 1 + fi + + - name: Configure git and create branch + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git checkout -b "$BRANCH_NAME" + + - name: Set up Python + uses: actions/setup-python@e797f83bcb11b83ae66e0230d6156d7c80228e7c #v6.0.0 + with: + python-version: '3.11' + + - name: Install Python dependencies + run: pip install requests + + - name: Update dependencies + run: python3 scripts/update_dependencies.py + + - name: Check for changes and create PR + 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 src/AWS.Distro.OpenTelemetry.AutoInstrumentation/AWS.Distro.OpenTelemetry.AutoInstrumentation.csproj build/Build.cs + git commit -m "chore: update OpenTelemetry dependencies to latest versions" + git push origin "$BRANCH_NAME" + + gh pr create \ + --title "Nightly dependency update: OpenTelemetry packages to latest versions" \ + --body "Automated update of OpenTelemetry dependencies to their latest available versions." \ + --base main \ + --head "$BRANCH_NAME" + fi + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/scripts/update_dependencies.py b/scripts/update_dependencies.py new file mode 100644 index 00000000..00a8e78b --- /dev/null +++ b/scripts/update_dependencies.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 + +import requests +import re +import sys +import xml.etree.ElementTree as ET + +def get_latest_version(package_name): + """Get the latest version of a NuGet package.""" + try: + response = requests.get( + f'https://api.nuget.org/v3-flatcontainer/{package_name.lower()}/index.json', + timeout=30 + ) + response.raise_for_status() + + data = response.json() + versions = data.get('versions', []) + + if not versions: + print(f"Warning: No versions found for {package_name}") + return None + + # Get the latest stable version (avoid pre-release versions for now) + stable_versions = [v for v in versions if not any(pre in v.lower() for pre in ['alpha', 'beta', 'rc', 'preview'])] + + if stable_versions: + return stable_versions[-1] # Last version is typically the latest + else: + # If no stable versions, use the latest version + return versions[-1] + + except requests.RequestException as request_error: + print(f"Warning: Could not get latest version for {package_name}: {request_error}") + return None + +def get_latest_dotnet_instrumentation_version(): + """Get the latest version of opentelemetry-dotnet-instrumentation from GitHub releases.""" + try: + response = requests.get( + 'https://api.github.com/repos/open-telemetry/opentelemetry-dotnet-instrumentation/releases/latest', + timeout=30 + ) + response.raise_for_status() + + release_data = response.json() + tag_name = release_data['tag_name'] + + return tag_name + + except requests.RequestException as request_error: + print(f"Warning: Could not get latest dotnet-instrumentation version: {request_error}") + return None + +def update_csproj_file(file_path): + """Update OpenTelemetry package versions in a .csproj file.""" + try: + # Parse the XML file + tree = ET.parse(file_path) + root = tree.getroot() + + updated = False + + # Find all PackageReference elements + for package_ref in root.findall('.//PackageReference'): + include = package_ref.get('Include', '') + + # Only update OpenTelemetry packages + if include.startswith('OpenTelemetry'): + current_version = package_ref.get('Version', '') + latest_version = get_latest_version(include) + + if latest_version and current_version != latest_version: + package_ref.set('Version', latest_version) + updated = True + print(f"Updated {include}: {current_version} → {latest_version}") + elif latest_version: + print(f"{include} already at latest version: {latest_version}") + + if updated: + tree.write(file_path, encoding='utf-8', xml_declaration=True) + print("Dependencies updated successfully") + return True + else: + print("No OpenTelemetry dependencies needed updating") + return False + + except (ET.ParseError, OSError, IOError) as file_error: + print(f"Error updating dependencies: {file_error}") + sys.exit(1) + +def update_build_cs_file(file_path): + """Update the openTelemetryAutoInstrumentationDefaultVersion in Build.cs.""" + try: + latest_version = get_latest_dotnet_instrumentation_version() + if not latest_version: + print("Could not get latest dotnet-instrumentation version") + return False + + with open(file_path, 'r', encoding='utf-8') as input_file: + content = input_file.read() + + pattern = r'private const string OpenTelemetryAutoInstrumentationDefaultVersion = "v[^"]*";' + replacement = f'private const string OpenTelemetryAutoInstrumentationDefaultVersion = "{latest_version}";' + + if re.search(pattern, content): + new_content = re.sub(pattern, replacement, content) + + if new_content != content: + with open(file_path, 'w', encoding='utf-8') as output_file: + output_file.write(new_content) + print(f"Updated OpenTelemetryAutoInstrumentationDefaultVersion to {latest_version}") + return True + else: + print(f"OpenTelemetryAutoInstrumentationDefaultVersion already at latest version: {latest_version}") + return False + else: + print("Could not find OpenTelemetryAutoInstrumentationDefaultVersion in Build.cs") + return False + + except (OSError, IOError) as file_error: + print(f"Error updating Build.cs: {file_error}") + sys.exit(1) + +def main(): + csproj_path = 'src/AWS.Distro.OpenTelemetry.AutoInstrumentation/AWS.Distro.OpenTelemetry.AutoInstrumentation.csproj' + build_cs_path = 'build/Build.cs' + + csproj_updated = update_csproj_file(csproj_path) + build_cs_updated = update_build_cs_file(build_cs_path) + + if not csproj_updated and not build_cs_updated: + print("No updates were made") + +if __name__ == '__main__': + main()