From 1c0a788663a0650c0556d10697203ce32a0e6d69 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Fri, 31 Mar 2023 18:04:32 +0100 Subject: [PATCH] Add workflow to automatically update the bundle --- .github/actions/update-bundle/action.yml | 14 ++++ .github/actions/update-bundle/index.ts | 64 ++++++++++++++++++ .github/workflows/update-bundle.yml | 82 ++++++++++++++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 .github/actions/update-bundle/index.ts create mode 100644 .github/workflows/update-bundle.yml diff --git a/.github/actions/update-bundle/action.yml b/.github/actions/update-bundle/action.yml index e69de29bb..0216d2465 100644 --- a/.github/actions/update-bundle/action.yml +++ b/.github/actions/update-bundle/action.yml @@ -0,0 +1,14 @@ +name: Update default CodeQL bundle +description: Updates 'src/defaults.json' to point to a new CodeQL bundle release. + +runs: + using: composite + steps: + - name: Install ts-node + shell: bash + run: npm install -g ts-node + + - name: Run update script + working-directory: ${{ github.action_path }} + shell: bash + run: ts-node ./index.ts diff --git a/.github/actions/update-bundle/index.ts b/.github/actions/update-bundle/index.ts new file mode 100644 index 000000000..92784c400 --- /dev/null +++ b/.github/actions/update-bundle/index.ts @@ -0,0 +1,64 @@ +import * as fs from 'fs'; +import * as github from '@actions/github'; + +interface BundleInfo { + bundleVersion: string; + cliVersion: string; +} + +interface Defaults { + bundleVersion: string; + cliVersion: string; + priorBundleVersion: string; + priorCliVersion: string; +} + +const CODEQL_BUNDLE_PREFIX = 'codeql-bundle-'; + +function getCodeQLCliVersionForRelease(release): string { + const cliVersionsFromMarkerFiles = release.assets + .map((asset) => asset.name.match(/cli-version-(.*)\.txt/)?.[1]) + .filter((v) => v) + .map((v) => v as string); + if (cliVersionsFromMarkerFiles.length > 1) { + throw new Error( + `Release ${release.tag_name} has multiple CLI version marker files.` + ); + } else if (cliVersionsFromMarkerFiles.length === 0) { + throw new Error( + `Failed to find the CodeQL CLI version for release ${release.tag_name}.` + ); + } + return cliVersionsFromMarkerFiles[0]; + } + +async function getBundleInfoFromRelease(release): Promise { + return { + bundleVersion: release.tag_name.substring(CODEQL_BUNDLE_PREFIX.length), + cliVersion: getCodeQLCliVersionForRelease(release) + }; +} + +async function getNewDefaults(currentDefaults: Defaults): Promise { + const release = github.context.payload.release; + console.log('Updating default bundle as a result of the following release: ' + + `${JSON.stringify(release)}.`) + + const bundleInfo = await getBundleInfoFromRelease(release); + return { + bundleVersion: bundleInfo.bundleVersion, + cliVersion: bundleInfo.cliVersion, + priorBundleVersion: currentDefaults.bundleVersion, + priorCliVersion: currentDefaults.cliVersion + }; +} + +async function main() { + const previousDefaults: Defaults = JSON.parse(fs.readFileSync('../../../src/defaults.json', 'utf8')); + const newDefaults = await getNewDefaults(previousDefaults); + fs.writeFileSync('../../../src/defaults.json', JSON.stringify(newDefaults, null, 2) + "\n"); +} + +// Ideally, we'd await main() here, but that doesn't work well with `ts-node`. +// So instead we rely on the fact that Node won't exit until the event loop is empty. +main(); diff --git a/.github/workflows/update-bundle.yml b/.github/workflows/update-bundle.yml new file mode 100644 index 000000000..e30af805c --- /dev/null +++ b/.github/workflows/update-bundle.yml @@ -0,0 +1,82 @@ +name: Update default CodeQL bundle + +on: + release: + types: [prereleased] + +jobs: + update-bundle: + if: startsWith(github.event.release.tag_name, 'codeql-bundle-') + runs-on: ubuntu-latest + steps: + - name: Dump environment + run: env + + - name: Dump GitHub context + env: + GITHUB_CONTEXT: '${{ toJson(github) }}' + run: echo "${GITHUB_CONTEXT}" + + - uses: actions/checkout@v3 + + - name: Update git config + run: | + git config --global user.email "github-actions@github.com" + git config --global user.name "github-actions[bot]" + + - name: Update bundle + uses: ./.github/actions/update-bundle + + - name: Rebuild Action + run: npm run build + + - name: Commit and push changes + env: + RELEASE_TAG: "${{ github.event.release.tag_name }}" + run: | + git checkout -b "update-bundle/${RELEASE_TAG}" + git commit -am "Update default bundle to ${RELEASE_TAG}" + git push --set-upstream origin "update-bundle/${RELEASE_TAG}" + + - name: Open pull request + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + cli_version=$(jq -r '.cliVersion' src/defaults.json) + pr_url=$(gh pr create \ + --title "Update default bundle to $cli_version" \ + --body "This pull request updates the default CodeQL bundle, as used with \`tools: latest\` and on GHES, to $cli_version." \ + --assignee "${GITHUB_ACTOR}" \ + --draft \ + ) + echo "CLI_VERSION=$cli_version" >> $GITHUB_ENV + echo "PR_URL=$pr_url" >> $GITHUB_ENV + + - name: Create changelog note + shell: python + run: | + import os + import re + + # Get the PR number from the PR URL. + pr_number = os.environ['PR_URL'].split('/')[-1] + changelog_note = f"- Update default CodeQL bundle version to {os.environ['CLI_VERSION']}. [#{pr_number}]({os.environ['PR_URL']})" + + # If the "[UNRELEASED]" section starts with "no user facing changes", remove that line. + # Use perl to avoid having to escape the newline character. + + with open('CHANGELOG.md', 'r') as f: + changelog = f.read() + + changelog = changelog.replace('## [UNRELEASED]\n\nNo user facing changes.', '## [UNRELEASED]\n') + + # Add the changelog note to the bottom of the "[UNRELEASED]" section. + changelog = re.sub(r'\n## (\d+\.\d+\.\d+)', f'{changelog_note}\n\n## \\1', changelog, count=1) + + with open('CHANGELOG.md', 'w') as f: + f.write(changelog) + + - name: Push changelog note + run: | + git commit -am "Add changelog note" + git push