diff --git a/.github/workflows/desktop-acceptance.yml b/.github/workflows/desktop-acceptance.yml index 9c2f523f..1c9e812e 100644 --- a/.github/workflows/desktop-acceptance.yml +++ b/.github/workflows/desktop-acceptance.yml @@ -176,6 +176,23 @@ jobs: name: ${{ needs.build-assets.outputs.complete_artifact }} path: release/desktop-acceptance + - name: Carry forward previous Shell blockmap for differential updates + if: needs.prepare.outputs.has_previous_desktop == 'true' && needs.prepare.outputs.release_kind == 'full' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + latest_tag=$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq .tag_name) + previous_metadata="${RUNNER_TEMP}/coder-studio-previous-latest.yml" + gh release download "${latest_tag}" \ + --pattern latest.yml \ + --output "${previous_metadata}" + previous_installer=$(node -e "const { readFileSync } = require('node:fs'); const { parse } = require('yaml'); const value = parse(readFileSync(process.argv[1], 'utf8')).path; if (typeof value !== 'string' || !value) process.exit(1); process.stdout.write(value)" "${previous_metadata}") + gh release download "${latest_tag}" \ + --pattern "${previous_installer}.blockmap" \ + --dir release/desktop-acceptance \ + --clobber + - name: Download acceptance public key uses: actions/download-artifact@v4 with: diff --git a/.github/workflows/desktop-release.yml b/.github/workflows/desktop-release.yml index a8a6993e..4dbb63db 100644 --- a/.github/workflows/desktop-release.yml +++ b/.github/workflows/desktop-release.yml @@ -351,6 +351,23 @@ jobs: path: release/desktop-release-final merge-multiple: true + - name: Carry forward previous Shell blockmap for differential updates + if: needs.prepare.outputs.has_previous_desktop == 'true' && needs.prepare.outputs.release_kind == 'full' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + latest_tag=$(gh api "repos/${GITHUB_REPOSITORY}/releases/latest" --jq .tag_name) + previous_metadata="${RUNNER_TEMP}/coder-studio-previous-latest.yml" + gh release download "${latest_tag}" \ + --pattern latest.yml \ + --output "${previous_metadata}" + previous_installer=$(node -e "const { readFileSync } = require('node:fs'); const { parse } = require('yaml'); const value = parse(readFileSync(process.argv[1], 'utf8')).path; if (typeof value !== 'string' || !value) process.exit(1); process.stdout.write(value)" "${previous_metadata}") + gh release download "${latest_tag}" \ + --pattern "${previous_installer}.blockmap" \ + --dir release/desktop-release-final \ + --clobber + - name: Build signed Desktop channel shell: bash run: | diff --git a/scripts/github-workflows.test.ts b/scripts/github-workflows.test.ts index dc647a24..68e54741 100644 --- a/scripts/github-workflows.test.ts +++ b/scripts/github-workflows.test.ts @@ -211,9 +211,15 @@ describe("GitHub workflow boundaries", () => { const publicKeyDownload = publishSteps.find( (step) => step.name === "Download acceptance public key" ); + const previousShellBlockmapIndex = publishSteps.findIndex( + (step) => step.name === "Carry forward previous Shell blockmap for differential updates" + ); const validation = publishSteps.find( (step) => step.name === "Validate complete signed acceptance channel" ); + const validationIndex = publishSteps.findIndex( + (step) => step.name === "Validate complete signed acceptance channel" + ); const release = publishSteps.find((step) => step.name === "Publish tag-pinned prerelease"); expect(workflow.on).toEqual({ @@ -318,6 +324,19 @@ describe("GitHub workflow boundaries", () => { name: "${{ needs.prepare.outputs.public_key_artifact }}", path: "release/desktop-ci-signing", }); + expect(previousShellBlockmapIndex).toBeGreaterThan(-1); + expect(previousShellBlockmapIndex).toBeLessThan(validationIndex); + expect(publishSteps[previousShellBlockmapIndex]?.if).toBe( + "needs.prepare.outputs.has_previous_desktop == 'true' && needs.prepare.outputs.release_kind == 'full'" + ); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain( + '--pattern "${previous_installer}.blockmap"' + ); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain("--pattern latest.yml"); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain("require('yaml')"); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain( + "--dir release/desktop-acceptance" + ); expect(validation?.run).toContain( "validate --directory release/desktop-acceptance --components 'desktop,win-runtime,wsl-engine,wsl-runtime'" ); @@ -441,6 +460,9 @@ describe("GitHub workflow boundaries", () => { const channelIndex = publishSteps.findIndex( (step) => step.name === "Build signed Desktop channel" ); + const previousShellBlockmapIndex = publishSteps.findIndex( + (step) => step.name === "Carry forward previous Shell blockmap for differential updates" + ); const productionValidateIndex = publishSteps.findIndex( (step) => step.name === "Validate complete production release" ); @@ -450,10 +472,22 @@ describe("GitHub workflow boundaries", () => { ); expect(previousIndex).toBeGreaterThan(-1); expect(carryIndex).toBeGreaterThan(previousIndex); - expect(channelIndex).toBeGreaterThan(carryIndex); + expect(previousShellBlockmapIndex).toBeGreaterThan(carryIndex); + expect(channelIndex).toBeGreaterThan(previousShellBlockmapIndex); expect(productionValidateIndex).toBeGreaterThan(channelIndex); expect(attestIndex).toBeGreaterThan(productionValidateIndex); expect(releaseIndex).toBeGreaterThan(attestIndex); + expect(publishSteps[previousShellBlockmapIndex]?.if).toBe( + "needs.prepare.outputs.has_previous_desktop == 'true' && needs.prepare.outputs.release_kind == 'full'" + ); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain( + '--pattern "${previous_installer}.blockmap"' + ); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain("--pattern latest.yml"); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain("require('yaml')"); + expect(publishSteps[previousShellBlockmapIndex]?.run).toContain( + "--dir release/desktop-release-final" + ); expect(publishSteps[productionValidateIndex]?.run).toContain("--release-kind"); expect(publishSteps[releaseIndex]?.run).toContain("--prerelease --latest=false"); expect(publishSteps[releaseIndex]?.run).toContain("not Authenticode-signed");