Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions .github/workflows/desktop-acceptance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ jobs:
signing_key_artifact: ${{ steps.channel.outputs.signing_key_artifact }}
public_key_artifact: ${{ steps.channel.outputs.public_key_artifact }}
has_previous_desktop: ${{ steps.channel.outputs.has_previous_desktop }}
release_kind: ${{ steps.channel.outputs.release_kind }}
acceptance_scenarios: ${{ steps.channel.outputs.acceptance_scenarios }}

steps:
Expand All @@ -50,6 +51,7 @@ jobs:
latest_release_json="${RUNNER_TEMP}/coder-studio-latest-release.json"
latest_release_error="${RUNNER_TEMP}/coder-studio-latest-release-error.log"
has_previous_desktop=false
release_kind=full
if gh api "repos/${GITHUB_REPOSITORY}/releases/latest" > "${latest_release_json}" 2> "${latest_release_error}"; then
has_previous_desktop=$(node -e "const release=require(process.argv[1]); process.stdout.write(Array.isArray(release.assets)&&release.assets.some((asset)=>asset?.name==='desktop-channel.json')?'true':'false')" "${latest_release_json}")
if [[ "${has_previous_desktop}" == "true" ]]; then
Expand All @@ -62,17 +64,32 @@ jobs:
current_shell=$(node -p "require('./packages/desktop/package.json').version")
current_runtime=$(node -p "require('./packages/cli/package.json').version")
has_previous_desktop=$(node -e "const channel=require(process.argv[1]); const currentShell=process.argv[2]; const currentRuntime=process.argv[3]; process.stdout.write(channel?.shell?.version===currentShell&&channel?.runtimes?.['win32-x64']?.version===currentRuntime&&channel?.runtimes?.['linux-x64']?.version===currentRuntime?'false':'true')" "${latest_channel}" "${current_shell}" "${current_runtime}")
if [[ "${has_previous_desktop}" == "true" ]]; then
previous_shell=$(node -e "const channel=require(process.argv[1]); if(typeof channel?.shell?.version!=='string'||!channel.shell.version) process.exit(1); process.stdout.write(channel.shell.version)" "${latest_channel}")
shell_change=$(node -e "const parse=(value)=>{const match=/^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(value);if(!match)throw new Error('Invalid Shell version: '+value);return {core:match.slice(1,4).map(Number),pre:match[4]??null}};const [current,previous]=process.argv.slice(1).map(parse);let order=0;for(let i=0;i<3&&!order;i++)order=Math.sign(current.core[i]-previous.core[i]);if(!order&&current.pre!==previous.pre)order=current.pre===null?1:previous.pre===null?-1:current.pre.localeCompare(previous.pre,undefined,{numeric:true});process.stdout.write(order>0?'upgrade':order<0?'downgrade':'same')" "${current_shell}" "${previous_shell}")
if [[ "${shell_change}" == "same" ]]; then
release_kind=runtime-only
elif [[ "${shell_change}" == "downgrade" ]]; then
echo "Desktop Shell ${current_shell} is older than stable ${previous_shell}" >&2
exit 1
fi
fi
fi
elif ! grep -q '(HTTP 404)' "${latest_release_error}"; then
cat "${latest_release_error}" >&2
exit 1
fi
if [[ "${has_previous_desktop}" == "true" ]]; then
acceptance_scenarios='["runtime-only","combined","wsl","wsl-combined","runtime-health-rollback","interrupted-download","restart-journal-recovery","external-sidecar-browser"]'
if [[ "${release_kind}" == "full" ]]; then
acceptance_scenarios='["combined","wsl-combined","runtime-health-rollback","interrupted-download","restart-journal-recovery","external-sidecar-browser"]'
else
acceptance_scenarios='["runtime-only","combined","wsl","wsl-combined","runtime-health-rollback","interrupted-download","restart-journal-recovery","external-sidecar-browser"]'
fi
else
acceptance_scenarios='["fresh-native","fresh-wsl"]'
fi
echo "has_previous_desktop=${has_previous_desktop}" >> "${GITHUB_OUTPUT}"
echo "release_kind=${release_kind}" >> "${GITHUB_OUTPUT}"
echo "acceptance_scenarios=${acceptance_scenarios}" >> "${GITHUB_OUTPUT}"

