Update Automation #8
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Update Automation | |
| on: | |
| # schedule: | |
| # - cron: '0 0 * * *' | |
| workflow_dispatch: | |
| jobs: | |
| update-automation: | |
| name: Run Automation Tasks | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| outputs: | |
| staging-branch: ${{ env.STAGING_BRANCH }} | |
| existing-code-oss-tag: ${{ env.EXISTING_CODE_OSS_TAG }} | |
| latest-code-oss-tag: ${{ env.LATEST_CODE_OSS_TAG }} | |
| steps: | |
| - name: Setup environment | |
| run: | | |
| echo "Installing required dependencies" | |
| sudo apt-get update | |
| sudo apt-get install -y quilt libxml2-utils jq | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Checkout main branch | |
| run: | | |
| git fetch origin | |
| echo "Using main branch as base for staging" | |
| git checkout main | |
| git submodule update --init --recursive | |
| - name: Check if update needed | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| run: | | |
| cd third-party-src | |
| git fetch --tags | |
| EXISTING_CODE_OSS_TAG=$(git describe --tags --exact-match HEAD 2>/dev/null | head -1) | |
| if [ -z "$EXISTING_CODE_OSS_TAG" ]; then | |
| echo "Error: Submodule is not on a tagged commit" | |
| exit 1 | |
| fi | |
| cd .. | |
| LATEST_CODE_OSS_TAG=$(gh api repos/microsoft/vscode/releases/latest --template '{{.tag_name}}') | |
| echo "EXISTING_CODE_OSS_TAG=$EXISTING_CODE_OSS_TAG" >> "$GITHUB_ENV" | |
| echo "LATEST_CODE_OSS_TAG=$LATEST_CODE_OSS_TAG" >> "$GITHUB_ENV" | |
| echo "Existing tag: $EXISTING_CODE_OSS_TAG" | |
| echo "Latest tag: $LATEST_CODE_OSS_TAG" | |
| if [ "$EXISTING_CODE_OSS_TAG" = "$LATEST_CODE_OSS_TAG" ]; then | |
| echo "Submodule is up to date with latest VS Code release" | |
| exit 0 | |
| else | |
| echo "Update needed: $EXISTING_CODE_OSS_TAG -> $LATEST_CODE_OSS_TAG" | |
| # Create or checkout staging branch | |
| STAGING_BRANCH="staging-code-editor-$LATEST_CODE_OSS_TAG" | |
| # Check if PR already exists for this staging branch | |
| EXISTING_STAGING_PR=$(gh pr list --head "$STAGING_BRANCH" --json number --jq '.[0].number' || echo "") | |
| if [ -n "$EXISTING_STAGING_PR" ]; then | |
| echo "PR already exists for staging branch $STAGING_BRANCH: #$EXISTING_STAGING_PR" | |
| echo "Skipping workflow as update is already in progress" | |
| exit 0 | |
| fi | |
| if git show-ref --verify --quiet refs/remotes/origin/"$STAGING_BRANCH"; then | |
| echo "Staging branch $STAGING_BRANCH already exists, checking it out" | |
| git checkout -b "$STAGING_BRANCH" origin/"$STAGING_BRANCH" | |
| else | |
| echo "Creating new staging branch: $STAGING_BRANCH from main" | |
| git checkout -b "$STAGING_BRANCH" main | |
| fi | |
| # Update submodule to latest VS Code release | |
| echo "Updating submodule to $LATEST_CODE_OSS_TAG" | |
| cd third-party-src | |
| git fetch --tags | |
| git checkout "$LATEST_CODE_OSS_TAG" | |
| cd .. | |
| git add third-party-src | |
| if git diff --staged --quiet; then | |
| echo "No changes to commit, submodule already up to date" | |
| else | |
| git commit -m "Update VS Code submodule to $LATEST_CODE_OSS_TAG" | |
| git push origin "$STAGING_BRANCH" | |
| fi | |
| echo "STAGING_BRANCH=$STAGING_BRANCH" >> "$GITHUB_ENV" | |
| echo "Created staging branch: $STAGING_BRANCH with VS Code $LATEST_CODE_OSS_TAG" | |
| fi | |
| - name: Rebase patches for all targets | |
| run: | | |
| echo "Rebasing patches for all build targets" | |
| # Test each target sequentially with rebasing | |
| FAILED_TARGETS=() | |
| for target in code-editor-sagemaker-server; do | |
| echo "" | |
| echo "=== REBASING TARGET: $target ===" | |
| if ./scripts/prepare-src.sh --command rebase_patches "$target"; then | |
| echo "Successfully rebased $target" | |
| else | |
| echo "Failed to rebase $target" | |
| FAILED_TARGETS+=("$target") | |
| fi | |
| # Clean up for next target | |
| rm -rf code-editor-src | |
| echo "=== END TARGET: $target ===" | |
| done | |
| # Report results | |
| if [ ${#FAILED_TARGETS[@]} -gt 0 ]; then | |
| echo "Failed targets: ${FAILED_TARGETS[*]}" | |
| exit 1 | |
| else | |
| echo "All targets rebased successfully" | |
| fi | |
| # Commit rebased patches if any changes | |
| git add patches/ | |
| if ! git diff --staged --quiet; then | |
| git commit -m "Rebase patches for all targets" | |
| git push origin "$STAGING_BRANCH" | |
| fi | |
| - name: Rebase test patches for all targets | |
| run: | | |
| echo "Rebasing test patches for all build targets" | |
| # Test each target sequentially with rebasing test patches | |
| FAILED_TARGETS=() | |
| for target in code-editor-sagemaker-server; do | |
| echo "" | |
| echo "=== REBASING TEST PATCHES FOR TARGET: $target ===" | |
| if ./scripts/prepare-src.sh --command rebase_test_patches "$target"; then | |
| echo "Successfully rebased test patches for $target" | |
| else | |
| echo "Failed to rebase test patches for $target" | |
| FAILED_TARGETS+=("$target") | |
| fi | |
| # Clean up for next target | |
| rm -rf code-editor-src | |
| echo "=== END TEST PATCHES TARGET: $target ===" | |
| done | |
| # Report results | |
| if [ ${#FAILED_TARGETS[@]} -gt 0 ]; then | |
| echo "Failed test patch targets: ${FAILED_TARGETS[*]}" | |
| exit 1 | |
| else | |
| echo "All test patch targets rebased successfully" | |
| fi | |
| # Commit rebased test patches if any changes | |
| git add patches/ | |
| if ! git diff --staged --quiet; then | |
| git commit -m "Rebase test patches for all targets" | |
| git push origin "$STAGING_BRANCH" | |
| fi | |
| build-and-update-package-locks: | |
| name: Build and Update Package Lock Overrides | |
| runs-on: ubuntu-latest | |
| needs: update-automation | |
| if: needs.update-automation.outputs.staging-branch != '' | |
| permissions: | |
| contents: write | |
| strategy: | |
| matrix: | |
| target: [code-editor-sagemaker-server] | |
| steps: | |
| - name: Setup environment | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y quilt libxml2-utils jq libx11-dev libxkbfile-dev | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.update-automation.outputs.staging-branch }} | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Build target | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| ./scripts/prepare-src.sh "$TARGET" | |
| cd code-editor-src | |
| npm install | |
| cd .. | |
| export DISABLE_MANGLE=true | |
| ./scripts/build-artifacts.sh "$TARGET" | |
| echo "Built target: $TARGET" | |
| - name: Update package-lock for target | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| OVERRIDE_PATH=$(jq -r '."package-lock-overrides".path' "configuration/$TARGET.json") | |
| rm -rf "$OVERRIDE_PATH" | |
| mkdir -p "$OVERRIDE_PATH" | |
| find code-editor-src -name "package-lock.json" -type f | while read -r file; do | |
| rel_path="${file#code-editor-src/}" | |
| third_party_file="third-party-src/$rel_path" | |
| # Skip files in node_modules | |
| if [[ "$rel_path" == node_modules/* ]]; then | |
| continue | |
| fi | |
| if [ ! -f "$third_party_file" ] || ! cmp -s "$file" "$third_party_file"; then | |
| dest_dir="$OVERRIDE_PATH/$(dirname "$rel_path")" | |
| mkdir -p "$dest_dir" | |
| cp "$file" "$dest_dir/" | |
| echo "Copied updated $rel_path to $OVERRIDE_PATH" | |
| fi | |
| done | |
| - name: Upload prepared source as artifact | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: ${{ github.run_id }}-prepared-source-${{ matrix.target }} | |
| path: code-editor-src/ | |
| retention-days: 1 | |
| - name: Commit package-lock overrides | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| STAGING_BRANCH: ${{ needs.update-automation.outputs.staging-branch }} | |
| run: | | |
| git add package-lock-overrides/ | |
| if ! git diff --staged --quiet; then | |
| git commit -m "Update package-lock.json overrides for $TARGET" | |
| # Retry push with rebase until successful | |
| for i in {1..5}; do | |
| if git pull --rebase origin "$STAGING_BRANCH" && git push origin "$STAGING_BRANCH"; then | |
| break | |
| fi | |
| sleep $((i * 2)) | |
| done | |
| fi | |
| generate-oss-attribution: | |
| name: Generate OSS Attribution | |
| runs-on: ubuntu-latest | |
| needs: [update-automation, build-and-update-package-locks] | |
| if: needs.update-automation.outputs.staging-branch != '' | |
| permissions: | |
| contents: write | |
| steps: | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| - name: Setup environment | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y jq | |
| npm i -g license-checker | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.update-automation.outputs.staging-branch }} | |
| fetch-depth: 0 | |
| submodules: true | |
| - name: Configure git | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| - name: Download prepared sources from artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| pattern: ${{ github.run_id }}-prepared-source-* | |
| merge-multiple: false | |
| - name: Organize downloaded sources | |
| run: | | |
| for target in code-editor-sagemaker-server; do | |
| if [[ -d "$GITHUB_RUN_ID-prepared-source-$target" ]]; then | |
| mv "$GITHUB_RUN_ID-prepared-source-$target" "code-editor-src-$target" | |
| echo "Organized prepared source for $target" | |
| else | |
| echo "Missing prepared source artifact for target: $target" | |
| exit 1 | |
| fi | |
| done | |
| - name: Update excluded packages | |
| env: | |
| LATEST_CODE_OSS_TAG: ${{ needs.update-automation.outputs.latest-code-oss-tag }} | |
| run: | | |
| jq --arg version "$LATEST_CODE_OSS_TAG" 'to_entries | map(if .key | startswith("code-oss-dev@") then .key = "code-oss-dev@" + $version else . end) | from_entries' build-tools/oss-attribution/excluded-packages.json > tmp.json && mv tmp.json build-tools/oss-attribution/excluded-packages.json | |
| - name: Generate unified OSS attribution | |
| run: | | |
| ./scripts/generate-oss-attribution.sh --command generate_unified_oss_attribution | |
| - name: Commit OSS attribution | |
| env: | |
| STAGING_BRANCH: ${{ needs.update-automation.outputs.staging-branch }} | |
| run: | | |
| # Copy LICENSE-THIRD-PARTY to root directory | |
| cp overrides/LICENSE-THIRD-PARTY LICENSE-THIRD-PARTY | |
| git add overrides/ LICENSE-THIRD-PARTY | |
| if ! git diff --staged --quiet; then | |
| git commit -m "Update unified OSS attribution" | |
| git push origin "$STAGING_BRANCH" | |
| fi | |
| - name: Clean up prepared source directories | |
| run: | | |
| rm -rf code-editor-src-* | |
| echo "Cleaned up local prepared source directories" | |
| create-pr: | |
| name: Create Pull Request | |
| runs-on: ubuntu-latest | |
| needs: [update-automation, build-and-update-package-locks, generate-oss-attribution] | |
| if: needs.update-automation.outputs.staging-branch != '' | |
| permissions: | |
| contents: write | |
| pull-requests: write | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| ref: ${{ needs.update-automation.outputs.staging-branch }} | |
| fetch-depth: 0 | |
| - name: Create PR to main | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| STAGING_BRANCH: ${{ needs.update-automation.outputs.staging-branch }} | |
| LATEST_CODE_OSS_TAG: ${{ needs.update-automation.outputs.latest-code-oss-tag }} | |
| run: | | |
| # Check if PR already exists for this staging branch | |
| EXISTING_PR=$(gh pr list --head "$STAGING_BRANCH" --json number --jq '.[0].number' || echo "") | |
| if [ -n "$EXISTING_PR" ]; then | |
| echo "PR already exists for branch $STAGING_BRANCH: #$EXISTING_PR" | |
| exit 0 | |
| fi | |
| # Get release body and generate links | |
| RELEASE_BODY=$(gh api "repos/microsoft/vscode/releases/tags/$LATEST_CODE_OSS_TAG" --template '{{.body}}') | |
| VERSION_NUMBER=$(echo "$LATEST_CODE_OSS_TAG" | cut -d. -f1,2 | sed 's/\./\_/g') | |
| VSCODE_RELEASE_NOTES="https://code.visualstudio.com/updates/v$VERSION_NUMBER" | |
| # Create PR | |
| PR_TITLE="Update VS Code to $LATEST_CODE_OSS_TAG" | |
| PR_BODY="## Automated update of VS Code submodule to version $LATEST_CODE_OSS_TAG | |
| Changes: | |
| - Updated third-party-src submodule to $LATEST_CODE_OSS_TAG | |
| - Rebased patches for all targets | |
| - Updated package-lock.json overrides | |
| - Updated OSS attribution | |
| ## Release Notes | |
| [VS Code Release Notes]($VSCODE_RELEASE_NOTES) | |
| ### Code-OSS Release Details | |
| $RELEASE_BODY | |
| ## Review Notes | |
| Please review the release notes above for: | |
| - Breaking changes | |
| - Changes in default behavior | |
| - Newly introduced features that may need to be disabled/modified | |
| - Remote extension host changes | |
| ## Next Steps | |
| After this PR is merged to main: | |
| - If this is a patch update (e.g., 1.101.0 -> 1.101.2), the commits need to be cherry-picked from main to the corresponding feature branch (format x.x) | |
| - A release can be created by following the guide in [RELEASE.md](RELEASE.md)" | |
| gh pr create \ | |
| --title "$PR_TITLE" \ | |
| --body "$PR_BODY" \ | |
| --base "main" \ | |
| --head "$STAGING_BRANCH" | |
| echo "Created PR from $STAGING_BRANCH to main" | |
| publish-release-lag-metric: | |
| name: Publish Release Lag Metric | |
| runs-on: ubuntu-latest | |
| needs: [update-automation] | |
| if: always() | |
| environment: update-automation-workflow-env | |
| permissions: | |
| id-token: write # Required for OIDC | |
| contents: read | |
| env: | |
| REPOSITORY: ${{ github.repository }} | |
| AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }} | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| submodules: true | |
| fetch-depth: 1 | |
| - name: Use role credentials for metrics | |
| id: aws-creds | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }} | |
| role-duration-seconds: 900 | |
| aws-region: us-east-1 | |
| - name: Calculate and publish release lag metric | |
| if: steps.aws-creds.outcome == 'success' | |
| run: | | |
| cd third-party-src | |
| SUBMODULE_COMMIT_TIMESTAMP=$(git log -1 --format=%ct) | |
| cd .. | |
| CURRENT_TIMESTAMP=$(date +%s) | |
| SECONDS_BEHIND=$((CURRENT_TIMESTAMP - SUBMODULE_COMMIT_TIMESTAMP)) | |
| NORMALIZED_VALUE=$(awk "BEGIN {printf \"%.6f\", $SECONDS_BEHIND / 86400}") | |
| aws cloudwatch put-metric-data \ | |
| --namespace "GitHub/Workflows" \ | |
| --metric-name "CodeOSSReleaseLag" \ | |
| --dimensions "Repository=$REPOSITORY,Workflow=UpdateAutomation" \ | |
| --value "$NORMALIZED_VALUE" \ | |
| --unit None | |
| echo "Published metric: CodeOSSReleaseLag = $NORMALIZED_VALUE (equivalent to $NORMALIZED_VALUE days behind upstream)" | |
| send-notification: | |
| name: Send Notification | |
| runs-on: ubuntu-latest | |
| needs: [update-automation, create-pr] | |
| environment: update-automation-workflow-env | |
| permissions: | |
| id-token: write # Required for OIDC | |
| env: | |
| REPOSITORY: ${{ github.repository }} | |
| AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }} | |
| steps: | |
| - name: Use role credentials for notification | |
| id: aws-creds | |
| continue-on-error: ${{ env.REPOSITORY != 'aws/code-editor' }} | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }} | |
| role-duration-seconds: 900 | |
| aws-region: us-east-1 | |
| - name: Send notification | |
| if: steps.aws-creds.outcome == 'success' | |
| run: | | |
| aws cloudwatch put-metric-data \ | |
| --namespace "GitHub/Workflows" \ | |
| --metric-name "PRCreated" \ | |
| --dimensions "Repository=$REPOSITORY,Workflow=UpdateAutomation" \ | |
| --value 1 | |
| publish-success-metrics: | |
| name: Publish Success Metrics | |
| runs-on: ubuntu-latest | |
| needs: [update-automation, build-and-update-package-locks, generate-oss-attribution, create-pr, send-notification, publish-release-lag-metric] | |
| environment: update-automation-workflow-env | |
| if: always() && !failure() && !cancelled() | |
| permissions: | |
| id-token: write # Required for OIDC | |
| env: | |
| REPOSITORY: ${{ github.repository }} | |
| AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }} | |
| steps: | |
| - name: Use role credentials for metrics | |
| id: aws-creds | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }} | |
| role-duration-seconds: 900 | |
| aws-region: us-east-1 | |
| - name: Publish success metrics | |
| if: steps.aws-creds.outcome == 'success' | |
| run: | | |
| aws cloudwatch put-metric-data \ | |
| --namespace "GitHub/Workflows" \ | |
| --metric-name "ExecutionsSucceeded" \ | |
| --dimensions "Repository=$REPOSITORY,Workflow=UpdateAutomation" \ | |
| --value 1 | |
| echo "Published metric: ExecutionsSucceeded" | |
| publish-failure-metrics: | |
| name: Publish Failure Metrics | |
| runs-on: ubuntu-latest | |
| needs: [update-automation, build-and-update-package-locks, generate-oss-attribution, create-pr, send-notification, publish-release-lag-metric] | |
| environment: update-automation-workflow-env | |
| if: failure() | |
| permissions: | |
| id-token: write # Required for OIDC | |
| env: | |
| REPOSITORY: ${{ github.repository }} | |
| AWS_ROLE_TO_ASSUME: ${{ secrets.AWS_ROLE_TO_ASSUME }} | |
| steps: | |
| - name: Use role credentials for metrics | |
| id: aws-creds | |
| continue-on-error: ${{ env.REPOSITORY != 'aws/code-editor' }} | |
| uses: aws-actions/configure-aws-credentials@v4 | |
| with: | |
| role-to-assume: ${{ env.AWS_ROLE_TO_ASSUME }} | |
| role-duration-seconds: 900 | |
| aws-region: us-east-1 | |
| - name: Publish failure metrics | |
| if: steps.aws-creds.outcome == 'success' | |
| run: | | |
| aws cloudwatch put-metric-data \ | |
| --namespace "GitHub/Workflows" \ | |
| --metric-name "ExecutionsFailed" \ | |
| --dimensions "Repository=$REPOSITORY,Workflow=UpdateAutomation" \ | |
| --value 1 | |
| echo "Published metric: ExecutionsFailed" |