# GitHub release workflow. name: Release on: workflow_dispatch: inputs: release_type: type: choice description: "Type of release (use 'Use workflow from' to pick the branch)" required: true options: - rc - release - create-release-branch commit_sha: description: "Optional: commit SHA to tag (defaults to HEAD of selected branch)" type: string default: "" permissions: contents: read concurrency: ${{ github.workflow }}-${{ github.ref }} jobs: # Only allow maintainers/admins to release. check-perms: runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }} steps: - name: Allow only maintainers/admins uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | const {data} = await github.rest.repos.getCollaboratorPermissionLevel({ owner: context.repo.owner, repo: context.repo.repo, username: context.actor }); const role = data.role_name || data.user?.role_name || data.permission; const perms = data.user?.permissions || {}; core.info(`Actor ${context.actor} permission=${data.permission}, role_name=${role}`); const allowed = role === 'admin' || role === 'maintain' || perms.admin === true || perms.maintain === true; if (!allowed) core.setFailed('Denied: requires maintain or admin'); prepare-release: name: Prepare release needs: [check-perms] runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }} permissions: contents: write outputs: version: ${{ steps.prepare.outputs.version }} previous_version: ${{ steps.prepare.outputs.previous_version }} stable: ${{ steps.prepare.outputs.stable }} target_ref: ${{ steps.prepare.outputs.target_ref }} create_branch: ${{ steps.prepare.outputs.create_branch }} steps: - name: Harden Runner uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 with: egress-policy: audit - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: true - name: Fetch git tags run: git fetch --tags --force - name: Setup Go uses: ./.github/actions/setup-go with: use-cache: false - name: Calculate version and create tag id: prepare env: RELEASE_TYPE: ${{ inputs.release_type }} REF_NAME: ${{ github.ref_name }} COMMIT_SHA: ${{ inputs.commit_sha }} run: | set -euo pipefail args=(--type "$RELEASE_TYPE" --ref "$REF_NAME") if [[ -n "$COMMIT_SHA" ]]; then args+=(--commit "$COMMIT_SHA") fi output=$(go run ./scripts/release-action calculate-version "${args[@]}") echo "Raw output: $output" version=$(echo "$output" | jq -r '.version') previous_version=$(echo "$output" | jq -r '.previous_version') stable=$(echo "$output" | jq -r '.stable') target_ref=$(echo "$output" | jq -r '.target_ref') create_branch=$(echo "$output" | jq -r '.create_branch // empty') # Validate required outputs are non-empty. for var in version previous_version target_ref; do eval "val=\$$var" if [[ -z "$val" || "$val" == "null" ]]; then echo "::error::calculate-version returned empty or null '$var'" exit 1 fi done { echo "version=$version" echo "previous_version=$previous_version" echo "stable=$stable" echo "target_ref=$target_ref" echo "create_branch=$create_branch" } >> "$GITHUB_OUTPUT" { echo "### Release preparation" echo "| Field | Value |" echo "|-------|-------|" echo "| Version | \`$version\` |" echo "| Previous | \`$previous_version\` |" echo "| Stable | \`$stable\` |" echo "| Target ref | \`$target_ref\` |" if [[ -n "$create_branch" ]]; then echo "| Create branch | \`$create_branch\` |" fi } >> "$GITHUB_STEP_SUMMARY" - name: Create and push tag env: VERSION: ${{ steps.prepare.outputs.version }} TARGET_REF: ${{ steps.prepare.outputs.target_ref }} run: | set -euo pipefail # Skip if tag already exists (idempotent) if git rev-parse "$VERSION" >/dev/null 2>&1; then echo "Tag $VERSION already exists, skipping." exit 0 fi git tag -a "$VERSION" -m "Release $VERSION" "$TARGET_REF" git push origin "$VERSION" - name: Create release branch if: ${{ steps.prepare.outputs.create_branch != '' }} env: CREATE_BRANCH: ${{ steps.prepare.outputs.create_branch }} TARGET_REF: ${{ steps.prepare.outputs.target_ref }} run: | set -euo pipefail # Skip if branch already exists if git ls-remote --exit-code origin "refs/heads/$CREATE_BRANCH" >/dev/null 2>&1; then echo "Branch $CREATE_BRANCH already exists, skipping." exit 0 fi git branch "$CREATE_BRANCH" "$TARGET_REF" git push origin "$CREATE_BRANCH" - name: Generate release notes env: VERSION: ${{ steps.prepare.outputs.version }} PREV_VERSION: ${{ steps.prepare.outputs.previous_version }} run: | set -euo pipefail go run ./scripts/release-action generate-notes \ --version "$VERSION" \ --previous-version "$PREV_VERSION" > /tmp/release_notes.md - name: Upload release notes uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 with: name: release-notes path: /tmp/release_notes.md retention-days: 30 release: name: Build and publish needs: [check-perms, prepare-release] runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }} permissions: # Required to publish a release contents: write # Necessary to push docker images to ghcr.io. packages: write # Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage) # Also necessary for keyless cosign (https://docs.sigstore.dev/cosign/signing/overview/) # And for GitHub Actions attestation id-token: write # Required for GitHub Actions attestation attestations: write env: CODER_RELEASE: "true" CODER_RELEASE_STABLE: ${{ needs.prepare-release.outputs.stable }} # Necessary for Docker manifest DOCKER_CLI_EXPERIMENTAL: "enabled" outputs: version: ${{ steps.version.outputs.version }} steps: - name: Harden Runner uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 with: egress-policy: audit - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false # If the event that triggered the build was an annotated tag (which our # tags are supposed to be), actions/checkout has a bug where the tag in # question is only a lightweight tag and not a full annotated tag. This # command seems to fix it. # https://github.com/actions/checkout/issues/290 - name: Fetch git tags run: git fetch --tags --force - name: Checkout release commit env: VERSION: ${{ needs.prepare-release.outputs.version }} run: | set -euo pipefail git checkout "refs/tags/$VERSION" - name: Print version id: version env: VERSION: ${{ needs.prepare-release.outputs.version }} run: | set -euo pipefail # VERSION comes from the env block, not a misspelling of the local 'version'. # shellcheck disable=SC2153 # Strip the "v" prefix for use in build steps. version="${VERSION#v}" echo "version=$version" >> "$GITHUB_OUTPUT" # Speed up future version.sh calls. echo "CODER_FORCE_VERSION=$version" >> "$GITHUB_ENV" echo "$version" - name: Download release notes uses: actions/download-artifact@95815c38cf2ff2164869cbab79da8d1f422bc89e # v4.2.1 with: name: release-notes path: /tmp - name: Set release notes env run: echo CODER_RELEASE_NOTES_FILE=/tmp/release_notes.md >> "$GITHUB_ENV" - name: Show release notes run: | set -euo pipefail cat "$CODER_RELEASE_NOTES_FILE" - name: Docker Login uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Set up mise tools uses: ./.github/actions/setup-mise with: install-args: "go node pnpm helm cosign syft" - name: Install pnpm dependencies uses: ./.github/actions/pnpm-install - name: Install Go mise tools run: ./.github/scripts/retry.sh -- mise install --locked go:github.com/tc-hib/go-winres go:github.com/goreleaser/nfpm/v2/cmd/nfpm # Necessary for signing Windows binaries. - name: Setup Java uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5.2.0 with: distribution: "zulu" java-version: "11.0" - name: Install nsis and zstd run: sudo apt-get install -y nsis zstd - name: Install rcodesign run: | set -euo pipefail wget -O /tmp/rcodesign.tar.gz https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-x86_64-unknown-linux-musl.tar.gz sudo tar -xzf /tmp/rcodesign.tar.gz \ -C /usr/bin \ --strip-components=1 \ apple-codesign-0.22.0-x86_64-unknown-linux-musl/rcodesign rm /tmp/rcodesign.tar.gz - name: Setup Apple Developer certificate and API key run: | set -euo pipefail touch /tmp/{apple_cert.p12,apple_cert_password.txt,apple_apikey.p8} chmod 600 /tmp/{apple_cert.p12,apple_cert_password.txt,apple_apikey.p8} echo "$AC_CERTIFICATE_P12_BASE64" | base64 -d > /tmp/apple_cert.p12 echo "$AC_CERTIFICATE_PASSWORD" > /tmp/apple_cert_password.txt echo "$AC_APIKEY_P8_BASE64" | base64 -d > /tmp/apple_apikey.p8 env: AC_CERTIFICATE_P12_BASE64: ${{ secrets.AC_CERTIFICATE_P12_BASE64 }} AC_CERTIFICATE_PASSWORD: ${{ secrets.AC_CERTIFICATE_PASSWORD }} AC_APIKEY_P8_BASE64: ${{ secrets.AC_APIKEY_P8_BASE64 }} - name: Setup Windows EV Signing Certificate run: | set -euo pipefail touch /tmp/ev_cert.pem chmod 600 /tmp/ev_cert.pem echo "$EV_SIGNING_CERT" > /tmp/ev_cert.pem wget https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar -O /tmp/jsign-6.0.jar env: EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }} - name: Test migrations from current ref to main run: | POSTGRES_VERSION=13 make test-migrations # Setup GCloud for signing Windows binaries. - name: Authenticate to Google Cloud id: gcloud_auth uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 with: workload_identity_provider: ${{ vars.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }} service_account: ${{ vars.GCP_CODE_SIGNING_SERVICE_ACCOUNT }} token_format: "access_token" - name: Setup GCloud SDK uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1 - name: Build binaries run: | set -euo pipefail ./.github/scripts/retry.sh -- go mod download version="$(./scripts/version.sh)" make gen/mark-fresh make -j \ build/coder_"$version"_linux_{amd64,armv7,arm64}.{tar.gz,apk,deb,rpm} \ build/coder_"$version"_{darwin,windows}_{amd64,arm64}.zip \ build/coder_"$version"_windows_amd64_installer.exe \ build/coder_helm_"$version".tgz \ build/provisioner_helm_"$version".tgz env: CODER_SIGN_WINDOWS: "1" CODER_SIGN_DARWIN: "1" CODER_SIGN_GPG: "1" CODER_GPG_RELEASE_KEY_BASE64: ${{ secrets.GPG_RELEASE_KEY_BASE64 }} CODER_WINDOWS_RESOURCES: "1" AC_CERTIFICATE_FILE: /tmp/apple_cert.p12 AC_CERTIFICATE_PASSWORD_FILE: /tmp/apple_cert_password.txt AC_APIKEY_ISSUER_ID: ${{ secrets.AC_APIKEY_ISSUER_ID }} AC_APIKEY_ID: ${{ secrets.AC_APIKEY_ID }} AC_APIKEY_FILE: /tmp/apple_apikey.p8 EV_KEY: ${{ secrets.EV_KEY }} EV_KEYSTORE: ${{ secrets.EV_KEYSTORE }} EV_TSA_URL: ${{ secrets.EV_TSA_URL }} EV_CERTIFICATE_PATH: /tmp/ev_cert.pem GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }} JSIGN_PATH: /tmp/jsign-6.0.jar - name: Delete Apple Developer certificate and API key run: rm -f /tmp/{apple_cert.p12,apple_cert_password.txt,apple_apikey.p8} - name: Delete Windows EV Signing Cert run: rm /tmp/ev_cert.pem - name: Determine base image tag id: image-base-tag run: | set -euo pipefail # Empty value means use the default and avoid building a fresh one. echo "tag=$(CODER_IMAGE_BASE=ghcr.io/coder/coder-base ./scripts/image_tag.sh)" >> "$GITHUB_OUTPUT" - name: Create empty base-build-context directory if: steps.image-base-tag.outputs.tag != '' run: mkdir base-build-context - name: Install depot.dev CLI if: steps.image-base-tag.outputs.tag != '' uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1 # This uses OIDC authentication, so no auth variables are required. - name: Build base Docker image via depot.dev id: build_base_image if: steps.image-base-tag.outputs.tag != '' uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0 with: project: wl5hnrrkns context: base-build-context file: scripts/Dockerfile.base platforms: linux/amd64,linux/arm64,linux/arm/v7 provenance: true sbom: true pull: true no-cache: true push: true tags: | ${{ steps.image-base-tag.outputs.tag }} - name: Verify that images are pushed properly if: steps.image-base-tag.outputs.tag != '' run: | # retry 10 times with a 5 second delay as the images may not be # available immediately for i in {1..10}; do rc=0 raw_manifests=$(docker buildx imagetools inspect --raw "${IMAGE_TAG}") || rc=$? if [[ "$rc" -eq 0 ]]; then break fi if [[ "$i" -eq 10 ]]; then echo "Failed to pull manifests after 10 retries" exit 1 fi echo "Failed to pull manifests, retrying in 5 seconds" sleep 5 done manifests=$( echo "$raw_manifests" | \ jq -r '.manifests[].platform | .os + "/" + .architecture + (if .variant then "/" + .variant else "" end)' ) # Verify all 3 platforms are present. set -euxo pipefail echo "$manifests" | grep -q linux/amd64 echo "$manifests" | grep -q linux/arm64 echo "$manifests" | grep -q linux/arm/v7 env: IMAGE_TAG: ${{ steps.image-base-tag.outputs.tag }} - name: GitHub Attestation for Base Docker image id: attest_base if: ${{ steps.build_base_image.outputs.digest != '' }} continue-on-error: true uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ghcr.io/coder/coder-base subject-digest: ${{ steps.build_base_image.outputs.digest }} push-to-registry: true - name: Build Linux Docker images id: build_docker run: | set -euxo pipefail # build Docker images for each architecture version="$(./scripts/version.sh)" make build/coder_"$version"_linux_{amd64,arm64,armv7}.tag # build and push multi-arch manifest, this depends on the other images # being pushed so will automatically push them. make push/build/coder_"$version"_linux.tag multiarch_image="$(./scripts/image_tag.sh)" echo "multiarch_image=${multiarch_image}" >> "$GITHUB_OUTPUT" # For debugging, print all docker image tags docker images # if the current version is equal to the highest (according to semver) # version in the repo, also create a multi-arch image as ":latest" and # push it if [[ "$(git tag | grep '^v' | grep -vE '(rc|dev|-|\+|\/)' | sort -r --version-sort | head -n1)" == "v$(./scripts/version.sh)" ]]; then latest_target="$(./scripts/image_tag.sh --version latest)" # shellcheck disable=SC2046 ./scripts/build_docker_multiarch.sh \ --push \ --target "${latest_target}" \ $(cat build/coder_"$version"_linux_{amd64,arm64,armv7}.tag) echo "created_latest_tag=true" >> "$GITHUB_OUTPUT" echo "latest_target=${latest_target}" >> "$GITHUB_OUTPUT" else echo "created_latest_tag=false" >> "$GITHUB_OUTPUT" fi env: CODER_BASE_IMAGE_TAG: ${{ steps.image-base-tag.outputs.tag }} - name: SBOM Generation and Attestation env: COSIGN_EXPERIMENTAL: '1' MULTIARCH_IMAGE: ${{ steps.build_docker.outputs.multiarch_image }} VERSION: ${{ steps.version.outputs.version }} CREATED_LATEST_TAG: ${{ steps.build_docker.outputs.created_latest_tag }} run: | set -euxo pipefail # Generate SBOM for multi-arch image with version in filename echo "Generating SBOM for multi-arch image: ${MULTIARCH_IMAGE}" syft "${MULTIARCH_IMAGE}" -o spdx-json > "coder_${VERSION}_sbom.spdx.json" echo "Attesting SBOM to multi-arch image: ${MULTIARCH_IMAGE}" cosign clean --force=true "${MULTIARCH_IMAGE}" cosign attest --type spdxjson \ --predicate "coder_${VERSION}_sbom.spdx.json" \ --yes \ "${MULTIARCH_IMAGE}" # If latest tag was created, also attest it if [[ "${CREATED_LATEST_TAG}" == "true" ]]; then latest_tag="$(./scripts/image_tag.sh --version latest)" echo "Generating SBOM for latest image: ${latest_tag}" syft "${latest_tag}" -o spdx-json > coder_latest_sbom.spdx.json echo "Attesting SBOM to latest image: ${latest_tag}" cosign clean --force=true "${latest_tag}" cosign attest --type spdxjson \ --predicate coder_latest_sbom.spdx.json \ --yes \ "${latest_tag}" fi - name: Resolve Docker image digests for attestation id: docker_digests continue-on-error: true env: MULTIARCH_IMAGE: ${{ steps.build_docker.outputs.multiarch_image }} LATEST_TARGET: ${{ steps.build_docker.outputs.latest_target }} run: | set -euxo pipefail if [[ -n "${MULTIARCH_IMAGE}" ]]; then multiarch_digest=$(docker buildx imagetools inspect --raw "${MULTIARCH_IMAGE}" | sha256sum | awk '{print "sha256:"$1}') echo "multiarch_digest=${multiarch_digest}" >> "$GITHUB_OUTPUT" fi if [[ -n "${LATEST_TARGET}" ]]; then latest_digest=$(docker buildx imagetools inspect --raw "${LATEST_TARGET}" | sha256sum | awk '{print "sha256:"$1}') echo "latest_digest=${latest_digest}" >> "$GITHUB_OUTPUT" fi - name: GitHub Attestation for Docker image id: attest_main if: ${{ steps.docker_digests.outputs.multiarch_digest != '' }} continue-on-error: true uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ghcr.io/coder/coder subject-digest: ${{ steps.docker_digests.outputs.multiarch_digest }} push-to-registry: true - name: GitHub Attestation for "latest" Docker image id: attest_latest if: ${{ steps.docker_digests.outputs.latest_digest != '' }} continue-on-error: true uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-name: ghcr.io/coder/coder subject-digest: ${{ steps.docker_digests.outputs.latest_digest }} push-to-registry: true - name: GitHub Attestation for release binaries id: attest_binaries continue-on-error: true uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0 with: subject-path: | ./build/*.tar.gz ./build/*.zip ./build/*.deb ./build/*.rpm ./build/*.apk ./build/*_installer.exe ./build/*_helm_*.tgz ./build/provisioner_helm_*.tgz # Report attestation failures but don't fail the workflow - name: Check attestation status run: | # zizmor: ignore[template-injection] We're just reading steps.attest_x.outcome here, no risk of injection if [[ "${{ steps.attest_base.outcome }}" == "failure" && "${{ steps.attest_base.conclusion }}" != "skipped" ]]; then echo "::warning::GitHub attestation for base image failed" fi if [[ "${{ steps.attest_main.outcome }}" == "failure" ]]; then echo "::warning::GitHub attestation for main image failed" fi if [[ "${{ steps.attest_latest.outcome }}" == "failure" && "${{ steps.attest_latest.conclusion }}" != "skipped" ]]; then echo "::warning::GitHub attestation for latest image failed" fi if [[ "${{ steps.attest_binaries.outcome }}" == "failure" && "${{ steps.attest_binaries.conclusion }}" != "skipped" ]]; then echo "::warning::GitHub attestation for release binaries failed" fi - name: Generate offline docs run: | version="$(./scripts/version.sh)" make -j build/coder_docs_"$version".tgz - name: ls build run: ls -lh build - name: Publish Coder CLI binaries and detached signatures to GCS run: | set -euxo pipefail version="$(./scripts/version.sh)" # Source array of slim binaries declare -A binaries binaries["coder-darwin-amd64"]="coder-slim_${version}_darwin_amd64" binaries["coder-darwin-arm64"]="coder-slim_${version}_darwin_arm64" binaries["coder-linux-amd64"]="coder-slim_${version}_linux_amd64" binaries["coder-linux-arm64"]="coder-slim_${version}_linux_arm64" binaries["coder-linux-armv7"]="coder-slim_${version}_linux_armv7" binaries["coder-windows-amd64.exe"]="coder-slim_${version}_windows_amd64.exe" binaries["coder-windows-arm64.exe"]="coder-slim_${version}_windows_arm64.exe" for cli_name in "${!binaries[@]}"; do slim_binary="${binaries[$cli_name]}" detached_signature="${slim_binary}.asc" gcloud storage cp "./build/${slim_binary}" "gs://releases.coder.com/coder-cli/${version}/${cli_name}" gcloud storage cp "./build/${detached_signature}" "gs://releases.coder.com/coder-cli/${version}/${cli_name}.asc" done - name: Publish release run: | set -euo pipefail # Build the list of files to publish. files=( ./build/*_installer.exe ./build/*.zip ./build/*.tar.gz ./build/*.tgz ./build/*.apk ./build/*.deb ./build/*.rpm "./coder_${VERSION}_sbom.spdx.json" ) # Only include the latest SBOM file if it was created. if [[ "${CREATED_LATEST_TAG}" == "true" ]]; then files+=(./coder_latest_sbom.spdx.json) fi stable_flag=() if [[ "$CODER_RELEASE_STABLE" == "true" ]]; then stable_flag=(--stable) fi go run ./scripts/release-action publish \ --version "v${VERSION}" \ "${stable_flag[@]}" \ --release-notes-file "$CODER_RELEASE_NOTES_FILE" \ "${files[@]}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VERSION: ${{ steps.version.outputs.version }} CREATED_LATEST_TAG: ${{ steps.build_docker.outputs.created_latest_tag }} # Mark the Linear release as shipped. - name: Extract Linear release version id: linear_version run: | # Skip RC releases — they must not complete the Linear release. if [[ "$VERSION" == *-rc* ]]; then echo "RC release (${VERSION}), skipping Linear release completion." echo "skip=true" >> "$GITHUB_OUTPUT" exit 0 fi # Strip patch to get the Linear release version (e.g. 2.32.0 -> 2.32). linear_version=$(echo "$VERSION" | cut -d. -f1,2) echo "version=$linear_version" >> "$GITHUB_OUTPUT" echo "skip=false" >> "$GITHUB_OUTPUT" echo "Completing Linear release ${linear_version}" env: VERSION: ${{ steps.version.outputs.version }} - name: Complete Linear release if: ${{ steps.linear_version.outputs.skip != 'true' }} continue-on-error: true uses: linear/linear-release-action@0353b5fa8c00326913966f00557d68f8f30b8b6b # v0.7.0 with: access_key: ${{ secrets.LINEAR_ACCESS_KEY }} command: complete version: ${{ steps.linear_version.outputs.version }} timeout: 300 - name: Authenticate to Google Cloud uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 with: workload_identity_provider: ${{ vars.GCP_WORKLOAD_ID_PROVIDER }} service_account: ${{ vars.GCP_SERVICE_ACCOUNT }} - name: Setup GCloud SDK uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # 3.0.1 - name: Publish Helm Chart run: | set -euo pipefail version="$(./scripts/version.sh)" mkdir -p build/helm cp "build/coder_helm_${version}.tgz" build/helm cp "build/provisioner_helm_${version}.tgz" build/helm gsutil cp gs://helm.coder.com/v2/index.yaml build/helm/index.yaml helm repo index build/helm --url https://helm.coder.com/v2 --merge build/helm/index.yaml gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/coder_helm_${version}.tgz" gs://helm.coder.com/v2 gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/provisioner_helm_${version}.tgz" gs://helm.coder.com/v2 gsutil -h "Cache-Control:no-cache,max-age=0" cp "build/helm/index.yaml" gs://helm.coder.com/v2 gsutil -h "Cache-Control:no-cache,max-age=0" cp "helm/artifacthub-repo.yml" gs://helm.coder.com/v2 helm push "build/coder_helm_${version}.tgz" oci://ghcr.io/coder/chart helm push "build/provisioner_helm_${version}.tgz" oci://ghcr.io/coder/chart - name: Send repository-dispatch event if: ${{ inputs.release_type != 'rc' && inputs.release_type != 'create-release-branch' }} uses: peter-evans/repository-dispatch@28959ce8df70de7be546dd1250a005dd32156697 # v4.0.1 with: token: ${{ secrets.CDRCI_GITHUB_TOKEN }} repository: coder/packages event-type: coder-release client-payload: '{"coder_version": "${{ steps.version.outputs.version }}"}' publish-homebrew: name: Publish to Homebrew tap runs-on: ubuntu-latest needs: [release, prepare-release] if: ${{ inputs.release_type != 'rc' && inputs.release_type != 'create-release-branch' && needs.prepare-release.outputs.stable == 'true' }} steps: - name: Harden Runner uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 with: egress-policy: audit - name: Update homebrew env: GH_REPO: coder/homebrew-coder GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }} VERSION: ${{ needs.release.outputs.version }} run: | # Keep version number around for reference, removing any potential leading v coder_version="$(echo "${VERSION}" | tr -d v)" set -euxo pipefail # Setup Git git config --global user.email "ci@coder.com" git config --global user.name "Coder CI" git config --global credential.helper "store" temp_dir="$(mktemp -d)" cd "$temp_dir" # Download checksums checksums_url="$(gh release view --repo coder/coder "v$coder_version" --json assets \ | jq -r ".assets | map(.url) | .[]" \ | grep -e ".checksums.txt\$")" wget "$checksums_url" -O checksums.txt # Get the SHAs darwin_arm_sha="$(grep "darwin_arm64.zip" checksums.txt | awk '{ print $1 }')" darwin_intel_sha="$(grep "darwin_amd64.zip" checksums.txt | awk '{ print $1 }')" linux_sha="$(grep "linux_amd64.tar.gz" checksums.txt | awk '{ print $1 }')" echo "macOS arm64: $darwin_arm_sha" echo "macOS amd64: $darwin_intel_sha" echo "Linux amd64: $linux_sha" # Check out the homebrew repo git clone "https://github.com/$GH_REPO" homebrew-coder brew_branch="auto-release/$coder_version" cd homebrew-coder # Check if a PR already exists. pr_count="$(gh pr list --search "head:$brew_branch" --json id,closed | jq -r ".[] | select(.closed == false) | .id" | wc -l)" if [ "$pr_count" -gt 0 ]; then echo "Bailing out as PR already exists" 2>&1 exit 0 fi # Set up cdrci credentials for pushing to homebrew-coder echo "https://x-access-token:$GH_TOKEN@github.com" >> ~/.git-credentials # Update the formulae and push git checkout -b "$brew_branch" ./scripts/update-v2.sh "$coder_version" "$darwin_arm_sha" "$darwin_intel_sha" "$linux_sha" git add . git commit -m "coder $coder_version" git push -u origin -f "$brew_branch" # Create PR gh pr create \ -B master -H "$brew_branch" \ -t "coder $coder_version" \ -b "" \ -r "${GITHUB_ACTOR}" \ -a "${GITHUB_ACTOR}" \ -b "This automatic PR was triggered by the release of Coder v$coder_version" publish-winget: name: Publish to winget-pkgs runs-on: windows-latest needs: [release, prepare-release] if: ${{ inputs.release_type != 'rc' && inputs.release_type != 'create-release-branch' }} steps: - name: Harden Runner uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 with: egress-policy: audit - name: Sync fork run: gh repo sync cdrci/winget-pkgs -b master env: GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }} - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: fetch-depth: 0 persist-credentials: false # If the event that triggered the build was an annotated tag (which our # tags are supposed to be), actions/checkout has a bug where the tag in # question is only a lightweight tag and not a full annotated tag. This # command seems to fix it. # https://github.com/actions/checkout/issues/290 - name: Fetch git tags run: git fetch --tags --force - name: Install wingetcreate run: | Invoke-WebRequest https://aka.ms/wingetcreate/latest -OutFile wingetcreate.exe - name: Submit updated manifest to winget-pkgs run: | # The package version is the same as the tag minus the leading "v". # The version in this output already has the leading "v" removed but # we do it again to be safe. $version = $env:VERSION.Trim('v') $release_assets = gh release view --repo coder/coder "v${version}" --json assets | ` ConvertFrom-Json # Get the installer URLs from the release assets. $amd64_installer_url = $release_assets.assets | ` Where-Object name -Match ".*_windows_amd64_installer.exe$" | ` Select -ExpandProperty url $amd64_zip_url = $release_assets.assets | ` Where-Object name -Match ".*_windows_amd64.zip$" | ` Select -ExpandProperty url $arm64_zip_url = $release_assets.assets | ` Where-Object name -Match ".*_windows_arm64.zip$" | ` Select -ExpandProperty url echo "amd64 Installer URL: ${amd64_installer_url}" echo "amd64 zip URL: ${amd64_zip_url}" echo "arm64 zip URL: ${arm64_zip_url}" echo "Package version: ${version}" .\wingetcreate.exe update Coder.Coder ` --submit ` --version "${version}" ` --urls "${amd64_installer_url}" "${amd64_zip_url}" "${arm64_zip_url}" env: # For gh CLI: GH_TOKEN: ${{ github.token }} # For wingetcreate. We need a real token since we're pushing a commit # to GitHub and then making a PR in a different repo. # wingetcreate will read the token from the environment variable defined below. # Reference: https://aka.ms/winget-create-token WINGET_CREATE_GITHUB_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }} VERSION: ${{ needs.release.outputs.version }} - name: Comment on PR run: | # wait 30 seconds Start-Sleep -Seconds 30.0 # Find the PR that wingetcreate just made. $version = $env:VERSION.Trim('v') $pr_list = gh pr list --repo microsoft/winget-pkgs --search "author:cdrci Coder.Coder version ${version}" --limit 1 --json number | ` ConvertFrom-Json $pr_number = $pr_list[0].number gh pr comment --repo microsoft/winget-pkgs "${pr_number}" --body "🤖 cc: @deansheather @matifali" env: # For gh CLI. We need a real token since we're commenting on a PR in a # different repo. GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }} VERSION: ${{ needs.release.outputs.version }} update-docs: name: Update release docs needs: [prepare-release, release] if: ${{ inputs.release_type != 'rc' && inputs.release_type != 'create-release-branch' }} runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }} permissions: contents: write pull-requests: write steps: - name: Harden Runner uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0 with: egress-policy: audit - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: ref: main fetch-depth: 0 persist-credentials: true - name: Fetch git tags run: git fetch --tags --force - name: Setup Node uses: ./.github/actions/setup-node - name: Update release calendar run: ./scripts/update-release-calendar.sh - name: Create docs update PR uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8 with: token: ${{ secrets.GITHUB_TOKEN }} commit-message: "docs: update release docs for ${{ needs.prepare-release.outputs.version }}" title: "docs: update release docs for ${{ needs.prepare-release.outputs.version }}" body: "Automated docs update for release ${{ needs.prepare-release.outputs.version }}." branch: docs/release-${{ needs.prepare-release.outputs.version }} base: main