# GitHub release workflow. name: Release on: workflow_dispatch: inputs: release_channel: type: choice description: Release channel options: - mainline - stable release_notes: description: Release notes for the publishing the release. This is required to create a release. dry_run: description: Perform a dry-run release (devel). Note that ref must be an annotated tag when run without dry-run. type: boolean required: true default: false permissions: contents: read concurrency: ${{ github.workflow }}-${{ github.ref }} env: # Use `inputs` (vs `github.event.inputs`) to ensure that booleans are actual # booleans, not strings. # https://github.blog/changelog/2022-06-10-github-actions-inputs-unified-across-manual-and-reusable-workflows/ CODER_RELEASE: ${{ !inputs.dry_run }} CODER_DRY_RUN: ${{ inputs.dry_run }} CODER_RELEASE_CHANNEL: ${{ inputs.release_channel }} CODER_RELEASE_NOTES: ${{ inputs.release_notes }} jobs: # build-dylib is a separate job to build the dylib on macOS. build-dylib: runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest' }} steps: # Harden Runner doesn't work on macOS. - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # 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: Setup build tools run: | brew install bash gnu-getopt make echo "$(brew --prefix bash)/bin" >> $GITHUB_PATH echo "$(brew --prefix gnu-getopt)/bin" >> $GITHUB_PATH echo "$(brew --prefix make)/libexec/gnubin" >> $GITHUB_PATH - name: Switch XCode Version uses: maxim-lobanov/setup-xcode@60606e260d2fc5762a71e64e74b2174e8ea3c8bd # v1.6.0 with: xcode-version: "16.1.0" - name: Setup Go uses: ./.github/actions/setup-go - 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-macos-universal.tar.gz sudo tar -xzf /tmp/rcodesign.tar.gz \ -C /usr/local/bin \ --strip-components=1 \ apple-codesign-0.22.0-macos-universal/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: Build dylibs run: | set -euxo pipefail go mod download make gen/mark-fresh make build/coder-dylib env: CODER_SIGN_DARWIN: 1 AC_CERTIFICATE_FILE: /tmp/apple_cert.p12 AC_CERTIFICATE_PASSWORD_FILE: /tmp/apple_cert_password.txt - name: Upload build artifacts uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: dylibs path: | ./build/*.h ./build/*.dylib retention-days: 7 - name: Delete Apple Developer certificate and API key run: rm -f /tmp/{apple_cert.p12,apple_cert_password.txt,apple_apikey.p8} release: name: Build and publish needs: build-dylib 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: # Necessary for Docker manifest DOCKER_CLI_EXPERIMENTAL: "enabled" outputs: version: ${{ steps.version.outputs.version }} steps: - name: Harden Runner uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # 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: Print version id: version run: | set -euo pipefail version="$(./scripts/version.sh)" echo "version=$version" >> $GITHUB_OUTPUT # Speed up future version.sh calls. echo "CODER_FORCE_VERSION=$version" >> $GITHUB_ENV echo "$version" # Verify that all expectations for a release are met. - name: Verify release input if: ${{ !inputs.dry_run }} run: | set -euo pipefail if [[ "${GITHUB_REF}" != "refs/tags/v"* ]]; then echo "Ref must be a semver tag when creating a release, did you use scripts/release.sh?" exit 1 fi # 2.10.2 -> release/2.10 version="$(./scripts/version.sh)" release_branch=release/${version%.*} branch_contains_tag=$(git branch --remotes --contains "${GITHUB_REF}" --list "*/${release_branch}" --format='%(refname)') if [[ -z "${branch_contains_tag}" ]]; then echo "Ref tag must exist in a branch named ${release_branch} when creating a release, did you use scripts/release.sh?" exit 1 fi if [[ -z "${CODER_RELEASE_NOTES}" ]]; then echo "Release notes are required to create a release, did you use scripts/release.sh?" exit 1 fi echo "Release inputs verified:" echo echo "- Ref: ${GITHUB_REF}" echo "- Version: ${version}" echo "- Release channel: ${CODER_RELEASE_CHANNEL}" echo "- Release branch: ${release_branch}" echo "- Release notes: true" - name: Create release notes file run: | set -euo pipefail release_notes_file="$(mktemp -t release_notes.XXXXXX)" echo "$CODER_RELEASE_NOTES" > "$release_notes_file" echo CODER_RELEASE_NOTES_FILE="$release_notes_file" >> $GITHUB_ENV - name: Show release notes run: | set -euo pipefail cat "$CODER_RELEASE_NOTES_FILE" - name: Docker Login uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Setup Go uses: ./.github/actions/setup-go - name: Setup Node uses: ./.github/actions/setup-node # Necessary for signing Windows binaries. - name: Setup Java uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: "zulu" java-version: "11.0" - name: Install go-winres run: go install github.com/tc-hib/go-winres@d743268d7ea168077ddd443c4240562d4f5e8c3e # v0.3.3 - name: Install nsis and zstd run: sudo apt-get install -y nsis zstd - name: Install nfpm run: | set -euo pipefail wget -O /tmp/nfpm.deb https://github.com/goreleaser/nfpm/releases/download/v2.35.1/nfpm_2.35.1_amd64.deb sudo dpkg -i /tmp/nfpm.deb rm /tmp/nfpm.deb - 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: Install cosign uses: ./.github/actions/install-cosign - name: Install syft uses: ./.github/actions/install-syft - 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@b7593ed2efd1c1617e1b0254da33b86225adb2a5 # v2.1.12 with: workload_identity_provider: ${{ secrets.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }} service_account: ${{ secrets.GCP_CODE_SIGNING_SERVICE_ACCOUNT }} token_format: "access_token" - name: Setup GCloud SDK uses: google-github-actions/setup-gcloud@6a7c903a70c8625ed6700fa299f5ddb4ca6022e9 # v2.1.5 - name: Download dylibs uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: dylibs path: ./build - name: Insert dylibs run: | mv ./build/*amd64.dylib ./site/out/bin/coder-vpn-darwin-amd64.dylib mv ./build/*arm64.dylib ./site/out/bin/coder-vpn-darwin-arm64.dylib mv ./build/*arm64.h ./site/out/bin/coder-vpn-darwin-dylib.h - name: Build binaries run: | set -euo pipefail 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 if [[ "${CODER_RELEASE:-}" != *t* ]] || [[ "${CODER_DRY_RUN:-}" == *t* ]]; then # Empty value means use the default and avoid building a fresh one. echo "tag=" >> $GITHUB_OUTPUT else echo "tag=$(CODER_IMAGE_BASE=ghcr.io/coder/coder-base ./scripts/image_tag.sh)" >> $GITHUB_OUTPUT fi - 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@b0b1ea4f69e92ebf5dea3f8713a1b0c37b2126a5 # v1.6.0 # This uses OIDC authentication, so no auth variables are required. - name: Build base Docker image via depot.dev if: steps.image-base-tag.outputs.tag != '' uses: depot/build-push-action@2583627a84956d07561420dcc1d0eb1f2af3fac0 # v1.15.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 "${{ steps.image-base-tag.outputs.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 # GitHub attestation provides SLSA provenance for Docker images, establishing a verifiable # record that these images were built in GitHub Actions with specific inputs and environment. # This complements our existing cosign attestations (which focus on SBOMs) by adding # GitHub-specific build provenance to enhance our supply chain security. # # TODO: Consider refactoring these attestation steps to use a matrix strategy or composite action # to reduce duplication while maintaining the required functionality for each distinct image tag. - name: GitHub Attestation for Base Docker image id: attest_base if: ${{ !inputs.dry_run && steps.image-base-tag.outputs.tag != '' }} continue-on-error: true uses: actions/attest@ce27ba3b4a9a139d9a20a4a07d69fabb52f1e5bc # v2.4.0 with: subject-name: ${{ steps.image-base-tag.outputs.tag }} predicate-type: "https://slsa.dev/provenance/v1" predicate: | { "buildType": "https://github.com/actions/runner-images/", "builder": { "id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" }, "invocation": { "configSource": { "uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}", "digest": { "sha1": "${{ github.sha }}" }, "entryPoint": ".github/workflows/release.yaml" }, "environment": { "github_workflow": "${{ github.workflow }}", "github_run_id": "${{ github.run_id }}" } }, "metadata": { "buildInvocationID": "${{ github.run_id }}", "completeness": { "environment": true, "materials": true } } } push-to-registry: true - name: Build Linux Docker images id: build_docker run: | set -euxo pipefail # we can't build multi-arch if the images aren't pushed, so quit now # if dry-running if [[ "$CODER_RELEASE" != *t* ]]; then echo Skipping multi-arch docker builds due to dry-run. exit 0 fi # 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 # Save multiarch image tag for attestation 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 created_latest_tag=false if [[ "$(git tag | grep '^v' | grep -vE '(rc|dev|-|\+|\/)' | sort -r --version-sort | head -n1)" == "v$(./scripts/version.sh)" ]]; then ./scripts/build_docker_multiarch.sh \ --push \ --target "$(./scripts/image_tag.sh --version latest)" \ $(cat build/coder_"$version"_linux_{amd64,arm64,armv7}.tag) created_latest_tag=true echo "created_latest_tag=true" >> $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 if: ${{ !inputs.dry_run }} env: COSIGN_EXPERIMENTAL: "1" run: | set -euxo pipefail # Generate SBOM for multi-arch image with version in filename echo "Generating SBOM for multi-arch image: ${{ steps.build_docker.outputs.multiarch_image }}" syft "${{ steps.build_docker.outputs.multiarch_image }}" -o spdx-json > coder_${{ steps.version.outputs.version }}_sbom.spdx.json # Attest SBOM to multi-arch image echo "Attesting SBOM to multi-arch image: ${{ steps.build_docker.outputs.multiarch_image }}" cosign clean --force=true "${{ steps.build_docker.outputs.multiarch_image }}" cosign attest --type spdxjson \ --predicate coder_${{ steps.version.outputs.version }}_sbom.spdx.json \ --yes \ "${{ steps.build_docker.outputs.multiarch_image }}" # If latest tag was created, also attest it if [[ "${{ steps.build_docker.outputs.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: GitHub Attestation for Docker image id: attest_main if: ${{ !inputs.dry_run }} continue-on-error: true uses: actions/attest@ce27ba3b4a9a139d9a20a4a07d69fabb52f1e5bc # v2.4.0 with: subject-name: ${{ steps.build_docker.outputs.multiarch_image }} predicate-type: "https://slsa.dev/provenance/v1" predicate: | { "buildType": "https://github.com/actions/runner-images/", "builder": { "id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" }, "invocation": { "configSource": { "uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}", "digest": { "sha1": "${{ github.sha }}" }, "entryPoint": ".github/workflows/release.yaml" }, "environment": { "github_workflow": "${{ github.workflow }}", "github_run_id": "${{ github.run_id }}" } }, "metadata": { "buildInvocationID": "${{ github.run_id }}", "completeness": { "environment": true, "materials": true } } } push-to-registry: true # Get the latest tag name for attestation - name: Get latest tag name id: latest_tag if: ${{ !inputs.dry_run && steps.build_docker.outputs.created_latest_tag == 'true' }} run: echo "tag=$(./scripts/image_tag.sh --version latest)" >> $GITHUB_OUTPUT # If this is the highest version according to semver, also attest the "latest" tag - name: GitHub Attestation for "latest" Docker image id: attest_latest if: ${{ !inputs.dry_run && steps.build_docker.outputs.created_latest_tag == 'true' }} continue-on-error: true uses: actions/attest@ce27ba3b4a9a139d9a20a4a07d69fabb52f1e5bc # v2.4.0 with: subject-name: ${{ steps.latest_tag.outputs.tag }} predicate-type: "https://slsa.dev/provenance/v1" predicate: | { "buildType": "https://github.com/actions/runner-images/", "builder": { "id": "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" }, "invocation": { "configSource": { "uri": "git+https://github.com/${{ github.repository }}@${{ github.ref }}", "digest": { "sha1": "${{ github.sha }}" }, "entryPoint": ".github/workflows/release.yaml" }, "environment": { "github_workflow": "${{ github.workflow }}", "github_run_id": "${{ github.run_id }}" } }, "metadata": { "buildInvocationID": "${{ github.run_id }}", "completeness": { "environment": true, "materials": true } } } push-to-registry: true # Report attestation failures but don't fail the workflow - name: Check attestation status if: ${{ !inputs.dry_run }} run: | 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 - 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 if: ${{ !inputs.dry_run }} run: | set -euxo pipefail version="$(./scripts/version.sh)" binaries=( "coder-darwin-amd64" "coder-darwin-arm64" "coder-linux-amd64" "coder-linux-arm64" "coder-linux-armv7" "coder-windows-amd64.exe" "coder-windows-arm64.exe" ) for binary in "${binaries[@]}"; do detached_signature="${binary}.asc" gcloud storage cp "./site/out/bin/${binary}" "gs://releases.coder.com/coder-cli/${version}/${binary}" gcloud storage cp "./site/out/bin/${detached_signature}" "gs://releases.coder.com/coder-cli/${version}/${detached_signature}" done - name: Publish release run: | set -euo pipefail publish_args=() if [[ $CODER_RELEASE_CHANNEL == "stable" ]]; then publish_args+=(--stable) fi if [[ $CODER_DRY_RUN == *t* ]]; then publish_args+=(--dry-run) fi declare -p publish_args # 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_${{ steps.version.outputs.version }}_sbom.spdx.json ) # Only include the latest SBOM file if it was created if [[ "${{ steps.build_docker.outputs.created_latest_tag }}" == "true" ]]; then files+=(./coder_latest_sbom.spdx.json) fi ./scripts/release/publish.sh \ "${publish_args[@]}" \ --release-notes-file "$CODER_RELEASE_NOTES_FILE" \ "${files[@]}" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} CODER_GPG_RELEASE_KEY_BASE64: ${{ secrets.GPG_RELEASE_KEY_BASE64 }} - name: Authenticate to Google Cloud uses: google-github-actions/auth@b7593ed2efd1c1617e1b0254da33b86225adb2a5 # v2.1.12 with: workload_identity_provider: ${{ secrets.GCP_WORKLOAD_ID_PROVIDER }} service_account: ${{ secrets.GCP_SERVICE_ACCOUNT }} - name: Setup GCloud SDK uses: google-github-actions/setup-gcloud@6a7c903a70c8625ed6700fa299f5ddb4ca6022e9 # 2.1.5 - name: Publish Helm Chart if: ${{ !inputs.dry_run }} 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: Upload artifacts to actions (if dry-run) if: ${{ inputs.dry_run }} uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: release-artifacts path: | ./build/*_installer.exe ./build/*.zip ./build/*.tar.gz ./build/*.tgz ./build/*.apk ./build/*.deb ./build/*.rpm ./coder_${{ steps.version.outputs.version }}_sbom.spdx.json retention-days: 7 - name: Upload latest sbom artifact to actions (if dry-run) if: inputs.dry_run && steps.build_docker.outputs.created_latest_tag == 'true' uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: latest-sbom-artifact path: ./coder_latest_sbom.spdx.json retention-days: 7 - name: Send repository-dispatch event if: ${{ !inputs.dry_run }} uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3.0.0 with: token: ${{ secrets.CDRCI_GITHUB_TOKEN }} repository: coder/packages event-type: coder-release client-payload: '{"coder_version": "${{ steps.version.outputs.version }}", "release_channel": "${{ inputs.release_channel }}"}' publish-homebrew: name: Publish to Homebrew tap runs-on: ubuntu-latest needs: release if: ${{ !inputs.dry_run }} steps: # TODO: skip this if it's not a new release (i.e. a backport). This is # fine right now because it just makes a PR that we can close. - name: Harden Runner uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit - name: Update homebrew env: # Variables used by the `gh` command GH_REPO: coder/homebrew-coder GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }} run: | # Keep version number around for reference, removing any potential leading v coder_version="$(echo "${{ needs.release.outputs.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="$(cat checksums.txt | grep "darwin_arm64.zip" | awk '{ print $1 }')" darwin_intel_sha="$(cat checksums.txt | grep "darwin_amd64.zip" | awk '{ print $1 }')" linux_sha="$(cat checksums.txt | grep "linux_amd64.tar.gz" | 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" > 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 if: ${{ !inputs.dry_run }} steps: - name: Harden Runner uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.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@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 0 # 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 = "${{ needs.release.outputs.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}" ` --token "$env:WINGET_GH_TOKEN" 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. WINGET_GH_TOKEN: ${{ secrets.CDRCI_GITHUB_TOKEN }} - name: Comment on PR run: | # wait 30 seconds Start-Sleep -Seconds 30.0 # Find the PR that wingetcreate just made. $version = "${{ needs.release.outputs.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 }} # publish-sqlc pushes the latest schema to sqlc cloud. # At present these pushes cannot be tagged, so the last push is always the latest. publish-sqlc: name: "Publish to schema sqlc cloud" runs-on: "ubuntu-latest" needs: release if: ${{ !inputs.dry_run }} steps: - name: Harden Runner uses: step-security/harden-runner@ec9f2d5744a09debf3a187a3f4f675c53b671911 # v2.13.0 with: egress-policy: audit - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: fetch-depth: 1 # We need golang to run the migration main.go - name: Setup Go uses: ./.github/actions/setup-go - name: Setup sqlc uses: ./.github/actions/setup-sqlc - name: Push schema to sqlc cloud # Don't block a release on this continue-on-error: true run: | make sqlc-push