- name: Generate ephemeral Runtime signing key
Expand Down Expand Up @@ -297,13 +314,15 @@ jobs:
$previous = 'release/desktop-installed-previous'
$scenario = '${{ matrix.scenario }}'
$hasPreviousDesktop = '${{ needs.prepare.outputs.has_previous_desktop }}' -eq 'true'
$releaseKind = '${{ needs.prepare.outputs.release_kind }}'
$useRuntimeOnlyChannel = $hasPreviousDesktop -and $releaseKind -eq 'runtime-only' -and $scenario -notin @('combined', 'wsl-combined')
$originalChannel = Get-Content (Join-Path $candidate 'desktop-channel.json') -Raw | ConvertFrom-Json
$generatedAt = ([DateTimeOffset]$originalChannel.generatedAt).UtcDateTime.ToString(
'yyyy-MM-ddTHH:mm:ss.fffZ',
[Globalization.CultureInfo]::InvariantCulture
)
$scenarioTag = "${{ needs.prepare.outputs.release_tag }}-$scenario"
if ($hasPreviousDesktop -and $scenario -notin @('combined', 'wsl-combined')) {
if ($useRuntimeOnlyChannel) {
pnpm desktop:channel -- --directory $candidate --carry-forward-from $previous
if ($LASTEXITCODE -ne 0) { throw 'Unable to carry forward the previous Shell for the Runtime-only scenario' }
}
Expand All @@ -320,7 +339,7 @@ jobs:
'--directory', $candidate,
'--components', 'desktop,win-runtime,wsl-engine,wsl-runtime'
)
if (-not $hasPreviousDesktop -or $scenario -in @('combined', 'wsl-combined')) {
if (-not $useRuntimeOnlyChannel) {
$validation += @('--release-kind', 'full')
} else {
$validation += @(
Expand Down Expand Up @@ -348,14 +367,16 @@ jobs:
}
$components = if ($scenario -in @('fresh-native', 'fresh-wsl')) {
''
} elseif ($scenario -eq 'external-sidecar-browser') {
''
} elseif ($releaseKind -eq 'full') {
'shell,runtime:win32-x64'
} elseif ($scenario -eq 'combined') {
'shell,runtime:win32-x64'
} elseif ($scenario -eq 'wsl-combined') {
'shell,runtime:win32-x64'
} elseif ($scenario -eq 'wsl') {
'runtime:win32-x64'
} elseif ($scenario -eq 'external-sidecar-browser') {
''
} else {
'runtime:win32-x64'
}
Expand Down Expand Up @@ -427,6 +448,10 @@ jobs:
}
pnpm acceptance:desktop:installed @arguments
if ($LASTEXITCODE -ne 0) { throw 'Installed Desktop acceptance failed' }
$reportPath = 'release/desktop-installed-report/${{ matrix.scenario }}.json'
$report = Get-Content $reportPath -Raw | ConvertFrom-Json
$report | Add-Member -NotePropertyName releaseKind -NotePropertyValue '${{ needs.prepare.outputs.release_kind }}' -Force
$report | ConvertTo-Json -Depth 32 | Set-Content $reportPath -Encoding utf8

- name: Upload installed-upgrade report
if: always()
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -673,6 +673,7 @@ jobs:
return JSON.parse(text.charCodeAt(0) === 0xfeff ? text.slice(1) : text);
};
const channel = parseJson("release/promotion-reports/channel/desktop-channel.json");
const releaseKind = "${{ needs.prepare.outputs.release_kind }}";
const channelSignatureDigest = createHash("sha256")
.update(channel.signature.value, "utf8")
.digest("hex");
Expand All @@ -699,10 +700,13 @@ jobs:
if (requiredProductionScenarios.size) throw new Error(`Production reports are missing: ${[...requiredProductionScenarios].join(", ")}`);
const matrix = readReports("release/promotion-reports/desktop-matrix");
const requiredScenarios = new Set(${{ needs.prepare.outputs.has_previous_desktop == 'true' }}
? ["runtime-only", "combined", "wsl", "wsl-combined", "runtime-health-rollback", "interrupted-download", "restart-journal-recovery", "external-sidecar-browser"]
? releaseKind === "full"
? ["combined", "wsl-combined", "runtime-health-rollback", "interrupted-download", "restart-journal-recovery", "external-sidecar-browser"]
: ["runtime-only", "combined", "wsl", "wsl-combined", "runtime-health-rollback", "interrupted-download", "restart-journal-recovery", "external-sidecar-browser"]
: ["fresh-native", "fresh-wsl"]);
for (const report of matrix) {
if (report.commitSha !== commitSha) throw new Error("Desktop matrix commitSha does not match");
if (report.releaseKind !== releaseKind) throw new Error("Desktop matrix releaseKind does not match");
if ((report.scenario === "fresh-wsl" || report.scenario === "wsl" || report.scenario === "wsl-combined") && report.wslRuntimeVersion !== productVersion) {
throw new Error("Desktop matrix WSL report does not match the shared Web product version");
}
Expand Down
19 changes: 17 additions & 2 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -220,14 +220,29 @@ jobs:
.filter((name) => String(name).endsWith(".json"))
.map((name) => parseJson(join(root, String(name))));
const bootstrap = reports.some((report) => report.scenario === "fresh-native");
const releaseKinds = new Set(reports.map((report) => report.releaseKind));
if (releaseKinds.size !== 1) throw new Error("Desktop reports do not agree on releaseKind");
const releaseKind = [...releaseKinds][0];
if (releaseKind !== "full" && releaseKind !== "runtime-only") {
throw new Error(`Desktop report releaseKind is invalid: ${releaseKind}`);
}
if (bootstrap && releaseKind !== "full") {
throw new Error("Fresh Desktop acceptance must use a full release");
}
const required = new Set(bootstrap
? ["fresh-native", "fresh-wsl"]
: ["runtime-only", "combined", "wsl", "wsl-combined", "runtime-health-rollback", "interrupted-download", "restart-journal-recovery", "external-sidecar-browser"]);
: releaseKind === "full"
? ["combined", "wsl-combined", "runtime-health-rollback", "interrupted-download", "restart-journal-recovery", "external-sidecar-browser"]
: ["runtime-only", "combined", "wsl", "wsl-combined", "runtime-health-rollback", "interrupted-download", "restart-journal-recovery", "external-sidecar-browser"]);
for (const report of reports) {
if (report.commitSha !== process.env.GITHUB_SHA) throw new Error("Desktop report commitSha does not match");
if (report.scenario !== "external-sidecar-browser" && report.actualRuntimeVersion !== process.env.CANDIDATE_VERSION) {
if (report.releaseKind !== releaseKind) throw new Error("Desktop report releaseKind does not match");
if (report.scenario !== "external-sidecar-browser" && report.scenario !== "runtime-health-rollback" && report.actualRuntimeVersion !== process.env.CANDIDATE_VERSION) {
throw new Error("Desktop report product version does not match CLI candidate");
}
if (report.scenario === "runtime-health-rollback" && (!report.rollbackRuntimeVersion || report.actualRuntimeVersion !== report.rollbackRuntimeVersion || report.actualRuntimeVersion === process.env.CANDIDATE_VERSION)) {
throw new Error("Desktop Runtime rollback report does not prove fallback to the previous Runtime");
}
if ((report.scenario === "fresh-wsl" || report.scenario === "wsl" || report.scenario === "wsl-combined") && report.wslRuntimeVersion !== process.env.CANDIDATE_VERSION) {
throw new Error("Desktop WSL report does not match the shared Web product version");
}
Expand Down
14 changes: 14 additions & 0 deletions scripts/github-workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,7 @@ describe("GitHub workflow boundaries", () => {
signing_key_artifact: "${{ steps.channel.outputs.signing_key_artifact }}",
public_key_artifact: "${{ steps.channel.outputs.public_key_artifact }}",
has_previous_desktop: "${{ steps.channel.outputs.has_previous_desktop }}",
release_kind: "${{ steps.channel.outputs.release_kind }}",
acceptance_scenarios: "${{ steps.channel.outputs.acceptance_scenarios }}",
});
expect(resolveChannel?.run).toContain(
Expand All @@ -267,6 +268,10 @@ describe("GitHub workflow boundaries", () => {
expect(resolveChannel?.run).toContain("require('./packages/cli/package.json').version");
expect(resolveChannel?.run).toContain("channel?.shell?.version===currentShell");
expect(resolveChannel?.run).toContain("?'false':'true'");
expect(resolveChannel?.run).toContain("release_kind=runtime-only");
expect(resolveChannel?.run).toContain(
'acceptance_scenarios=\'["combined","wsl-combined","runtime-health-rollback","interrupted-download","restart-journal-recovery","external-sidecar-browser"]\''
);
expect(resolveChannel?.run).toContain('acceptance_scenarios=\'["fresh-native","fresh-wsl"]\'');
expect(generateKey?.run).toContain("openssl genpkey -algorithm Ed25519");
expect(signingKeyUpload?.with).toMatchObject({
Expand Down Expand Up @@ -479,6 +484,8 @@ describe("GitHub workflow boundaries", () => {
expect(runInstalled?.run).toContain("-SkipAuthenticode");
expect(prepareScenario?.run).toContain("'runtime:win32-x64'");
expect(prepareScenario?.run).toContain("'wsl-combined'");
expect(prepareScenario?.run).toContain("$useRuntimeOnlyChannel");
expect(prepareScenario?.run).toContain("$releaseKind -eq 'full'");
expect(prepareScenario?.run).toContain("yyyy-MM-ddTHH:mm:ss.fffZ");
expect(prepareScenario?.run).toContain("InvariantCulture");
expect(prepareScenario?.run).toContain("'desktop:artifacts', 'validate'");
Expand Down Expand Up @@ -524,6 +531,7 @@ describe("GitHub workflow boundaries", () => {
expect(validateReports?.run).toContain("commitSha");
expect(validateReports?.run).toContain("wslRuntimeVersion");
expect(validateReports?.run).toContain("wsl-combined");
expect(validateReports?.run).toContain("report.releaseKind !== releaseKind");
expect(promote?.run?.trim()).toBe(
'gh release edit "${{ needs.prepare.outputs.tag }}" --prerelease=false --latest'
);
Expand Down Expand Up @@ -589,6 +597,12 @@ describe("GitHub workflow boundaries", () => {
expect(steps[desktopReportIndex]?.run).toContain("wsl-combined");
expect(steps[desktopReportIndex]?.run).toContain("fresh-native");
expect(steps[desktopReportIndex]?.run).toContain("fresh-wsl");
expect(steps[desktopReportIndex]?.run).toContain("releaseKinds");
expect(steps[desktopReportIndex]?.run).toContain('releaseKind === "full"');
expect(steps[desktopReportIndex]?.run).toContain(
'report.scenario !== "runtime-health-rollback"'
);
expect(steps[desktopReportIndex]?.run).toContain("report.rollbackRuntimeVersion");
expect(steps[desktopReportIndex]?.run).toContain("text.charCodeAt(0) === 0xfeff");
expect(steps[preserveDesktopIndex]?.if).toBe("inputs.promote");
expect(steps[promoteIndex]?.run).toContain("npm dist-tag add");
Expand Down
Loading