Skip to content

Update Automation

Update Automation #11

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"