From bc4f485e507aef24b4301b52ddc5bc56cc6b27fc Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:07:22 -0600 Subject: [PATCH 01/31] Automate Docker image builds --- .dockerignore | 34 +++++++++++++++++ .github/docker/.gitignore | 1 + .github/docker/Makefile | 54 +++++++++++++++++++++++++++ .github/docker/cli.Dockerfile | 34 +++++++++++++++++ .github/docker/docker-bake.hcl | 47 +++++++++++++++++++++++ .github/workflows/goreleaser.yml | 52 +++++++++++++++++++++++++- .github/workflows/trigger-publish.yml | 40 -------------------- Dockerfile | 12 ------ Makefile | 5 ++- 9 files changed, 224 insertions(+), 55 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/docker/.gitignore create mode 100644 .github/docker/Makefile create mode 100644 .github/docker/cli.Dockerfile create mode 100644 .github/docker/docker-bake.hcl delete mode 100644 .github/workflows/trigger-publish.yml delete mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..5f46c6175 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,34 @@ +# Git +.git +.gitignore +.gitattributes + +# GitHub +.github + +# Build artifacts +dist/ +build/ +.goreleaser/ +*.snap + +# Development +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Documentation +*.md +docs/ + +# CI/CD +.circleci/ + +# Test files +*_test.go +testdata/ + +# Misc +.DS_Store diff --git a/.github/docker/.gitignore b/.github/docker/.gitignore new file mode 100644 index 000000000..567609b12 --- /dev/null +++ b/.github/docker/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.github/docker/Makefile b/.github/docker/Makefile new file mode 100644 index 000000000..79ba140c9 --- /dev/null +++ b/.github/docker/Makefile @@ -0,0 +1,54 @@ +IMAGE_REPO ?= temporalio + +CLI_SHA := $(shell git rev-parse HEAD) +IMAGE_SHA_TAG := $(shell git rev-parse --short HEAD) +IMAGE_BRANCH_TAG := $(shell git rev-parse --abbrev-ref HEAD) + +BAKE_ENV := CLI_SHA=$(CLI_SHA) \ + IMAGE_REPO=$(IMAGE_REPO) \ + IMAGE_SHA_TAG=$(IMAGE_SHA_TAG) \ + IMAGE_BRANCH_TAG=$(IMAGE_BRANCH_TAG) + +.PHONY: help +help: ## Display this help message + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' + +.PHONY: build +build: ## Build Docker images locally + cd ../.. && goreleaser release --snapshot --clean --skip=publish + $(BAKE_ENV) docker buildx bake --file docker-bake.hcl cli + +.PHONY: load +load: ## Build and load image into local Docker + cd ../.. && goreleaser release --snapshot --clean --skip=publish + $(BAKE_ENV) docker buildx bake --file docker-bake.hcl --load cli + +.PHONY: push +push: ## Build and push Docker images + cd ../.. && goreleaser release --snapshot --clean --skip=publish + $(BAKE_ENV) docker buildx bake --file docker-bake.hcl --push cli + +.PHONY: clean +clean: ## Clean build artifacts + rm -rf ../../dist + +.PHONY: update-alpine +update-alpine: ## Update Alpine base image to latest version and digest (usage: make update-alpine [ALPINE_TAG=3.22]) + @if [ -n "$(ALPINE_TAG)" ]; then \ + LATEST_TAG=$(ALPINE_TAG); \ + else \ + echo "Fetching latest Alpine version from Docker Hub..."; \ + LATEST_TAG=$$(curl -s https://registry.hub.docker.com/v2/repositories/library/alpine/tags\?page_size=100 | \ + jq -r '.results[].name' | grep -E '^3\.[0-9]+$$' | sort -V | tail -1); \ + fi && \ + echo "Alpine version: $$LATEST_TAG" && \ + DIGEST=$$(docker buildx imagetools inspect alpine:$$LATEST_TAG 2>/dev/null | grep "Digest:" | head -1 | awk '{print $$2}') && \ + DIGEST_HASH=$${DIGEST#sha256:} && \ + echo "Digest: sha256:$$DIGEST_HASH" && \ + ALPINE_FULL="alpine:$$LATEST_TAG@sha256:$$DIGEST_HASH" && \ + if sed --version 2>&1 | grep -q GNU; then \ + sed -i "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" docker-bake.hcl; \ + else \ + sed -i '' "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" docker-bake.hcl; \ + fi && \ + echo "Updated docker-bake.hcl with $$ALPINE_FULL" diff --git a/.github/docker/cli.Dockerfile b/.github/docker/cli.Dockerfile new file mode 100644 index 000000000..3efb0bc2d --- /dev/null +++ b/.github/docker/cli.Dockerfile @@ -0,0 +1,34 @@ +# syntax=docker/dockerfile:1 + +ARG ALPINE_IMAGE +ARG BUILDARCH + +# Build stage - copy binaries from goreleaser output +FROM --platform=$BUILDARCH scratch AS dist +COPY ../../dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal +COPY ../../dist/nix_linux_arm64/temporal /dist/arm64/temporal + +# Stage to extract CA certificates and create user files +FROM ${ALPINE_IMAGE} AS certs +RUN apk add --no-cache ca-certificates && \ + adduser -u 1000 -D temporal + +# Final stage - minimal scratch-based image +FROM scratch + +ARG TARGETARCH + +# Copy CA certificates from certs stage +COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ + +# Copy passwd and group files for non-root user +COPY --from=certs /etc/passwd /etc/passwd +COPY --from=certs /etc/group /etc/group + +# Copy the appropriate binary for target architecture +COPY --from=dist /dist/$TARGETARCH/temporal /temporal + +# Run as non-root user temporal (uid 1000) +USER 1000:1000 + +ENTRYPOINT ["/temporal"] diff --git a/.github/docker/docker-bake.hcl b/.github/docker/docker-bake.hcl new file mode 100644 index 000000000..735eb19fd --- /dev/null +++ b/.github/docker/docker-bake.hcl @@ -0,0 +1,47 @@ +variable "IMAGE_REPO" { + default = "temporalio" +} + +variable "IMAGE_SHA_TAG" {} + +variable "IMAGE_BRANCH_TAG" {} + +variable "CLI_SHA" { + default = "" +} + +variable "VERSION" { + default = "dev" +} + +variable "TAG_LATEST" { + default = false +} + +# Alpine base image with digest for reproducible builds +variable "ALPINE_IMAGE" { + default = "alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412" +} + +target "cli" { + dockerfile = "cli.Dockerfile" + context = ".github/docker" + tags = compact([ + "${IMAGE_REPO}/temporal:${IMAGE_SHA_TAG}", + "${IMAGE_REPO}/temporal:${VERSION}", + TAG_LATEST ? "${IMAGE_REPO}/temporal:latest" : "", + ]) + platforms = ["linux/amd64", "linux/arm64"] + args = { + ALPINE_IMAGE = "${ALPINE_IMAGE}" + } + labels = { + "org.opencontainers.image.title" = "temporal" + "org.opencontainers.image.description" = "Temporal CLI" + "org.opencontainers.image.url" = "https://github.com/temporalio/cli" + "org.opencontainers.image.source" = "https://github.com/temporalio/cli" + "org.opencontainers.image.licenses" = "MIT" + "org.opencontainers.image.revision" = "${CLI_SHA}" + "org.opencontainers.image.created" = timestamp() + } +} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index 3ef1d0b13..dedf11410 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -1,4 +1,4 @@ -name: goreleaser +name: Release on: workflow_dispatch: @@ -7,7 +7,8 @@ on: - published jobs: - goreleaser: + release: + name: Build and Publish Release runs-on: ubuntu-latest steps: - name: Checkout @@ -53,3 +54,50 @@ jobs: GIT_BRANCH: ${{ steps.branch.outputs.branch }} BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} GO_VERSION: ${{ steps.go.outputs.go }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Get build metadata + id: meta + run: | + echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + echo "image_sha_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "image_branch_tag=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT + # Get version from tag, strip 'v' prefix + VERSION="${GITHUB_REF#refs/tags/v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Check if release is latest + id: check_latest + uses: actions/github-script@v7 + with: + script: | + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: context.ref.replace('refs/tags/', '') + }); + // Tag as latest only if release is marked as latest (not pre-release) + core.setOutput('tag_latest', release.prerelease ? 'false' : 'true'); + console.log(`Release prerelease: ${release.prerelease}, tag_latest: ${!release.prerelease}`) + + - name: Build and push Docker images + run: | + docker buildx bake \ + --file .github/docker/docker-bake.hcl \ + --push \ + cli + env: + CLI_SHA: ${{ steps.meta.outputs.cli_sha }} + IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} + IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} + VERSION: ${{ steps.meta.outputs.version }} + TAG_LATEST: ${{ steps.check_latest.outputs.tag_latest }} + IMAGE_REPO: temporalio diff --git a/.github/workflows/trigger-publish.yml b/.github/workflows/trigger-publish.yml deleted file mode 100644 index 6f65fefe4..000000000 --- a/.github/workflows/trigger-publish.yml +++ /dev/null @@ -1,40 +0,0 @@ -name: 'Trigger Docker image build' - -on: - workflow_dispatch: - release: - types: [published] - -jobs: - trigger: - if: ${{ ! contains(github.ref, '-rc.') }} - name: 'trigger Docker image build' - runs-on: ubuntu-latest - - defaults: - run: - shell: bash - - steps: - - name: Generate a token - id: generate_token - uses: actions/create-github-app-token@v2 - with: - app-id: ${{ secrets.TEMPORAL_CICD_APP_ID }} - private-key: ${{ secrets.TEMPORAL_CICD_PRIVATE_KEY }} - owner: temporalio - repositories: | - cli - docker-builds - - - name: Dispatch docker builds Github Action - env: - PAT: ${{ steps.generate_token.outputs.token }} - PARENT_REPO: temporalio/docker-builds - PARENT_BRANCH: ${{ toJSON('main') }} - WORKFLOW_ID: update-submodules.yml - REPO: ${{ toJSON('cli') }} - BRANCH: ${{ toJSON('main') }} - COMMIT: ${{ toJSON(github.sha) }} - run: | - curl -fL -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: token $PAT" "https://api.github.com/repos/$PARENT_REPO/actions/workflows/$WORKFLOW_ID/dispatches" -d '{"ref":'"$PARENT_BRANCH"', "inputs": { "repo":'"$REPO"', "branch":'"$BRANCH"', "commit": '"$COMMIT"' }}' diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 05c3938a3..000000000 --- a/Dockerfile +++ /dev/null @@ -1,12 +0,0 @@ -FROM --platform=$BUILDARCH scratch AS dist -COPY ./dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal -COPY ./dist/nix_linux_arm64/temporal /dist/arm64/temporal - -FROM alpine:3.22 -ARG TARGETARCH -RUN apk add --no-cache ca-certificates -COPY --from=dist /dist/$TARGETARCH/temporal /usr/local/bin/temporal -RUN adduser -u 1000 -D temporal -USER temporal - -ENTRYPOINT ["temporal"] diff --git a/Makefile b/Makefile index 448ee5246..d3795361e 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: all gen build fmt-imports +.PHONY: all gen build fmt-imports update-alpine all: gen build @@ -9,3 +9,6 @@ internal/commands.gen.go: internal/commandsgen/commands.yml build: go build ./cmd/temporal + +update-alpine: + $(MAKE) -C .github/docker update-alpine From 42cc84f3174d7756d6bd126d57406320369290c0 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:08:25 -0600 Subject: [PATCH 02/31] move the docker ignore file to the correct location --- .dockerignore => .github/docker/.dockerignore | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .dockerignore => .github/docker/.dockerignore (100%) diff --git a/.dockerignore b/.github/docker/.dockerignore similarity index 100% rename from .dockerignore rename to .github/docker/.dockerignore From 04e92b6518de7b385fd98d9c52ec92c22a9377ec Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 11:40:04 -0600 Subject: [PATCH 03/31] fix a few bugs --- .github/docker/.dockerignore => .dockerignore | 2 +- .github/docker/Makefile | 38 +++++++++++++------ .github/docker/cli.Dockerfile | 4 +- .github/docker/docker-bake.hcl | 4 +- .github/workflows/goreleaser.yml | 11 +++++- .goreleaser.yml | 4 +- 6 files changed, 44 insertions(+), 19 deletions(-) rename .github/docker/.dockerignore => .dockerignore (80%) diff --git a/.github/docker/.dockerignore b/.dockerignore similarity index 80% rename from .github/docker/.dockerignore rename to .dockerignore index 5f46c6175..35017b879 100644 --- a/.github/docker/.dockerignore +++ b/.dockerignore @@ -7,10 +7,10 @@ .github # Build artifacts -dist/ build/ .goreleaser/ *.snap +# Keep dist/ as it contains binaries needed for Docker build # Development .vscode/ diff --git a/.github/docker/Makefile b/.github/docker/Makefile index 79ba140c9..ead30199d 100644 --- a/.github/docker/Makefile +++ b/.github/docker/Makefile @@ -1,4 +1,14 @@ -IMAGE_REPO ?= temporalio +# Detect repository owner from git remote +REPO_OWNER := $(shell git remote get-url origin | sed -E 's|.*[:/]([^/]+)/[^/]+\.git|\1|' | tr '[:upper:]' '[:lower:]') + +# For temporalio org, default to temporalio. For others, require IMAGE_REPO to be set +ifeq ($(REPO_OWNER),temporalio) + IMAGE_REPO ?= temporalio +else + ifndef IMAGE_REPO + $(error IMAGE_REPO must be set for non-temporalio repositories. Usage: make build IMAGE_REPO=your-dockerhub-username) + endif +endif CLI_SHA := $(shell git rev-parse HEAD) IMAGE_SHA_TAG := $(shell git rev-parse --short HEAD) @@ -14,19 +24,25 @@ help: ## Display this help message @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' .PHONY: build -build: ## Build Docker images locally - cd ../.. && goreleaser release --snapshot --clean --skip=publish - $(BAKE_ENV) docker buildx bake --file docker-bake.hcl cli - -.PHONY: load -load: ## Build and load image into local Docker +build: ## Build Docker images (multi-arch) + @echo "Building binaries with goreleaser..." cd ../.. && goreleaser release --snapshot --clean --skip=publish - $(BAKE_ENV) docker buildx bake --file docker-bake.hcl --load cli + @echo "Building Docker images (multi-arch)..." + cd ../.. && $(BAKE_ENV) docker buildx bake --file .github/docker/docker-bake.hcl cli + @echo "" + @echo "Success! Built multi-arch images" + @echo "Note: Multi-arch images are not loaded into Docker. Use 'make build-local' to build and test locally." -.PHONY: push -push: ## Build and push Docker images +.PHONY: build-local +build-local: ## Build and load Docker image for local testing + @echo "Building binaries with goreleaser..." cd ../.. && goreleaser release --snapshot --clean --skip=publish - $(BAKE_ENV) docker buildx bake --file docker-bake.hcl --push cli + @echo "Building Docker image for local platform..." + cd ../.. && $(BAKE_ENV) docker buildx bake --file .github/docker/docker-bake.hcl --set cli.platforms=linux/amd64 --load cli + @echo "Testing Docker image..." + docker run --rm $(IMAGE_REPO)/temporal:$(IMAGE_SHA_TAG) --version + @echo "" + @echo "Success! Image: $(IMAGE_REPO)/temporal:$(IMAGE_SHA_TAG)" .PHONY: clean clean: ## Clean build artifacts diff --git a/.github/docker/cli.Dockerfile b/.github/docker/cli.Dockerfile index 3efb0bc2d..d64700de0 100644 --- a/.github/docker/cli.Dockerfile +++ b/.github/docker/cli.Dockerfile @@ -5,8 +5,8 @@ ARG BUILDARCH # Build stage - copy binaries from goreleaser output FROM --platform=$BUILDARCH scratch AS dist -COPY ../../dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal -COPY ../../dist/nix_linux_arm64/temporal /dist/arm64/temporal +COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal +COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal # Stage to extract CA certificates and create user files FROM ${ALPINE_IMAGE} AS certs diff --git a/.github/docker/docker-bake.hcl b/.github/docker/docker-bake.hcl index 735eb19fd..e1818912b 100644 --- a/.github/docker/docker-bake.hcl +++ b/.github/docker/docker-bake.hcl @@ -24,8 +24,8 @@ variable "ALPINE_IMAGE" { } target "cli" { - dockerfile = "cli.Dockerfile" - context = ".github/docker" + dockerfile = ".github/docker/cli.Dockerfile" + context = "." tags = compact([ "${IMAGE_REPO}/temporal:${IMAGE_SHA_TAG}", "${IMAGE_REPO}/temporal:${VERSION}", diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index dedf11410..dd1c43055 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -43,9 +43,9 @@ jobs: run: echo "::set-output name=go::$(go version | cut -d ' ' -f 3)" - name: Run GoReleaser - uses: goreleaser/goreleaser-action@336e29918d653399e599bfca99fadc1d7ffbc9f7 # v4.3.0 + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 with: - version: v1.26.2 + version: v2.12.7 args: release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -73,6 +73,12 @@ jobs: # Get version from tag, strip 'v' prefix VERSION="${GITHUB_REF#refs/tags/v}" echo "version=$VERSION" >> $GITHUB_OUTPUT + # Determine image repo based on repository owner + if [[ "${{ github.repository_owner }}" == "temporalio" ]]; then + echo "image_repo=temporalio" >> $GITHUB_OUTPUT + else + echo "image_repo=${{ github.repository_owner }}" >> $GITHUB_OUTPUT + fi - name: Check if release is latest id: check_latest @@ -89,6 +95,7 @@ jobs: console.log(`Release prerelease: ${release.prerelease}, tag_latest: ${!release.prerelease}`) - name: Build and push Docker images + if: github.repository_owner == 'temporalio' run: | docker buildx bake \ --file .github/docker/docker-bake.hcl \ diff --git a/.goreleaser.yml b/.goreleaser.yml index 9c94e1b4f..f751b2f02 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,3 +1,5 @@ +version: 2 + before: hooks: - go mod download @@ -61,7 +63,7 @@ checksum: algorithm: sha256 changelog: - skip: true + disable: true announce: skip: "true" From 0e9929ee7e4f84b0b76a936714fef751425f9f85 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:33:36 -0600 Subject: [PATCH 04/31] support building on pr and upgrade goreleaser --- .dockerignore | 34 ----- .github/workflows/build-and-publish.yml | 161 +++++++++++++++++++++++ .github/workflows/build-docker-image.yml | 16 +++ .github/workflows/goreleaser.yml | 110 ++-------------- 4 files changed, 187 insertions(+), 134 deletions(-) delete mode 100644 .dockerignore create mode 100644 .github/workflows/build-and-publish.yml create mode 100644 .github/workflows/build-docker-image.yml diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 35017b879..000000000 --- a/.dockerignore +++ /dev/null @@ -1,34 +0,0 @@ -# Git -.git -.gitignore -.gitattributes - -# GitHub -.github - -# Build artifacts -build/ -.goreleaser/ -*.snap -# Keep dist/ as it contains binaries needed for Docker build - -# Development -.vscode/ -.idea/ -*.swp -*.swo -*~ - -# Documentation -*.md -docs/ - -# CI/CD -.circleci/ - -# Test files -*_test.go -testdata/ - -# Misc -.DS_Store diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml new file mode 100644 index 000000000..310c270e4 --- /dev/null +++ b/.github/workflows/build-and-publish.yml @@ -0,0 +1,161 @@ +name: Build and Publish (Reusable) + +on: + workflow_call: + inputs: + publish: + description: 'Whether to publish the release and Docker image' + required: true + type: boolean + version: + description: 'Version tag for the release (required if publish is true)' + required: false + type: string + secrets: + DOCKER_USERNAME: + required: false + DOCKER_PASSWORD: + required: false + +permissions: + contents: write + +jobs: + build: + name: Build and Publish + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + with: + go-version-file: "go.mod" + check-latest: true + + - name: Get build date + id: date + run: echo "::set-output name=date::$(date '+%F-%T')" + + - name: Get build unix timestamp + id: timestamp + run: echo "::set-output name=timestamp::$(date '+%s')" + + - name: Get git branch + id: branch + run: echo "::set-output name=branch::$(git rev-parse --abbrev-ref HEAD)" + + - name: Get build platform + id: platform + run: echo "::set-output name=platform::$(go version | cut -d ' ' -f 4)" + + - name: Get Go version + id: go + run: echo "::set-output name=go::$(go version | cut -d ' ' -f 3)" + + - name: Run GoReleaser (release) + if: inputs.publish + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + with: + version: v2.12.7 + args: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_DATE: ${{ steps.date.outputs.date }} + BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} + GIT_BRANCH: ${{ steps.branch.outputs.branch }} + BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} + GO_VERSION: ${{ steps.go.outputs.go }} + + - name: Run GoReleaser (snapshot) + if: ${{ !inputs.publish }} + uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + with: + version: v2.12.7 + args: release --snapshot --clean + env: + BUILD_DATE: ${{ steps.date.outputs.date }} + BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} + GIT_BRANCH: ${{ steps.branch.outputs.branch }} + BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} + GO_VERSION: ${{ steps.go.outputs.go }} + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + if: inputs.publish && github.repository_owner == 'temporalio' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Get build metadata + id: meta + run: | + echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT + echo "image_sha_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT + echo "image_branch_tag=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT + + if [[ "${{ inputs.publish }}" == "true" ]]; then + # Get version from input, strip 'v' prefix + VERSION="${{ inputs.version }}" + VERSION="${VERSION#v}" + echo "version=$VERSION" >> $GITHUB_OUTPUT + else + echo "version=snapshot" >> $GITHUB_OUTPUT + fi + + # Determine image repo based on repository owner + if [[ "${{ github.repository_owner }}" == "temporalio" ]]; then + echo "image_repo=temporalio" >> $GITHUB_OUTPUT + else + echo "image_repo=${{ github.repository_owner }}" >> $GITHUB_OUTPUT + fi + + - name: Check if release is latest + if: inputs.publish + id: check_latest + uses: actions/github-script@v7 + with: + script: | + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: context.ref.replace('refs/tags/', '') + }); + // Tag as latest only if release is marked as latest (not pre-release) + core.setOutput('tag_latest', release.prerelease ? 'false' : 'true'); + console.log(`Release prerelease: ${release.prerelease}, tag_latest: ${!release.prerelease}`) + + - name: Build and push Docker image + if: inputs.publish && github.repository_owner == 'temporalio' + run: | + docker buildx bake \ + --file .github/docker/docker-bake.hcl \ + --push \ + cli + env: + CLI_SHA: ${{ steps.meta.outputs.cli_sha }} + IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} + IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} + VERSION: ${{ steps.meta.outputs.version }} + TAG_LATEST: ${{ steps.check_latest.outputs.tag_latest }} + IMAGE_REPO: temporalio + + - name: Build Docker image + if: ${{ !inputs.publish }} + run: | + docker buildx bake \ + --file .github/docker/docker-bake.hcl \ + cli + env: + CLI_SHA: ${{ steps.meta.outputs.cli_sha }} + IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} + IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} + VERSION: ${{ steps.meta.outputs.version }} + TAG_LATEST: false + IMAGE_REPO: temporalio diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml new file mode 100644 index 000000000..0a5ea7c3b --- /dev/null +++ b/.github/workflows/build-docker-image.yml @@ -0,0 +1,16 @@ +name: Build Docker Image + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + build: + uses: ./.github/workflows/build-and-publish.yml + with: + publish: false diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index dd1c43055..dfd79cedd 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -6,105 +6,15 @@ on: types: - published +permissions: + contents: write + jobs: release: - name: Build and Publish Release - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 - with: - go-version-file: "go.mod" - check-latest: true - - - name: Get build date - id: date - run: echo "::set-output name=date::$(date '+%F-%T')" - - - name: Get build unix timestamp - id: timestamp - run: echo "::set-output name=timestamp::$(date '+%s')" - - - name: Get git branch - id: branch - run: echo "::set-output name=branch::$(git rev-parse --abbrev-ref HEAD)" - - - name: Get build platform - id: platform - run: echo "::set-output name=platform::$(go version | cut -d ' ' -f 4)" - - - name: Get Go version - id: go - run: echo "::set-output name=go::$(go version | cut -d ' ' -f 3)" - - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 - with: - version: v2.12.7 - args: release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_DATE: ${{ steps.date.outputs.date }} - BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} - GIT_BRANCH: ${{ steps.branch.outputs.branch }} - BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} - GO_VERSION: ${{ steps.go.outputs.go }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Get build metadata - id: meta - run: | - echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "image_sha_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - echo "image_branch_tag=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT - # Get version from tag, strip 'v' prefix - VERSION="${GITHUB_REF#refs/tags/v}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - # Determine image repo based on repository owner - if [[ "${{ github.repository_owner }}" == "temporalio" ]]; then - echo "image_repo=temporalio" >> $GITHUB_OUTPUT - else - echo "image_repo=${{ github.repository_owner }}" >> $GITHUB_OUTPUT - fi - - - name: Check if release is latest - id: check_latest - uses: actions/github-script@v7 - with: - script: | - const { data: release } = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: context.ref.replace('refs/tags/', '') - }); - // Tag as latest only if release is marked as latest (not pre-release) - core.setOutput('tag_latest', release.prerelease ? 'false' : 'true'); - console.log(`Release prerelease: ${release.prerelease}, tag_latest: ${!release.prerelease}`) - - - name: Build and push Docker images - if: github.repository_owner == 'temporalio' - run: | - docker buildx bake \ - --file .github/docker/docker-bake.hcl \ - --push \ - cli - env: - CLI_SHA: ${{ steps.meta.outputs.cli_sha }} - IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} - IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} - VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest.outputs.tag_latest }} - IMAGE_REPO: temporalio + uses: ./.github/workflows/build-and-publish.yml + with: + publish: true + version: ${{ github.ref_name }} + secrets: + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} From 4667b5c2cbc41c99068e9a5f24722704eb194e0b Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:39:00 -0600 Subject: [PATCH 05/31] Fix semgrep issues and wf permission --- .github/workflows/build-and-publish.yml | 16 ++++++++++------ .github/workflows/build-docker-image.yml | 5 ++--- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 310c270e4..11f8adf28 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -4,11 +4,11 @@ on: workflow_call: inputs: publish: - description: 'Whether to publish the release and Docker image' + description: "Whether to publish the release and Docker image" required: true type: boolean version: - description: 'Version tag for the release (required if publish is true)' + description: "Version tag for the release (required if publish is true)" required: false type: string secrets: @@ -95,14 +95,18 @@ jobs: - name: Get build metadata id: meta + env: + INPUT_VERSION: ${{ inputs.version }} + INPUT_PUBLISH: ${{ inputs.publish }} + REPO_OWNER: ${{ github.repository_owner }} run: | echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT echo "image_sha_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT echo "image_branch_tag=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT - if [[ "${{ inputs.publish }}" == "true" ]]; then + if [[ "$INPUT_PUBLISH" == "true" ]]; then # Get version from input, strip 'v' prefix - VERSION="${{ inputs.version }}" + VERSION="$INPUT_VERSION" VERSION="${VERSION#v}" echo "version=$VERSION" >> $GITHUB_OUTPUT else @@ -110,10 +114,10 @@ jobs: fi # Determine image repo based on repository owner - if [[ "${{ github.repository_owner }}" == "temporalio" ]]; then + if [[ "$REPO_OWNER" == "temporalio" ]]; then echo "image_repo=temporalio" >> $GITHUB_OUTPUT else - echo "image_repo=${{ github.repository_owner }}" >> $GITHUB_OUTPUT + echo "image_repo=$REPO_OWNER" >> $GITHUB_OUTPUT fi - name: Check if release is latest diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml index 0a5ea7c3b..baa33d148 100644 --- a/.github/workflows/build-docker-image.yml +++ b/.github/workflows/build-docker-image.yml @@ -6,11 +6,10 @@ on: branches: - main -permissions: - contents: read - jobs: build: + permissions: + contents: read uses: ./.github/workflows/build-and-publish.yml with: publish: false From 15163e457cfda4cd3e6e0396f51873916b61cd3b Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:40:40 -0600 Subject: [PATCH 06/31] remove write perm from re-usable wf --- .github/workflows/build-and-publish.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 11f8adf28..b2b3c098c 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -17,9 +17,6 @@ on: DOCKER_PASSWORD: required: false -permissions: - contents: write - jobs: build: name: Build and Publish From 062c9b725643761daa2c62bc6949d8a02d37342f Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:46:02 -0600 Subject: [PATCH 07/31] update go releaser to use formats instead of format and gh wf to stop using deprecated stuff. --- .github/workflows/build-and-publish.yml | 10 +++++----- .goreleaser.yml | 12 +++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index b2b3c098c..c95f1d323 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -35,23 +35,23 @@ jobs: - name: Get build date id: date - run: echo "::set-output name=date::$(date '+%F-%T')" + run: echo "date=$(date '+%F-%T')" >> $GITHUB_OUTPUT - name: Get build unix timestamp id: timestamp - run: echo "::set-output name=timestamp::$(date '+%s')" + run: echo "timestamp=$(date '+%s')" >> $GITHUB_OUTPUT - name: Get git branch id: branch - run: echo "::set-output name=branch::$(git rev-parse --abbrev-ref HEAD)" + run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT - name: Get build platform id: platform - run: echo "::set-output name=platform::$(go version | cut -d ' ' -f 4)" + run: echo "platform=$(go version | cut -d ' ' -f 4)" >> $GITHUB_OUTPUT - name: Get Go version id: go - run: echo "::set-output name=go::$(go version | cut -d ' ' -f 3)" + run: echo "go=$(go version | cut -d ' ' -f 3)" >> $GITHUB_OUTPUT - name: Run GoReleaser (release) if: inputs.publish diff --git a/.goreleaser.yml b/.goreleaser.yml index f751b2f02..f3e29ced2 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -13,24 +13,26 @@ archives: - <<: &archive_defaults name_template: "temporal_cli_{{ .Version }}_{{ .Os }}_{{ .Arch }}" id: nix - builds: + ids: - nix - format: tar.gz + formats: + - tar.gz files: - LICENSE - <<: *archive_defaults id: windows-zip - builds: + ids: - windows - format: zip + formats: + - zip files: - LICENSE # used by SDKs as zip cannot be used by rust https://github.com/zip-rs/zip/issues/108 - <<: *archive_defaults id: windows-targz - builds: + ids: - windows files: - LICENSE From 215501244d1685a9a7a1225feba6073dc9d8f77f Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Thu, 20 Nov 2025 16:57:39 -0600 Subject: [PATCH 08/31] turn on caching and upload build artifacts. --- .github/workflows/build-and-publish.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index c95f1d323..d60fcc37a 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -32,6 +32,7 @@ jobs: with: go-version-file: "go.mod" check-latest: true + cache: true - name: Get build date id: date @@ -160,3 +161,11 @@ jobs: VERSION: ${{ steps.meta.outputs.version }} TAG_LATEST: false IMAGE_REPO: temporalio + + - name: Upload build artifacts + if: ${{ !inputs.publish }} + uses: actions/upload-artifact@v4 + with: + name: temporal-cli-dist + path: dist/ + retention-days: 7 From 5ecf25afb14840278ed2c2266f0779d45bc98466 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 06:37:08 -0600 Subject: [PATCH 09/31] remove makefile that is not longer needed --- .github/docker/Makefile | 70 ----------------------------------------- 1 file changed, 70 deletions(-) delete mode 100644 .github/docker/Makefile diff --git a/.github/docker/Makefile b/.github/docker/Makefile deleted file mode 100644 index ead30199d..000000000 --- a/.github/docker/Makefile +++ /dev/null @@ -1,70 +0,0 @@ -# Detect repository owner from git remote -REPO_OWNER := $(shell git remote get-url origin | sed -E 's|.*[:/]([^/]+)/[^/]+\.git|\1|' | tr '[:upper:]' '[:lower:]') - -# For temporalio org, default to temporalio. For others, require IMAGE_REPO to be set -ifeq ($(REPO_OWNER),temporalio) - IMAGE_REPO ?= temporalio -else - ifndef IMAGE_REPO - $(error IMAGE_REPO must be set for non-temporalio repositories. Usage: make build IMAGE_REPO=your-dockerhub-username) - endif -endif - -CLI_SHA := $(shell git rev-parse HEAD) -IMAGE_SHA_TAG := $(shell git rev-parse --short HEAD) -IMAGE_BRANCH_TAG := $(shell git rev-parse --abbrev-ref HEAD) - -BAKE_ENV := CLI_SHA=$(CLI_SHA) \ - IMAGE_REPO=$(IMAGE_REPO) \ - IMAGE_SHA_TAG=$(IMAGE_SHA_TAG) \ - IMAGE_BRANCH_TAG=$(IMAGE_BRANCH_TAG) - -.PHONY: help -help: ## Display this help message - @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf " %-20s %s\n", $$1, $$2}' - -.PHONY: build -build: ## Build Docker images (multi-arch) - @echo "Building binaries with goreleaser..." - cd ../.. && goreleaser release --snapshot --clean --skip=publish - @echo "Building Docker images (multi-arch)..." - cd ../.. && $(BAKE_ENV) docker buildx bake --file .github/docker/docker-bake.hcl cli - @echo "" - @echo "Success! Built multi-arch images" - @echo "Note: Multi-arch images are not loaded into Docker. Use 'make build-local' to build and test locally." - -.PHONY: build-local -build-local: ## Build and load Docker image for local testing - @echo "Building binaries with goreleaser..." - cd ../.. && goreleaser release --snapshot --clean --skip=publish - @echo "Building Docker image for local platform..." - cd ../.. && $(BAKE_ENV) docker buildx bake --file .github/docker/docker-bake.hcl --set cli.platforms=linux/amd64 --load cli - @echo "Testing Docker image..." - docker run --rm $(IMAGE_REPO)/temporal:$(IMAGE_SHA_TAG) --version - @echo "" - @echo "Success! Image: $(IMAGE_REPO)/temporal:$(IMAGE_SHA_TAG)" - -.PHONY: clean -clean: ## Clean build artifacts - rm -rf ../../dist - -.PHONY: update-alpine -update-alpine: ## Update Alpine base image to latest version and digest (usage: make update-alpine [ALPINE_TAG=3.22]) - @if [ -n "$(ALPINE_TAG)" ]; then \ - LATEST_TAG=$(ALPINE_TAG); \ - else \ - echo "Fetching latest Alpine version from Docker Hub..."; \ - LATEST_TAG=$$(curl -s https://registry.hub.docker.com/v2/repositories/library/alpine/tags\?page_size=100 | \ - jq -r '.results[].name' | grep -E '^3\.[0-9]+$$' | sort -V | tail -1); \ - fi && \ - echo "Alpine version: $$LATEST_TAG" && \ - DIGEST=$$(docker buildx imagetools inspect alpine:$$LATEST_TAG 2>/dev/null | grep "Digest:" | head -1 | awk '{print $$2}') && \ - DIGEST_HASH=$${DIGEST#sha256:} && \ - echo "Digest: sha256:$$DIGEST_HASH" && \ - ALPINE_FULL="alpine:$$LATEST_TAG@sha256:$$DIGEST_HASH" && \ - if sed --version 2>&1 | grep -q GNU; then \ - sed -i "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" docker-bake.hcl; \ - else \ - sed -i '' "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" docker-bake.hcl; \ - fi && \ - echo "Updated docker-bake.hcl with $$ALPINE_FULL" From 8918ee7ba3b756e090b7bdbce175b7e0aee84385 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 06:46:44 -0600 Subject: [PATCH 10/31] correct alpine update command --- Makefile | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index d3795361e..71de3c2d1 100644 --- a/Makefile +++ b/Makefile @@ -10,5 +10,22 @@ internal/commands.gen.go: internal/commandsgen/commands.yml build: go build ./cmd/temporal -update-alpine: - $(MAKE) -C .github/docker update-alpine +update-alpine: ## Update Alpine base image to latest version and digest (usage: make update-alpine [ALPINE_TAG=3.22]) + @if [ -n "$(ALPINE_TAG)" ]; then \ + LATEST_TAG=$(ALPINE_TAG); \ + else \ + echo "Fetching latest Alpine version from Docker Hub..."; \ + LATEST_TAG=$$(curl -s https://registry.hub.docker.com/v2/repositories/library/alpine/tags\?page_size=100 | \ + jq -r '.results[].name' | grep -E '^3\.[0-9]+$$' | sort -V | tail -1); \ + fi && \ + echo "Alpine version: $$LATEST_TAG" && \ + DIGEST=$$(docker buildx imagetools inspect alpine:$$LATEST_TAG 2>/dev/null | grep "Digest:" | head -1 | awk '{print $$2}') && \ + DIGEST_HASH=$${DIGEST#sha256:} && \ + echo "Digest: sha256:$$DIGEST_HASH" && \ + ALPINE_FULL="alpine:$$LATEST_TAG@sha256:$$DIGEST_HASH" && \ + if sed --version 2>&1 | grep -q GNU; then \ + sed -i "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" .github/docker/docker-bake.hcl; \ + else \ + sed -i '' "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" .github/docker/docker-bake.hcl; \ + fi && \ + echo "Updated .github/docker/docker-bake.hcl with $$ALPINE_FULL" From f6d0470f1156ff0d1233af6877c8ff450d3ef076 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 11:27:09 -0600 Subject: [PATCH 11/31] Modernize Docker build with parameterization and registry flexibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Major improvements: - Add full parameterization (registry, namespace, image name) - Auto-detect registry: temporalio → docker.io, others → ghcr.io - Separate workflow for managing 'latest' tag on release events - Dynamic Docker labels using GITHUB_REPOSITORY variable - Add packages:write permission for GHCR - Remove artifact uploading (no longer needed) Benefits: - Works out-of-box for both upstream and forks - Flexible registry support (Docker Hub, GHCR, any registry) - Clean separation of release vs latest-tag concerns - Proper package association in GitHub Configuration requirements: - DOCKER_USERNAME and DOCKER_PASSWORD secrets needed for Docker Hub - GITHUB_TOKEN automatically provides GHCR access --- .github/docker/docker-bake.hcl | 24 +++-- .github/workflows/build-and-publish.yml | 115 ++++++++++++++++------- .github/workflows/goreleaser.yml | 1 + .github/workflows/update-latest-tag.yml | 120 ++++++++++++++++++++++++ 4 files changed, 218 insertions(+), 42 deletions(-) create mode 100644 .github/workflows/update-latest-tag.yml diff --git a/.github/docker/docker-bake.hcl b/.github/docker/docker-bake.hcl index e1818912b..0f158496c 100644 --- a/.github/docker/docker-bake.hcl +++ b/.github/docker/docker-bake.hcl @@ -1,5 +1,17 @@ variable "IMAGE_REPO" { - default = "temporalio" + default = "ghcr.io" +} + +variable "IMAGE_NAMESPACE" { + default = "" +} + +variable "IMAGE_NAME" { + default = "temporal" +} + +variable "GITHUB_REPOSITORY" { + default = "temporalio/cli" } variable "IMAGE_SHA_TAG" {} @@ -27,9 +39,9 @@ target "cli" { dockerfile = ".github/docker/cli.Dockerfile" context = "." tags = compact([ - "${IMAGE_REPO}/temporal:${IMAGE_SHA_TAG}", - "${IMAGE_REPO}/temporal:${VERSION}", - TAG_LATEST ? "${IMAGE_REPO}/temporal:latest" : "", + IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", + IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}", + TAG_LATEST ? (IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest") : "", ]) platforms = ["linux/amd64", "linux/arm64"] args = { @@ -38,8 +50,8 @@ target "cli" { labels = { "org.opencontainers.image.title" = "temporal" "org.opencontainers.image.description" = "Temporal CLI" - "org.opencontainers.image.url" = "https://github.com/temporalio/cli" - "org.opencontainers.image.source" = "https://github.com/temporalio/cli" + "org.opencontainers.image.url" = "https://github.com/${GITHUB_REPOSITORY}" + "org.opencontainers.image.source" = "https://github.com/${GITHUB_REPOSITORY}" "org.opencontainers.image.licenses" = "MIT" "org.opencontainers.image.revision" = "${CLI_SHA}" "org.opencontainers.image.created" = timestamp() diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index d60fcc37a..9cc7b646a 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -11,6 +11,21 @@ on: description: "Version tag for the release (required if publish is true)" required: false type: string + registry: + description: "Container registry (docker.io, ghcr.io, etc.)" + required: false + type: string + default: "" + registry_namespace: + description: "Registry namespace/organization" + required: false + type: string + default: "" + image_name: + description: "Image name" + required: false + type: string + default: "temporal" secrets: DOCKER_USERNAME: required: false @@ -84,18 +99,14 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - - name: Log in to Docker Hub - if: inputs.publish && github.repository_owner == 'temporalio' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Get build metadata id: meta env: INPUT_VERSION: ${{ inputs.version }} INPUT_PUBLISH: ${{ inputs.publish }} + INPUT_REGISTRY: ${{ inputs.registry }} + INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} + INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} run: | echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT @@ -111,30 +122,64 @@ jobs: echo "version=snapshot" >> $GITHUB_OUTPUT fi - # Determine image repo based on repository owner - if [[ "$REPO_OWNER" == "temporalio" ]]; then - echo "image_repo=temporalio" >> $GITHUB_OUTPUT + # Determine registry (with auto-detection for temporalio vs forks) + REGISTRY="$INPUT_REGISTRY" + if [[ -z "$REGISTRY" ]]; then + if [[ "$REPO_OWNER" == "temporalio" ]]; then + REGISTRY="docker.io" + else + REGISTRY="ghcr.io" + fi + fi + + # Determine registry type for authentication + if [[ "$REGISTRY" == "ghcr.io" ]]; then + echo "registry_type=ghcr" >> $GITHUB_OUTPUT + elif [[ "$REGISTRY" == "docker.io" ]]; then + echo "registry_type=dockerhub" >> $GITHUB_OUTPUT else - echo "image_repo=$REPO_OWNER" >> $GITHUB_OUTPUT + echo "registry_type=other" >> $GITHUB_OUTPUT fi - - name: Check if release is latest - if: inputs.publish - id: check_latest - uses: actions/github-script@v7 + # Set namespace (defaults to repository owner) + NAMESPACE="$INPUT_REGISTRY_NAMESPACE" + if [[ -z "$NAMESPACE" ]]; then + NAMESPACE="$REPO_OWNER" + fi + + # Set image name (defaults to 'temporal') + IMAGE_NAME="$INPUT_IMAGE_NAME" + if [[ -z "$IMAGE_NAME" ]]; then + IMAGE_NAME="temporal" + fi + + # For Docker Hub, use empty string as registry (special case) + if [[ "$REGISTRY" == "docker.io" ]]; then + echo "image_repo=" >> $GITHUB_OUTPUT + else + echo "image_repo=${REGISTRY}" >> $GITHUB_OUTPUT + fi + + echo "image_namespace=${NAMESPACE}" >> $GITHUB_OUTPUT + echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + if: inputs.publish && steps.meta.outputs.registry_type == 'ghcr' + uses: docker/login-action@v3 with: - script: | - const { data: release } = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: context.ref.replace('refs/tags/', '') - }); - // Tag as latest only if release is marked as latest (not pre-release) - core.setOutput('tag_latest', release.prerelease ? 'false' : 'true'); - console.log(`Release prerelease: ${release.prerelease}, tag_latest: ${!release.prerelease}`) + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: inputs.publish && steps.meta.outputs.registry_type == 'dockerhub' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image - if: inputs.publish && github.repository_owner == 'temporalio' + if: inputs.publish run: | docker buildx bake \ --file .github/docker/docker-bake.hcl \ @@ -145,8 +190,11 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest.outputs.tag_latest }} - IMAGE_REPO: temporalio + TAG_LATEST: false + IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} + IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} + IMAGE_NAME: ${{ steps.meta.outputs.image_name }} + GITHUB_REPOSITORY: ${{ github.repository }} - name: Build Docker image if: ${{ !inputs.publish }} @@ -160,12 +208,7 @@ jobs: IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} TAG_LATEST: false - IMAGE_REPO: temporalio - - - name: Upload build artifacts - if: ${{ !inputs.publish }} - uses: actions/upload-artifact@v4 - with: - name: temporal-cli-dist - path: dist/ - retention-days: 7 + IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} + IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} + IMAGE_NAME: ${{ steps.meta.outputs.image_name }} + GITHUB_REPOSITORY: ${{ github.repository }} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/goreleaser.yml index dfd79cedd..100129438 100644 --- a/.github/workflows/goreleaser.yml +++ b/.github/workflows/goreleaser.yml @@ -8,6 +8,7 @@ on: permissions: contents: write + packages: write jobs: release: diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml new file mode 100644 index 000000000..d8a73cba2 --- /dev/null +++ b/.github/workflows/update-latest-tag.yml @@ -0,0 +1,120 @@ +name: Update Latest Docker Tag + +on: + release: + types: + - edited + - released + +permissions: + contents: read + packages: write + +jobs: + update-latest: + name: Update Latest Tag + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + with: + ref: ${{ github.event.release.tag_name }} + + - name: Check if release is latest + id: check_latest + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + uses: actions/github-script@v7 + with: + script: | + const releaseTag = process.env.RELEASE_TAG; + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: releaseTag + }); + + const isLatest = !release.prerelease && !release.draft; + core.setOutput('is_latest', isLatest); + console.log(`Release: ${release.tag_name}`); + console.log(`Prerelease: ${release.prerelease}, Draft: ${release.draft}`); + console.log(`Should tag as latest: ${isLatest}`); + + - name: Set up Docker Buildx + if: steps.check_latest.outputs.is_latest == 'true' + uses: docker/setup-buildx-action@v3 + + - name: Get registry configuration + if: steps.check_latest.outputs.is_latest == 'true' + id: registry + run: | + REPO_OWNER="${{ github.repository_owner }}" + + # Auto-detect registry based on repository owner + if [[ "$REPO_OWNER" == "temporalio" ]]; then + REGISTRY="docker.io" + echo "type=dockerhub" >> $GITHUB_OUTPUT + echo "repo=" >> $GITHUB_OUTPUT + else + REGISTRY="ghcr.io" + echo "type=ghcr" >> $GITHUB_OUTPUT + echo "repo=${REGISTRY}" >> $GITHUB_OUTPUT + fi + + echo "namespace=${REPO_OWNER}" >> $GITHUB_OUTPUT + echo "image=temporal" >> $GITHUB_OUTPUT + + - name: Log in to GitHub Container Registry + if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'ghcr' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Log in to Docker Hub + if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'dockerhub' + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Get version tag + if: steps.check_latest.outputs.is_latest == 'true' + id: version + env: + RELEASE_TAG: ${{ github.event.release.tag_name }} + run: | + VERSION="$RELEASE_TAG" + VERSION="${VERSION#v}" + echo "version=${VERSION}" >> $GITHUB_OUTPUT + + - name: Pull and retag image as latest + if: steps.check_latest.outputs.is_latest == 'true' + run: | + # Construct image paths + REPO="${{ steps.registry.outputs.repo }}" + NAMESPACE="${{ steps.registry.outputs.namespace }}" + IMAGE="${{ steps.registry.outputs.image }}" + VERSION="${{ steps.version.outputs.version }}" + + if [[ -z "$REPO" ]]; then + # Docker Hub format + SOURCE_IMAGE="${NAMESPACE}/${IMAGE}:${VERSION}" + LATEST_IMAGE="${NAMESPACE}/${IMAGE}:latest" + else + # Other registries + SOURCE_IMAGE="${REPO}/${NAMESPACE}/${IMAGE}:${VERSION}" + LATEST_IMAGE="${REPO}/${NAMESPACE}/${IMAGE}:latest" + fi + + echo "Pulling ${SOURCE_IMAGE}..." + docker pull ${SOURCE_IMAGE} + + echo "Tagging as ${LATEST_IMAGE}..." + docker tag ${SOURCE_IMAGE} ${LATEST_IMAGE} + + echo "Pushing ${LATEST_IMAGE}..." + docker push ${LATEST_IMAGE} + + echo "✅ Successfully updated latest tag to point to version ${VERSION}" From 363f893470355b718d9cf9576fb5bd69b664e37a Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:19:19 -0600 Subject: [PATCH 12/31] Address PR feedback: simplify Docker build and fix workflow issues --- .github/docker/.gitignore | 1 - .github/docker/cli.Dockerfile | 34 ---- .github/workflows/build-and-publish.yml | 151 +++++++++++------- .github/workflows/trigger-docs.yml | 1 + .github/workflows/update-latest-tag.yml | 41 +++-- Dockerfile | 17 ++ Makefile | 31 ---- .../docker/docker-bake.hcl => docker-bake.hcl | 10 +- 8 files changed, 134 insertions(+), 152 deletions(-) delete mode 100644 .github/docker/.gitignore delete mode 100644 .github/docker/cli.Dockerfile create mode 100644 Dockerfile delete mode 100644 Makefile rename .github/docker/docker-bake.hcl => docker-bake.hcl (82%) diff --git a/.github/docker/.gitignore b/.github/docker/.gitignore deleted file mode 100644 index 567609b12..000000000 --- a/.github/docker/.gitignore +++ /dev/null @@ -1 +0,0 @@ -build/ diff --git a/.github/docker/cli.Dockerfile b/.github/docker/cli.Dockerfile deleted file mode 100644 index d64700de0..000000000 --- a/.github/docker/cli.Dockerfile +++ /dev/null @@ -1,34 +0,0 @@ -# syntax=docker/dockerfile:1 - -ARG ALPINE_IMAGE -ARG BUILDARCH - -# Build stage - copy binaries from goreleaser output -FROM --platform=$BUILDARCH scratch AS dist -COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal -COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal - -# Stage to extract CA certificates and create user files -FROM ${ALPINE_IMAGE} AS certs -RUN apk add --no-cache ca-certificates && \ - adduser -u 1000 -D temporal - -# Final stage - minimal scratch-based image -FROM scratch - -ARG TARGETARCH - -# Copy CA certificates from certs stage -COPY --from=certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ - -# Copy passwd and group files for non-root user -COPY --from=certs /etc/passwd /etc/passwd -COPY --from=certs /etc/group /etc/group - -# Copy the appropriate binary for target architecture -COPY --from=dist /dist/$TARGETARCH/temporal /temporal - -# Run as non-root user temporal (uid 1000) -USER 1000:1000 - -ENTRYPOINT ["/temporal"] diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 9cc7b646a..bf0eea9e8 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -38,12 +38,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@v3 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # v4.0.1 + uses: actions/setup-go@v4 with: go-version-file: "go.mod" check-latest: true @@ -69,9 +69,28 @@ jobs: id: go run: echo "go=$(go version | cut -d ' ' -f 3)" >> $GITHUB_OUTPUT + - name: Check if release is latest + if: inputs.publish + id: check_latest_release + uses: actions/github-script@v7 + with: + script: | + const releaseTag = '${{ inputs.version }}'; + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: releaseTag + }); + + const isLatest = !release.prerelease && !release.draft; + core.setOutput('is_latest', isLatest); + console.log(`Release: ${release.tag_name}`); + console.log(`Prerelease: ${release.prerelease}, Draft: ${release.draft}`); + console.log(`Should tag as latest: ${isLatest}`); + - name: Run GoReleaser (release) if: inputs.publish - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + uses: goreleaser/goreleaser-action@v6.4.0 with: version: v2.12.7 args: release @@ -85,7 +104,7 @@ jobs: - name: Run GoReleaser (snapshot) if: ${{ !inputs.publish }} - uses: goreleaser/goreleaser-action@e435ccd777264be153ace6237001ef4d979d3a7a # v6.4.0 + uses: goreleaser/goreleaser-action@v6.4.0 with: version: v2.12.7 args: release --snapshot --clean @@ -108,60 +127,70 @@ jobs: INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} - run: | - echo "cli_sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT - echo "image_sha_tag=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT - echo "image_branch_tag=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT - - if [[ "$INPUT_PUBLISH" == "true" ]]; then - # Get version from input, strip 'v' prefix - VERSION="$INPUT_VERSION" - VERSION="${VERSION#v}" - echo "version=$VERSION" >> $GITHUB_OUTPUT - else - echo "version=snapshot" >> $GITHUB_OUTPUT - fi - - # Determine registry (with auto-detection for temporalio vs forks) - REGISTRY="$INPUT_REGISTRY" - if [[ -z "$REGISTRY" ]]; then - if [[ "$REPO_OWNER" == "temporalio" ]]; then - REGISTRY="docker.io" - else - REGISTRY="ghcr.io" - fi - fi - - # Determine registry type for authentication - if [[ "$REGISTRY" == "ghcr.io" ]]; then - echo "registry_type=ghcr" >> $GITHUB_OUTPUT - elif [[ "$REGISTRY" == "docker.io" ]]; then - echo "registry_type=dockerhub" >> $GITHUB_OUTPUT - else - echo "registry_type=other" >> $GITHUB_OUTPUT - fi - - # Set namespace (defaults to repository owner) - NAMESPACE="$INPUT_REGISTRY_NAMESPACE" - if [[ -z "$NAMESPACE" ]]; then - NAMESPACE="$REPO_OWNER" - fi - - # Set image name (defaults to 'temporal') - IMAGE_NAME="$INPUT_IMAGE_NAME" - if [[ -z "$IMAGE_NAME" ]]; then - IMAGE_NAME="temporal" - fi - - # For Docker Hub, use empty string as registry (special case) - if [[ "$REGISTRY" == "docker.io" ]]; then - echo "image_repo=" >> $GITHUB_OUTPUT - else - echo "image_repo=${REGISTRY}" >> $GITHUB_OUTPUT - fi - - echo "image_namespace=${NAMESPACE}" >> $GITHUB_OUTPUT - echo "image_name=${IMAGE_NAME}" >> $GITHUB_OUTPUT + uses: actions/github-script@v7 + with: + script: | + const inputVersion = process.env.INPUT_VERSION; + const inputPublish = process.env.INPUT_PUBLISH; + const inputRegistry = process.env.INPUT_REGISTRY; + const inputRegistryNamespace = process.env.INPUT_REGISTRY_NAMESPACE; + const inputImageName = process.env.INPUT_IMAGE_NAME; + const repoOwner = process.env.REPO_OWNER; + + // Get git information + const { execSync } = require('child_process'); + const cliSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); + const imageShaTag = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); + const imageBranchTag = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim(); + + core.setOutput('cli_sha', cliSha); + core.setOutput('image_sha_tag', imageShaTag); + core.setOutput('image_branch_tag', imageBranchTag); + + // Determine version + let version; + if (inputPublish === 'true') { + // Get version from input, strip 'v' prefix + version = inputVersion.startsWith('v') ? inputVersion.slice(1) : inputVersion; + } else { + version = 'snapshot'; + } + core.setOutput('version', version); + + // Determine registry (with auto-detection for temporalio vs forks) + let registry = inputRegistry; + if (!registry) { + if (repoOwner === 'temporalio') { + registry = 'docker.io'; + } else { + registry = 'ghcr.io'; + } + } + + // Determine registry type for authentication + let registryType; + if (registry === 'ghcr.io') { + registryType = 'ghcr'; + } else if (registry === 'docker.io') { + registryType = 'dockerhub'; + } else { + registryType = 'other'; + } + core.setOutput('registry_type', registryType); + + // Set namespace (defaults to repository owner) + const namespace = inputRegistryNamespace || repoOwner; + core.setOutput('image_namespace', namespace); + + // Set image name (defaults to 'temporal') + const imageName = inputImageName || 'temporal'; + core.setOutput('image_name', imageName); + + // For Docker Hub, use empty string as registry (special case) + const imageRepo = registry === 'docker.io' ? '' : registry; + core.setOutput('image_repo', imageRepo); + + console.log(`Registry: ${registry}, Type: ${registryType}, Namespace: ${namespace}, Image: ${imageName}`); - name: Log in to GitHub Container Registry if: inputs.publish && steps.meta.outputs.registry_type == 'ghcr' @@ -182,7 +211,7 @@ jobs: if: inputs.publish run: | docker buildx bake \ - --file .github/docker/docker-bake.hcl \ + --file docker-bake.hcl \ --push \ cli env: @@ -190,7 +219,7 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: false + TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} @@ -200,7 +229,7 @@ jobs: if: ${{ !inputs.publish }} run: | docker buildx bake \ - --file .github/docker/docker-bake.hcl \ + --file docker-bake.hcl \ cli env: CLI_SHA: ${{ steps.meta.outputs.cli_sha }} diff --git a/.github/workflows/trigger-docs.yml b/.github/workflows/trigger-docs.yml index 645d976b5..b39848cb3 100644 --- a/.github/workflows/trigger-docs.yml +++ b/.github/workflows/trigger-docs.yml @@ -5,6 +5,7 @@ on: types: [published] jobs: update: + if: github.repository == 'temporalio/cli' runs-on: ubuntu-latest defaults: run: diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index d8a73cba2..b226ee55e 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3.5.3 + uses: actions/checkout@v3 with: ref: ${{ github.event.release.tag_name }} @@ -47,22 +47,29 @@ jobs: - name: Get registry configuration if: steps.check_latest.outputs.is_latest == 'true' id: registry - run: | - REPO_OWNER="${{ github.repository_owner }}" - - # Auto-detect registry based on repository owner - if [[ "$REPO_OWNER" == "temporalio" ]]; then - REGISTRY="docker.io" - echo "type=dockerhub" >> $GITHUB_OUTPUT - echo "repo=" >> $GITHUB_OUTPUT - else - REGISTRY="ghcr.io" - echo "type=ghcr" >> $GITHUB_OUTPUT - echo "repo=${REGISTRY}" >> $GITHUB_OUTPUT - fi - - echo "namespace=${REPO_OWNER}" >> $GITHUB_OUTPUT - echo "image=temporal" >> $GITHUB_OUTPUT + uses: actions/github-script@v7 + with: + script: | + const repoOwner = context.repo.owner; + + // Auto-detect registry based on repository owner + let registry, type, repo; + if (repoOwner === 'temporalio') { + registry = 'docker.io'; + type = 'dockerhub'; + repo = ''; + } else { + registry = 'ghcr.io'; + type = 'ghcr'; + repo = registry; + } + + core.setOutput('type', type); + core.setOutput('repo', repo); + core.setOutput('namespace', repoOwner); + core.setOutput('image', 'temporal'); + + console.log(`Registry: ${registry}, Type: ${type}, Namespace: ${repoOwner}`); - name: Log in to GitHub Container Registry if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'ghcr' diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..23de4ed74 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +# Intermediate stage to normalize goreleaser output paths +# This copies both architecture binaries and renames them to clean paths, +# allowing the final stage to select the correct binary using TARGETARCH +FROM scratch AS dist +COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal +COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal + +FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 + +ARG TARGETARCH + +RUN apk add --no-cache ca-certificates +COPY --chmod=755 --from=dist /dist/${TARGETARCH}/temporal /usr/local/bin/temporal +RUN adduser -u 1000 -D temporal +USER temporal + +ENTRYPOINT ["temporal"] diff --git a/Makefile b/Makefile deleted file mode 100644 index 71de3c2d1..000000000 --- a/Makefile +++ /dev/null @@ -1,31 +0,0 @@ -.PHONY: all gen build fmt-imports update-alpine - -all: gen build - -gen: internal/commands.gen.go - -internal/commands.gen.go: internal/commandsgen/commands.yml - go run ./internal/cmd/gen-commands - -build: - go build ./cmd/temporal - -update-alpine: ## Update Alpine base image to latest version and digest (usage: make update-alpine [ALPINE_TAG=3.22]) - @if [ -n "$(ALPINE_TAG)" ]; then \ - LATEST_TAG=$(ALPINE_TAG); \ - else \ - echo "Fetching latest Alpine version from Docker Hub..."; \ - LATEST_TAG=$$(curl -s https://registry.hub.docker.com/v2/repositories/library/alpine/tags\?page_size=100 | \ - jq -r '.results[].name' | grep -E '^3\.[0-9]+$$' | sort -V | tail -1); \ - fi && \ - echo "Alpine version: $$LATEST_TAG" && \ - DIGEST=$$(docker buildx imagetools inspect alpine:$$LATEST_TAG 2>/dev/null | grep "Digest:" | head -1 | awk '{print $$2}') && \ - DIGEST_HASH=$${DIGEST#sha256:} && \ - echo "Digest: sha256:$$DIGEST_HASH" && \ - ALPINE_FULL="alpine:$$LATEST_TAG@sha256:$$DIGEST_HASH" && \ - if sed --version 2>&1 | grep -q GNU; then \ - sed -i "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" .github/docker/docker-bake.hcl; \ - else \ - sed -i '' "s|default = \"alpine:[^\"]*\"|default = \"$$ALPINE_FULL\"|" .github/docker/docker-bake.hcl; \ - fi && \ - echo "Updated .github/docker/docker-bake.hcl with $$ALPINE_FULL" diff --git a/.github/docker/docker-bake.hcl b/docker-bake.hcl similarity index 82% rename from .github/docker/docker-bake.hcl rename to docker-bake.hcl index 0f158496c..b4343e7ee 100644 --- a/.github/docker/docker-bake.hcl +++ b/docker-bake.hcl @@ -30,13 +30,10 @@ variable "TAG_LATEST" { default = false } -# Alpine base image with digest for reproducible builds -variable "ALPINE_IMAGE" { - default = "alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412" -} + target "cli" { - dockerfile = ".github/docker/cli.Dockerfile" + dockerfile = "Dockerfile" context = "." tags = compact([ IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", @@ -44,9 +41,6 @@ target "cli" { TAG_LATEST ? (IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest") : "", ]) platforms = ["linux/amd64", "linux/arm64"] - args = { - ALPINE_IMAGE = "${ALPINE_IMAGE}" - } labels = { "org.opencontainers.image.title" = "temporal" "org.opencontainers.image.description" = "Temporal CLI" From 41f7f4d2be42c834ac1f962ab65b6dc34a0eedae Mon Sep 17 00:00:00 2001 From: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:22:06 -0600 Subject: [PATCH 13/31] Update Dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 23de4ed74..fe2a4cb90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,7 @@ FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2 ARG TARGETARCH RUN apk add --no-cache ca-certificates -COPY --chmod=755 --from=dist /dist/${TARGETARCH}/temporal /usr/local/bin/temporal +COPY --from=dist /dist/${TARGETARCH}/temporal /usr/local/bin/temporal RUN adduser -u 1000 -D temporal USER temporal From 98cfaffdd97f6588f1a763b6d4771265106d3afd Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:24:15 -0600 Subject: [PATCH 14/31] Add explicit permissions to trigger-docs workflow --- .github/workflows/trigger-docs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/trigger-docs.yml b/.github/workflows/trigger-docs.yml index b39848cb3..5d455607f 100644 --- a/.github/workflows/trigger-docs.yml +++ b/.github/workflows/trigger-docs.yml @@ -3,6 +3,10 @@ on: workflow_dispatch: release: types: [published] + +permissions: + contents: read + jobs: update: if: github.repository == 'temporalio/cli' From 24bbfb840081fdf6596553169a89630f7a183545 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Fri, 21 Nov 2025 19:25:55 -0600 Subject: [PATCH 15/31] Restore Makefile from upstream --- Makefile | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 000000000..448ee5246 --- /dev/null +++ b/Makefile @@ -0,0 +1,11 @@ +.PHONY: all gen build fmt-imports + +all: gen build + +gen: internal/commands.gen.go + +internal/commands.gen.go: internal/commandsgen/commands.yml + go run ./internal/cmd/gen-commands + +build: + go build ./cmd/temporal From 5aa5d9d303633046e7bcbf4a84ef1e3c956b2d3a Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:44:37 -0600 Subject: [PATCH 16/31] remoe support for ghcr, simplify script, action version update --- .github/workflows/build-and-publish.yml | 72 +++++++------------------ .github/workflows/update-latest-tag.yml | 6 +-- docker-bake.hcl | 12 ++--- 3 files changed, 24 insertions(+), 66 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index bf0eea9e8..a723f410f 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -43,7 +43,7 @@ jobs: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v6 with: go-version-file: "go.mod" check-latest: true @@ -72,7 +72,7 @@ jobs: - name: Check if release is latest if: inputs.publish id: check_latest_release - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const releaseTag = '${{ inputs.version }}'; @@ -123,7 +123,6 @@ jobs: env: INPUT_VERSION: ${{ inputs.version }} INPUT_PUBLISH: ${{ inputs.publish }} - INPUT_REGISTRY: ${{ inputs.registry }} INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} @@ -141,11 +140,9 @@ jobs: const { execSync } = require('child_process'); const cliSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); const imageShaTag = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); - const imageBranchTag = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim(); core.setOutput('cli_sha', cliSha); core.setOutput('image_sha_tag', imageShaTag); - core.setOutput('image_branch_tag', imageBranchTag); // Determine version let version; @@ -159,24 +156,6 @@ jobs: // Determine registry (with auto-detection for temporalio vs forks) let registry = inputRegistry; - if (!registry) { - if (repoOwner === 'temporalio') { - registry = 'docker.io'; - } else { - registry = 'ghcr.io'; - } - } - - // Determine registry type for authentication - let registryType; - if (registry === 'ghcr.io') { - registryType = 'ghcr'; - } else if (registry === 'docker.io') { - registryType = 'dockerhub'; - } else { - registryType = 'other'; - } - core.setOutput('registry_type', registryType); // Set namespace (defaults to repository owner) const namespace = inputRegistryNamespace || repoOwner; @@ -186,58 +165,43 @@ jobs: const imageName = inputImageName || 'temporal'; core.setOutput('image_name', imageName); - // For Docker Hub, use empty string as registry (special case) - const imageRepo = registry === 'docker.io' ? '' : registry; - core.setOutput('image_repo', imageRepo); - - console.log(`Registry: ${registry}, Type: ${registryType}, Namespace: ${namespace}, Image: ${imageName}`); - - - name: Log in to GitHub Container Registry - if: inputs.publish && steps.meta.outputs.registry_type == 'ghcr' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Log in to Docker Hub - if: inputs.publish && steps.meta.outputs.registry_type == 'dockerhub' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} + console.log(`Namespace: ${namespace}, Image: ${imageName}`); - - name: Build and push Docker image - if: inputs.publish + - name: Build Docker image + if: ${{ !inputs.publish }} run: | docker buildx bake \ --file docker-bake.hcl \ - --push \ cli env: CLI_SHA: ${{ steps.meta.outputs.cli_sha }} IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} - IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} - IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} + TAG_LATEST: false IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} - - name: Build Docker image - if: ${{ !inputs.publish }} + - name: Log in to Docker Hub + if: inputs.publish + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + if: ${{ inputs.publish }} run: | docker buildx bake \ --file docker-bake.hcl \ + --push \ cli env: CLI_SHA: ${{ steps.meta.outputs.cli_sha }} IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} - IMAGE_BRANCH_TAG: ${{ steps.meta.outputs.image_branch_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: false - IMAGE_REPO: ${{ steps.meta.outputs.image_repo }} + TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} + diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index b226ee55e..91028f8c4 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} @@ -24,7 +24,7 @@ jobs: id: check_latest env: RELEASE_TAG: ${{ github.event.release.tag_name }} - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const releaseTag = process.env.RELEASE_TAG; @@ -47,7 +47,7 @@ jobs: - name: Get registry configuration if: steps.check_latest.outputs.is_latest == 'true' id: registry - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const repoOwner = context.repo.owner; diff --git a/docker-bake.hcl b/docker-bake.hcl index b4343e7ee..20cd6e827 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -1,7 +1,3 @@ -variable "IMAGE_REPO" { - default = "ghcr.io" -} - variable "IMAGE_NAMESPACE" { default = "" } @@ -16,8 +12,6 @@ variable "GITHUB_REPOSITORY" { variable "IMAGE_SHA_TAG" {} -variable "IMAGE_BRANCH_TAG" {} - variable "CLI_SHA" { default = "" } @@ -36,9 +30,9 @@ target "cli" { dockerfile = "Dockerfile" context = "." tags = compact([ - IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", - IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}", - TAG_LATEST ? (IMAGE_REPO == "" ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "${IMAGE_REPO}/${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest") : "", + "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", + "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}", + TAG_LATEST ? ${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest : "", ]) platforms = ["linux/amd64", "linux/arm64"] labels = { From 4274740d9921f094ef51022c9abcb19bd399e423 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 26 Nov 2025 12:51:50 -0600 Subject: [PATCH 17/31] a few minor fixes --- .github/workflows/build-and-publish.yml | 3 ++- .github/workflows/update-latest-tag.yml | 12 ++++++++++-- docker-bake.hcl | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index a723f410f..45e19d050 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -123,6 +123,7 @@ jobs: env: INPUT_VERSION: ${{ inputs.version }} INPUT_PUBLISH: ${{ inputs.publish }} + INPUT_REGISTRY: ${{ inputs.registry }} INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} @@ -183,7 +184,7 @@ jobs: GITHUB_REPOSITORY: ${{ github.repository }} - name: Log in to Docker Hub - if: inputs.publish + if: inputs.publish && secrets.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index 91028f8c4..962e35d62 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -99,6 +99,7 @@ jobs: - name: Pull and retag image as latest if: steps.check_latest.outputs.is_latest == 'true' run: | + set -e # Construct image paths REPO="${{ steps.registry.outputs.repo }}" NAMESPACE="${{ steps.registry.outputs.namespace }}" @@ -116,12 +117,19 @@ jobs: fi echo "Pulling ${SOURCE_IMAGE}..." - docker pull ${SOURCE_IMAGE} + if ! docker pull ${SOURCE_IMAGE}; then + echo "Error: Failed to pull ${SOURCE_IMAGE}. The image may not exist yet." + echo "This can happen if the build-and-publish workflow hasn't completed successfully." + exit 1 + fi echo "Tagging as ${LATEST_IMAGE}..." docker tag ${SOURCE_IMAGE} ${LATEST_IMAGE} echo "Pushing ${LATEST_IMAGE}..." - docker push ${LATEST_IMAGE} + if ! docker push ${LATEST_IMAGE}; then + echo "Error: Failed to push ${LATEST_IMAGE}" + exit 1 + fi echo "✅ Successfully updated latest tag to point to version ${VERSION}" diff --git a/docker-bake.hcl b/docker-bake.hcl index 20cd6e827..ce21ffd63 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -32,7 +32,7 @@ target "cli" { tags = compact([ "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}", - TAG_LATEST ? ${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest : "", + TAG_LATEST ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "", ]) platforms = ["linux/amd64", "linux/arm64"] labels = { From fa17267f5847cc381fc10a17544a3205aaddd66a Mon Sep 17 00:00:00 2001 From: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:07:34 -0600 Subject: [PATCH 18/31] update action versions --- .github/workflows/build-and-publish.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 45e19d050..1d16ebc88 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -38,7 +38,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v6 with: fetch-depth: 0 @@ -90,7 +90,7 @@ jobs: - name: Run GoReleaser (release) if: inputs.publish - uses: goreleaser/goreleaser-action@v6.4.0 + uses: goreleaser/goreleaser-action@v6 with: version: v2.12.7 args: release @@ -104,7 +104,7 @@ jobs: - name: Run GoReleaser (snapshot) if: ${{ !inputs.publish }} - uses: goreleaser/goreleaser-action@v6.4.0 + uses: goreleaser/goreleaser-action@v6 with: version: v2.12.7 args: release --snapshot --clean @@ -127,7 +127,7 @@ jobs: INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const inputVersion = process.env.INPUT_VERSION; From ef83ff283bac947d5fdb556c80f1aca91b6daa7d Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:21:10 -0600 Subject: [PATCH 19/31] simplify script again --- .github/workflows/build-and-publish.yml | 21 ++------------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 1d16ebc88..7551516e5 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -11,11 +11,6 @@ on: description: "Version tag for the release (required if publish is true)" required: false type: string - registry: - description: "Container registry (docker.io, ghcr.io, etc.)" - required: false - type: string - default: "" registry_namespace: description: "Registry namespace/organization" required: false @@ -123,7 +118,6 @@ jobs: env: INPUT_VERSION: ${{ inputs.version }} INPUT_PUBLISH: ${{ inputs.publish }} - INPUT_REGISTRY: ${{ inputs.registry }} INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} INPUT_IMAGE_NAME: ${{ inputs.image_name }} REPO_OWNER: ${{ github.repository_owner }} @@ -132,9 +126,7 @@ jobs: script: | const inputVersion = process.env.INPUT_VERSION; const inputPublish = process.env.INPUT_PUBLISH; - const inputRegistry = process.env.INPUT_REGISTRY; const inputRegistryNamespace = process.env.INPUT_REGISTRY_NAMESPACE; - const inputImageName = process.env.INPUT_IMAGE_NAME; const repoOwner = process.env.REPO_OWNER; // Get git information @@ -155,19 +147,10 @@ jobs: } core.setOutput('version', version); - // Determine registry (with auto-detection for temporalio vs forks) - let registry = inputRegistry; - // Set namespace (defaults to repository owner) const namespace = inputRegistryNamespace || repoOwner; core.setOutput('image_namespace', namespace); - // Set image name (defaults to 'temporal') - const imageName = inputImageName || 'temporal'; - core.setOutput('image_name', imageName); - - console.log(`Namespace: ${namespace}, Image: ${imageName}`); - - name: Build Docker image if: ${{ !inputs.publish }} run: | @@ -179,7 +162,7 @@ jobs: IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} VERSION: ${{ steps.meta.outputs.version }} TAG_LATEST: false - IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} + IMAGE_NAMESPACE: ${{ inputs.registry_namespace }} IMAGE_NAME: ${{ steps.meta.outputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} @@ -203,6 +186,6 @@ jobs: VERSION: ${{ steps.meta.outputs.version }} TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} - IMAGE_NAME: ${{ steps.meta.outputs.image_name }} + IMAGE_NAME: ${{ inputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} From 48648132d2800d87bc18c0d0642aa71be73a3c1e Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:33:42 -0600 Subject: [PATCH 20/31] fix syntax and corrections for lint issues --- .github/workflows/build-and-publish.yml | 12 ++++++------ .github/workflows/trigger-docs.yml | 4 ++-- .github/workflows/update-latest-tag.yml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 7551516e5..36d8053d7 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -46,23 +46,23 @@ jobs: - name: Get build date id: date - run: echo "date=$(date '+%F-%T')" >> $GITHUB_OUTPUT + run: echo "date=$(date '+%F-%T')" >> "$GITHUB_OUTPUT" - name: Get build unix timestamp id: timestamp - run: echo "timestamp=$(date '+%s')" >> $GITHUB_OUTPUT + run: echo "timestamp=$(date '+%s')" >> "$GITHUB_OUTPUT" - name: Get git branch id: branch - run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> $GITHUB_OUTPUT + run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" - name: Get build platform id: platform - run: echo "platform=$(go version | cut -d ' ' -f 4)" >> $GITHUB_OUTPUT + run: echo "platform=$(go version | cut -d ' ' -f 4)" >> "$GITHUB_OUTPUT" - name: Get Go version id: go - run: echo "go=$(go version | cut -d ' ' -f 3)" >> $GITHUB_OUTPUT + run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" - name: Check if release is latest if: inputs.publish @@ -167,7 +167,7 @@ jobs: GITHUB_REPOSITORY: ${{ github.repository }} - name: Log in to Docker Hub - if: inputs.publish && secrets.DOCKER_USERNAME != '' && secrets.DOCKER_PASSWORD != '' + if: inputs.publish uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} diff --git a/.github/workflows/trigger-docs.yml b/.github/workflows/trigger-docs.yml index 5d455607f..1fda68858 100644 --- a/.github/workflows/trigger-docs.yml +++ b/.github/workflows/trigger-docs.yml @@ -32,8 +32,8 @@ jobs: git_email="${{ github.actor }}@users.noreply.github.com" # Set the outputs for subsequent steps. - echo "GIT_NAME=$git_name" >> $GITHUB_OUTPUT - echo "GIT_EMAIL=$git_email" >> $GITHUB_OUTPUT + echo "GIT_NAME=$git_name" >> "$GITHUB_OUTPUT" + echo "GIT_EMAIL=$git_email" >> "$GITHUB_OUTPUT" - name: Generate token id: generate_token diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index 962e35d62..99cf7544f 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -94,7 +94,7 @@ jobs: run: | VERSION="$RELEASE_TAG" VERSION="${VERSION#v}" - echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" - name: Pull and retag image as latest if: steps.check_latest.outputs.is_latest == 'true' From 05b0021684677d02f463132746621a29c816e135 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Wed, 26 Nov 2025 13:52:08 -0600 Subject: [PATCH 21/31] add namespace default --- .github/workflows/build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 36d8053d7..94e485a22 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -15,7 +15,7 @@ on: description: "Registry namespace/organization" required: false type: string - default: "" + default: "temporalio" image_name: description: "Image name" required: false From b7bda83845998bf8f791844126099909755c1c09 Mon Sep 17 00:00:00 2001 From: Alex Stanfield <13949480+chaptersix@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:08:20 -0600 Subject: [PATCH 22/31] correct image name --- .github/workflows/build-and-publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 94e485a22..04ac3e842 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -163,7 +163,7 @@ jobs: VERSION: ${{ steps.meta.outputs.version }} TAG_LATEST: false IMAGE_NAMESPACE: ${{ inputs.registry_namespace }} - IMAGE_NAME: ${{ steps.meta.outputs.image_name }} + IMAGE_NAME: ${{ inputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} - name: Log in to Docker Hub From 0388341951d93413cd3f66ee82652dd31fa4d820 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Mon, 1 Dec 2025 09:59:23 -0600 Subject: [PATCH 23/31] refactor update tag workflow --- .github/scripts/check-latest-tag.js | 52 ++++++++++++++ .../workflows/{goreleaser.yml => release.yml} | 0 .github/workflows/update-latest-tag.yml | 71 +++---------------- 3 files changed, 63 insertions(+), 60 deletions(-) create mode 100644 .github/scripts/check-latest-tag.js rename .github/workflows/{goreleaser.yml => release.yml} (100%) diff --git a/.github/scripts/check-latest-tag.js b/.github/scripts/check-latest-tag.js new file mode 100644 index 000000000..6d958f687 --- /dev/null +++ b/.github/scripts/check-latest-tag.js @@ -0,0 +1,52 @@ +const { execFileSync } = require('child_process'); + +// Get inputs from environment +const releaseTag = process.env.RELEASE_TAG; + +// Strip 'v' prefix from version +const version = releaseTag.startsWith('v') ? releaseTag.slice(1) : releaseTag; +const sourceImage = `temporalio/temporal:${version}`; +const latestImage = 'temporalio/temporal:latest'; + +// Set outputs for use in next step +console.log(`::set-output name=version::${version}`); +console.log(`::set-output name=source_image::${sourceImage}`); +console.log(`::set-output name=latest_image::${latestImage}`); + +console.log(`Version: ${version}`); +console.log('Checking if image is already tagged as latest...'); + +try { + // Get digest for the latest tag + const latestManifest = execFileSync( + 'docker', + ['manifest', 'inspect', latestImage], + { encoding: 'utf8' } + ); + const latestDigest = JSON.parse(latestManifest).config.digest; + + // Get digest for the version tag + const sourceManifest = execFileSync( + 'docker', + ['manifest', 'inspect', sourceImage], + { encoding: 'utf8' } + ); + const sourceDigest = JSON.parse(sourceManifest).config.digest; + + // Compare digests + if (latestDigest === sourceDigest) { + console.log(`✅ Image ${version} is already tagged as latest`); + console.log('::set-output name=already_latest::true'); + process.exit(0); + } + + console.log(`Latest digest: ${latestDigest}`); + console.log(`Source digest: ${sourceDigest}`); + console.log('Digests do not match, will update latest tag'); + console.log('::set-output name=already_latest::false'); + +} catch (error) { + console.log('Could not compare digests (image may not exist yet)'); + console.log(`Error: ${error.message}`); + console.log('::set-output name=already_latest::false'); +} diff --git a/.github/workflows/goreleaser.yml b/.github/workflows/release.yml similarity index 100% rename from .github/workflows/goreleaser.yml rename to .github/workflows/release.yml diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index 99cf7544f..828777450 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -44,83 +44,34 @@ jobs: if: steps.check_latest.outputs.is_latest == 'true' uses: docker/setup-buildx-action@v3 - - name: Get registry configuration - if: steps.check_latest.outputs.is_latest == 'true' - id: registry - uses: actions/github-script@v8 - with: - script: | - const repoOwner = context.repo.owner; - - // Auto-detect registry based on repository owner - let registry, type, repo; - if (repoOwner === 'temporalio') { - registry = 'docker.io'; - type = 'dockerhub'; - repo = ''; - } else { - registry = 'ghcr.io'; - type = 'ghcr'; - repo = registry; - } - - core.setOutput('type', type); - core.setOutput('repo', repo); - core.setOutput('namespace', repoOwner); - core.setOutput('image', 'temporal'); - - console.log(`Registry: ${registry}, Type: ${type}, Namespace: ${repoOwner}`); - - - name: Log in to GitHub Container Registry - if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'ghcr' - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Log in to Docker Hub - if: steps.check_latest.outputs.is_latest == 'true' && steps.registry.outputs.type == 'dockerhub' + if: steps.check_latest.outputs.is_latest == 'true' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - - name: Get version tag + - name: Prepare version and check if already tagged as latest if: steps.check_latest.outputs.is_latest == 'true' - id: version + id: check_current_latest env: RELEASE_TAG: ${{ github.event.release.tag_name }} - run: | - VERSION="$RELEASE_TAG" - VERSION="${VERSION#v}" - echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + run: node .github/scripts/check-latest-tag.js - name: Pull and retag image as latest - if: steps.check_latest.outputs.is_latest == 'true' + if: steps.check_latest.outputs.is_latest == 'true' && steps.check_current_latest.outputs.already_latest != 'true' + env: + SOURCE_IMAGE: ${{ steps.check_current_latest.outputs.source_image }} + LATEST_IMAGE: ${{ steps.check_current_latest.outputs.latest_image }} + VERSION: ${{ steps.check_current_latest.outputs.version }} run: | set -e - # Construct image paths - REPO="${{ steps.registry.outputs.repo }}" - NAMESPACE="${{ steps.registry.outputs.namespace }}" - IMAGE="${{ steps.registry.outputs.image }}" - VERSION="${{ steps.version.outputs.version }}" - - if [[ -z "$REPO" ]]; then - # Docker Hub format - SOURCE_IMAGE="${NAMESPACE}/${IMAGE}:${VERSION}" - LATEST_IMAGE="${NAMESPACE}/${IMAGE}:latest" - else - # Other registries - SOURCE_IMAGE="${REPO}/${NAMESPACE}/${IMAGE}:${VERSION}" - LATEST_IMAGE="${REPO}/${NAMESPACE}/${IMAGE}:latest" - fi echo "Pulling ${SOURCE_IMAGE}..." if ! docker pull ${SOURCE_IMAGE}; then echo "Error: Failed to pull ${SOURCE_IMAGE}. The image may not exist yet." - echo "This can happen if the build-and-publish workflow hasn't completed successfully." - exit 1 + echo "This can happen if the build-and-publish workflow has not yet completed successfully." + exit 0 fi echo "Tagging as ${LATEST_IMAGE}..." From 2098e5c7f7dd7a8689b89998a4b8261a04a41b08 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:01:02 -0600 Subject: [PATCH 24/31] remove comments and checks that are no longer needed --- .github/scripts/check-latest-tag.js | 5 ----- .github/workflows/update-latest-tag.yml | 11 ++--------- 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/.github/scripts/check-latest-tag.js b/.github/scripts/check-latest-tag.js index 6d958f687..602727240 100644 --- a/.github/scripts/check-latest-tag.js +++ b/.github/scripts/check-latest-tag.js @@ -1,9 +1,7 @@ const { execFileSync } = require('child_process'); -// Get inputs from environment const releaseTag = process.env.RELEASE_TAG; -// Strip 'v' prefix from version const version = releaseTag.startsWith('v') ? releaseTag.slice(1) : releaseTag; const sourceImage = `temporalio/temporal:${version}`; const latestImage = 'temporalio/temporal:latest'; @@ -17,7 +15,6 @@ console.log(`Version: ${version}`); console.log('Checking if image is already tagged as latest...'); try { - // Get digest for the latest tag const latestManifest = execFileSync( 'docker', ['manifest', 'inspect', latestImage], @@ -25,7 +22,6 @@ try { ); const latestDigest = JSON.parse(latestManifest).config.digest; - // Get digest for the version tag const sourceManifest = execFileSync( 'docker', ['manifest', 'inspect', sourceImage], @@ -33,7 +29,6 @@ try { ); const sourceDigest = JSON.parse(sourceManifest).config.digest; - // Compare digests if (latestDigest === sourceDigest) { console.log(`✅ Image ${version} is already tagged as latest`); console.log('::set-output name=already_latest::true'); diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index 828777450..8eb59bd17 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -68,19 +68,12 @@ jobs: set -e echo "Pulling ${SOURCE_IMAGE}..." - if ! docker pull ${SOURCE_IMAGE}; then - echo "Error: Failed to pull ${SOURCE_IMAGE}. The image may not exist yet." - echo "This can happen if the build-and-publish workflow has not yet completed successfully." - exit 0 - fi + docker pull ${SOURCE_IMAGE} echo "Tagging as ${LATEST_IMAGE}..." docker tag ${SOURCE_IMAGE} ${LATEST_IMAGE} echo "Pushing ${LATEST_IMAGE}..." - if ! docker push ${LATEST_IMAGE}; then - echo "Error: Failed to push ${LATEST_IMAGE}" - exit 1 - fi + docker push ${LATEST_IMAGE} echo "✅ Successfully updated latest tag to point to version ${VERSION}" From a096c9281ec63e70c038c8e98440f9ea00922999 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Mon, 1 Dec 2025 14:17:37 -0600 Subject: [PATCH 25/31] security fixes --- .github/scripts/check-latest-tag.js | 16 ++++++++-------- .github/workflows/trigger-docs.yml | 16 +++++++++------- .github/workflows/update-latest-tag.yml | 7 ++++--- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/.github/scripts/check-latest-tag.js b/.github/scripts/check-latest-tag.js index 602727240..972da77a4 100644 --- a/.github/scripts/check-latest-tag.js +++ b/.github/scripts/check-latest-tag.js @@ -1,15 +1,15 @@ const { execFileSync } = require('child_process'); +const fs = require('fs'); const releaseTag = process.env.RELEASE_TAG; - const version = releaseTag.startsWith('v') ? releaseTag.slice(1) : releaseTag; const sourceImage = `temporalio/temporal:${version}`; const latestImage = 'temporalio/temporal:latest'; // Set outputs for use in next step -console.log(`::set-output name=version::${version}`); -console.log(`::set-output name=source_image::${sourceImage}`); -console.log(`::set-output name=latest_image::${latestImage}`); +fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`); +fs.appendFileSync(process.env.GITHUB_OUTPUT, `source_image=${sourceImage}\n`); +fs.appendFileSync(process.env.GITHUB_OUTPUT, `latest_image=${latestImage}\n`); console.log(`Version: ${version}`); console.log('Checking if image is already tagged as latest...'); @@ -30,18 +30,18 @@ try { const sourceDigest = JSON.parse(sourceManifest).config.digest; if (latestDigest === sourceDigest) { - console.log(`✅ Image ${version} is already tagged as latest`); - console.log('::set-output name=already_latest::true'); + console.log(`Image ${version} is already tagged as latest`); + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'already_latest=true\n'); process.exit(0); } console.log(`Latest digest: ${latestDigest}`); console.log(`Source digest: ${sourceDigest}`); console.log('Digests do not match, will update latest tag'); - console.log('::set-output name=already_latest::false'); + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'already_latest=false\n'); } catch (error) { console.log('Could not compare digests (image may not exist yet)'); console.log(`Error: ${error.message}`); - console.log('::set-output name=already_latest::false'); + fs.appendFileSync(process.env.GITHUB_OUTPUT, 'already_latest=false\n'); } diff --git a/.github/workflows/trigger-docs.yml b/.github/workflows/trigger-docs.yml index 1fda68858..cfa6349a8 100644 --- a/.github/workflows/trigger-docs.yml +++ b/.github/workflows/trigger-docs.yml @@ -17,20 +17,22 @@ jobs: steps: - name: Get user info from GitHub API id: get_user + env: + GITHUB_ACTOR: ${{ github.actor }} run: | - echo "GitHub actor: ${{ github.actor }}" + echo "GitHub actor: ${GITHUB_ACTOR}" # Query the GitHub API for the user's details. curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - https://api.github.com/users/${{ github.actor }} > user.json - + "https://api.github.com/users/${GITHUB_ACTOR}" > user.json + # Extract the user's full name if available, default to the username otherwise. git_name=$(jq -r '.name // empty' user.json) if [ -z "$git_name" ]; then - git_name="${{ github.actor }}" + git_name="${GITHUB_ACTOR}" fi - - git_email="${{ github.actor }}@users.noreply.github.com" - + + git_email="${GITHUB_ACTOR}@users.noreply.github.com" + # Set the outputs for subsequent steps. echo "GIT_NAME=$git_name" >> "$GITHUB_OUTPUT" echo "GIT_EMAIL=$git_email" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml index 8eb59bd17..127943450 100644 --- a/.github/workflows/update-latest-tag.yml +++ b/.github/workflows/update-latest-tag.yml @@ -19,6 +19,7 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ github.event.release.tag_name }} + persist-credentials: false - name: Check if release is latest id: check_latest @@ -68,12 +69,12 @@ jobs: set -e echo "Pulling ${SOURCE_IMAGE}..." - docker pull ${SOURCE_IMAGE} + docker pull "${SOURCE_IMAGE}" echo "Tagging as ${LATEST_IMAGE}..." - docker tag ${SOURCE_IMAGE} ${LATEST_IMAGE} + docker tag "${SOURCE_IMAGE}" "${LATEST_IMAGE}" echo "Pushing ${LATEST_IMAGE}..." - docker push ${LATEST_IMAGE} + docker push "${LATEST_IMAGE}" echo "✅ Successfully updated latest tag to point to version ${VERSION}" From 90577f91c1c91d9f95b442513e13babfa5f723ad Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Tue, 2 Dec 2025 13:51:12 -0600 Subject: [PATCH 26/31] add tzdata to dockerfile --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index fe2a4cb90..46f601a8b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2 ARG TARGETARCH -RUN apk add --no-cache ca-certificates +RUN apk add --no-cache ca-certificates tzdata COPY --from=dist /dist/${TARGETARCH}/temporal /usr/local/bin/temporal RUN adduser -u 1000 -D temporal USER temporal From 7b1df2e78e301194b6ad9d8e1b4fbfbb780421ea Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Tue, 2 Dec 2025 16:48:45 -0600 Subject: [PATCH 27/31] update tag latest workflow and remove latest tagging from other code --- .github/scripts/check-latest-tag.js | 47 -------------- .github/scripts/tag-latest.js | 81 +++++++++++++++++++++++++ .github/workflows/build-and-publish.yml | 22 ------- .github/workflows/release.yml | 1 - .github/workflows/tag-latest.yml | 42 +++++++++++++ .github/workflows/update-latest-tag.yml | 80 ------------------------ docker-bake.hcl | 11 +--- 7 files changed, 125 insertions(+), 159 deletions(-) delete mode 100644 .github/scripts/check-latest-tag.js create mode 100644 .github/scripts/tag-latest.js create mode 100644 .github/workflows/tag-latest.yml delete mode 100644 .github/workflows/update-latest-tag.yml diff --git a/.github/scripts/check-latest-tag.js b/.github/scripts/check-latest-tag.js deleted file mode 100644 index 972da77a4..000000000 --- a/.github/scripts/check-latest-tag.js +++ /dev/null @@ -1,47 +0,0 @@ -const { execFileSync } = require('child_process'); -const fs = require('fs'); - -const releaseTag = process.env.RELEASE_TAG; -const version = releaseTag.startsWith('v') ? releaseTag.slice(1) : releaseTag; -const sourceImage = `temporalio/temporal:${version}`; -const latestImage = 'temporalio/temporal:latest'; - -// Set outputs for use in next step -fs.appendFileSync(process.env.GITHUB_OUTPUT, `version=${version}\n`); -fs.appendFileSync(process.env.GITHUB_OUTPUT, `source_image=${sourceImage}\n`); -fs.appendFileSync(process.env.GITHUB_OUTPUT, `latest_image=${latestImage}\n`); - -console.log(`Version: ${version}`); -console.log('Checking if image is already tagged as latest...'); - -try { - const latestManifest = execFileSync( - 'docker', - ['manifest', 'inspect', latestImage], - { encoding: 'utf8' } - ); - const latestDigest = JSON.parse(latestManifest).config.digest; - - const sourceManifest = execFileSync( - 'docker', - ['manifest', 'inspect', sourceImage], - { encoding: 'utf8' } - ); - const sourceDigest = JSON.parse(sourceManifest).config.digest; - - if (latestDigest === sourceDigest) { - console.log(`Image ${version} is already tagged as latest`); - fs.appendFileSync(process.env.GITHUB_OUTPUT, 'already_latest=true\n'); - process.exit(0); - } - - console.log(`Latest digest: ${latestDigest}`); - console.log(`Source digest: ${sourceDigest}`); - console.log('Digests do not match, will update latest tag'); - fs.appendFileSync(process.env.GITHUB_OUTPUT, 'already_latest=false\n'); - -} catch (error) { - console.log('Could not compare digests (image may not exist yet)'); - console.log(`Error: ${error.message}`); - fs.appendFileSync(process.env.GITHUB_OUTPUT, 'already_latest=false\n'); -} diff --git a/.github/scripts/tag-latest.js b/.github/scripts/tag-latest.js new file mode 100644 index 000000000..4098e693d --- /dev/null +++ b/.github/scripts/tag-latest.js @@ -0,0 +1,81 @@ +const { execFileSync } = require('child_process'); + +const inputTag = process.env.INPUT_TAG; +const inputSha = process.env.INPUT_SHA; + +// Validate inputs +if (!inputTag && !inputSha) { + console.error('Error: Either "tag" or "sha" input must be provided'); + process.exit(1); +} + +if (inputTag && inputSha) { + console.error('Error: Only one of "tag" or "sha" should be provided, not both'); + process.exit(1); +} + +// Determine source image +let sourceImage; +let imageRef; + +if (inputTag) { + const version = inputTag.startsWith('v') ? inputTag.slice(1) : inputTag; + sourceImage = `temporalio/temporal:${version}`; + imageRef = version; + console.log(`Using tag: ${version}`); +} else { + if (!inputSha.startsWith('sha256:')) { + console.error('Error: SHA must start with "sha256:"'); + process.exit(1); + } + sourceImage = `temporalio/temporal@${inputSha}`; + imageRef = inputSha; + console.log(`Using SHA: ${inputSha}`); +} + +const latestImage = 'temporalio/temporal:latest'; + +// Check if already tagged as latest +console.log('Checking if image is already tagged as latest...'); +try { + const latestManifest = execFileSync( + 'docker', + ['manifest', 'inspect', latestImage], + { encoding: 'utf8' } + ); + const latestDigest = JSON.parse(latestManifest).config.digest; + + const sourceManifest = execFileSync( + 'docker', + ['manifest', 'inspect', sourceImage], + { encoding: 'utf8' } + ); + const sourceDigest = JSON.parse(sourceManifest).config.digest; + + if (latestDigest === sourceDigest) { + console.log(`ℹ️ Image ${imageRef} is already tagged as latest. No action needed.`); + process.exit(0); + } + + console.log(`Latest digest: ${latestDigest}`); + console.log(`Source digest: ${sourceDigest}`); +} catch (error) { + console.log('Could not compare digests, will proceed with tagging'); +} + +// Pull, tag, and push +try { + console.log(`Pulling ${sourceImage}...`); + execFileSync('docker', ['pull', sourceImage], { stdio: 'inherit' }); + + console.log(`Tagging as ${latestImage}...`); + execFileSync('docker', ['tag', sourceImage, latestImage], { stdio: 'inherit' }); + + console.log(`Pushing ${latestImage}...`); + execFileSync('docker', ['push', latestImage], { stdio: 'inherit' }); + + console.log(`✅ Successfully updated latest tag to point to ${imageRef}`); +} catch (error) { + console.error('Error during Docker operations:', error.message); + process.exit(1); +} diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml index 04ac3e842..188a2c943 100644 --- a/.github/workflows/build-and-publish.yml +++ b/.github/workflows/build-and-publish.yml @@ -64,25 +64,6 @@ jobs: id: go run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" - - name: Check if release is latest - if: inputs.publish - id: check_latest_release - uses: actions/github-script@v8 - with: - script: | - const releaseTag = '${{ inputs.version }}'; - const { data: release } = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: releaseTag - }); - - const isLatest = !release.prerelease && !release.draft; - core.setOutput('is_latest', isLatest); - console.log(`Release: ${release.tag_name}`); - console.log(`Prerelease: ${release.prerelease}, Draft: ${release.draft}`); - console.log(`Should tag as latest: ${isLatest}`); - - name: Run GoReleaser (release) if: inputs.publish uses: goreleaser/goreleaser-action@v6 @@ -161,7 +142,6 @@ jobs: CLI_SHA: ${{ steps.meta.outputs.cli_sha }} IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: false IMAGE_NAMESPACE: ${{ inputs.registry_namespace }} IMAGE_NAME: ${{ inputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} @@ -184,8 +164,6 @@ jobs: CLI_SHA: ${{ steps.meta.outputs.cli_sha }} IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} VERSION: ${{ steps.meta.outputs.version }} - TAG_LATEST: ${{ steps.check_latest_release.outputs.is_latest == 'true' }} IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} IMAGE_NAME: ${{ inputs.image_name }} GITHUB_REPOSITORY: ${{ github.repository }} - diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 100129438..dfd79cedd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,6 @@ on: permissions: contents: write - packages: write jobs: release: diff --git a/.github/workflows/tag-latest.yml b/.github/workflows/tag-latest.yml new file mode 100644 index 000000000..75f33ea1d --- /dev/null +++ b/.github/workflows/tag-latest.yml @@ -0,0 +1,42 @@ +name: Tag Docker Image as Latest + +on: + workflow_dispatch: + inputs: + tag: + description: 'Docker image tag to promote to latest (e.g., 1.2.3 or v1.2.3)' + required: false + type: string + sha: + description: 'Docker image SHA to promote to latest (e.g., sha256:abc123...)' + required: false + type: string + +permissions: + contents: read + +jobs: + update-latest: + name: Update Latest Tag + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Tag image as latest + env: + INPUT_TAG: ${{ inputs.tag }} + INPUT_SHA: ${{ inputs.sha }} + run: node .github/scripts/tag-latest.js diff --git a/.github/workflows/update-latest-tag.yml b/.github/workflows/update-latest-tag.yml deleted file mode 100644 index 127943450..000000000 --- a/.github/workflows/update-latest-tag.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: Update Latest Docker Tag - -on: - release: - types: - - edited - - released - -permissions: - contents: read - packages: write - -jobs: - update-latest: - name: Update Latest Tag - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - ref: ${{ github.event.release.tag_name }} - persist-credentials: false - - - name: Check if release is latest - id: check_latest - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - uses: actions/github-script@v8 - with: - script: | - const releaseTag = process.env.RELEASE_TAG; - const { data: release } = await github.rest.repos.getReleaseByTag({ - owner: context.repo.owner, - repo: context.repo.repo, - tag: releaseTag - }); - - const isLatest = !release.prerelease && !release.draft; - core.setOutput('is_latest', isLatest); - console.log(`Release: ${release.tag_name}`); - console.log(`Prerelease: ${release.prerelease}, Draft: ${release.draft}`); - console.log(`Should tag as latest: ${isLatest}`); - - - name: Set up Docker Buildx - if: steps.check_latest.outputs.is_latest == 'true' - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - if: steps.check_latest.outputs.is_latest == 'true' - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Prepare version and check if already tagged as latest - if: steps.check_latest.outputs.is_latest == 'true' - id: check_current_latest - env: - RELEASE_TAG: ${{ github.event.release.tag_name }} - run: node .github/scripts/check-latest-tag.js - - - name: Pull and retag image as latest - if: steps.check_latest.outputs.is_latest == 'true' && steps.check_current_latest.outputs.already_latest != 'true' - env: - SOURCE_IMAGE: ${{ steps.check_current_latest.outputs.source_image }} - LATEST_IMAGE: ${{ steps.check_current_latest.outputs.latest_image }} - VERSION: ${{ steps.check_current_latest.outputs.version }} - run: | - set -e - - echo "Pulling ${SOURCE_IMAGE}..." - docker pull "${SOURCE_IMAGE}" - - echo "Tagging as ${LATEST_IMAGE}..." - docker tag "${SOURCE_IMAGE}" "${LATEST_IMAGE}" - - echo "Pushing ${LATEST_IMAGE}..." - docker push "${LATEST_IMAGE}" - - echo "✅ Successfully updated latest tag to point to version ${VERSION}" diff --git a/docker-bake.hcl b/docker-bake.hcl index ce21ffd63..fa2130e2c 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -20,20 +20,13 @@ variable "VERSION" { default = "dev" } -variable "TAG_LATEST" { - default = false -} - - - target "cli" { dockerfile = "Dockerfile" context = "." - tags = compact([ + tags = [ "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}", - TAG_LATEST ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "", - ]) + ] platforms = ["linux/amd64", "linux/arm64"] labels = { "org.opencontainers.image.title" = "temporal" From 4249db76cea5624261b1532e3173e3cad3fef5e1 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:39:37 -0600 Subject: [PATCH 28/31] refactor to build docker images only in a manually triggered workflow. --- .github/scripts/tag-latest.js | 81 --------- .../workflows/build-and-publish-docker.yml | 99 ++++++++++ .github/workflows/build-and-publish.yml | 169 ------------------ .github/workflows/build-docker-image.yml | 15 -- .github/workflows/build.yml | 59 ++++++ .github/workflows/release.yml | 54 +++++- .github/workflows/tag-latest.yml | 42 ----- Dockerfile | 9 +- docker-bake.hcl | 9 +- 9 files changed, 213 insertions(+), 324 deletions(-) delete mode 100644 .github/scripts/tag-latest.js create mode 100644 .github/workflows/build-and-publish-docker.yml delete mode 100644 .github/workflows/build-and-publish.yml delete mode 100644 .github/workflows/build-docker-image.yml create mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/tag-latest.yml diff --git a/.github/scripts/tag-latest.js b/.github/scripts/tag-latest.js deleted file mode 100644 index 4098e693d..000000000 --- a/.github/scripts/tag-latest.js +++ /dev/null @@ -1,81 +0,0 @@ -const { execFileSync } = require('child_process'); - -const inputTag = process.env.INPUT_TAG; -const inputSha = process.env.INPUT_SHA; - -// Validate inputs -if (!inputTag && !inputSha) { - console.error('Error: Either "tag" or "sha" input must be provided'); - process.exit(1); -} - -if (inputTag && inputSha) { - console.error('Error: Only one of "tag" or "sha" should be provided, not both'); - process.exit(1); -} - -// Determine source image -let sourceImage; -let imageRef; - -if (inputTag) { - const version = inputTag.startsWith('v') ? inputTag.slice(1) : inputTag; - sourceImage = `temporalio/temporal:${version}`; - imageRef = version; - console.log(`Using tag: ${version}`); -} else { - if (!inputSha.startsWith('sha256:')) { - console.error('Error: SHA must start with "sha256:"'); - process.exit(1); - } - sourceImage = `temporalio/temporal@${inputSha}`; - imageRef = inputSha; - console.log(`Using SHA: ${inputSha}`); -} - -const latestImage = 'temporalio/temporal:latest'; - -// Check if already tagged as latest -console.log('Checking if image is already tagged as latest...'); -try { - const latestManifest = execFileSync( - 'docker', - ['manifest', 'inspect', latestImage], - { encoding: 'utf8' } - ); - const latestDigest = JSON.parse(latestManifest).config.digest; - - const sourceManifest = execFileSync( - 'docker', - ['manifest', 'inspect', sourceImage], - { encoding: 'utf8' } - ); - const sourceDigest = JSON.parse(sourceManifest).config.digest; - - if (latestDigest === sourceDigest) { - console.log(`ℹ️ Image ${imageRef} is already tagged as latest. No action needed.`); - process.exit(0); - } - - console.log(`Latest digest: ${latestDigest}`); - console.log(`Source digest: ${sourceDigest}`); -} catch (error) { - console.log('Could not compare digests, will proceed with tagging'); -} - -// Pull, tag, and push -try { - console.log(`Pulling ${sourceImage}...`); - execFileSync('docker', ['pull', sourceImage], { stdio: 'inherit' }); - - console.log(`Tagging as ${latestImage}...`); - execFileSync('docker', ['tag', sourceImage, latestImage], { stdio: 'inherit' }); - - console.log(`Pushing ${latestImage}...`); - execFileSync('docker', ['push', latestImage], { stdio: 'inherit' }); - - console.log(`✅ Successfully updated latest tag to point to ${imageRef}`); -} catch (error) { - console.error('Error during Docker operations:', error.message); - process.exit(1); -} diff --git a/.github/workflows/build-and-publish-docker.yml b/.github/workflows/build-and-publish-docker.yml new file mode 100644 index 000000000..1a9075f71 --- /dev/null +++ b/.github/workflows/build-and-publish-docker.yml @@ -0,0 +1,99 @@ +name: Build and Publish Docker Image + +on: + workflow_dispatch: + inputs: + version: + description: 'Version tag for the Docker image (e.g., 1.2.3 or v1.2.3)' + required: true + type: string + tag_latest: + description: 'Also tag this image as latest' + required: false + type: boolean + default: false + +permissions: + contents: read + +jobs: + build-and-publish: + name: Build and Publish Docker Image + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Get build metadata + id: meta + env: + INPUT_VERSION: ${{ inputs.version }} + INPUT_TAG_LATEST: ${{ inputs.tag_latest }} + uses: actions/github-script@v8 + with: + script: | + const inputVersion = process.env.INPUT_VERSION; + const inputTagLatest = process.env.INPUT_TAG_LATEST; + + const { execSync } = require('child_process'); + const cliSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); + const imageShaTag = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); + + core.setOutput('cli_sha', cliSha); + core.setOutput('image_sha_tag', imageShaTag); + + const version = inputVersion.startsWith('v') ? inputVersion.slice(1) : inputVersion; + core.setOutput('version', version); + + const releaseTag = inputVersion.startsWith('v') ? inputVersion : `v${inputVersion}`; + core.setOutput('release_tag', releaseTag); + + core.setOutput('tag_latest', inputTagLatest === 'true'); + + - name: Download release assets + env: + GH_TOKEN: ${{ github.token }} + RELEASE_TAG: ${{ steps.meta.outputs.release_tag }} + run: | + echo "Downloading assets from release ${RELEASE_TAG}..." + gh release download "${RELEASE_TAG}" --pattern "temporal_*_linux_*.tar.gz" + + echo "Extracting and organizing binaries..." + mkdir -p dist/amd64 dist/arm64 + + tar -xzf temporal_*_linux_amd64.tar.gz + mv temporal dist/amd64/temporal + + tar -xzf temporal_*_linux_arm64.tar.gz + mv temporal dist/arm64/temporal + + echo "Verifying binaries..." + ls -lh dist/amd64/temporal + ls -lh dist/arm64/temporal + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Build and push Docker image + run: | + docker buildx bake \ + --file docker-bake.hcl \ + --push \ + cli + env: + CLI_SHA: ${{ steps.meta.outputs.cli_sha }} + IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} + VERSION: ${{ steps.meta.outputs.version }} + TAG_LATEST: ${{ steps.meta.outputs.tag_latest }} + IMAGE_NAMESPACE: temporalio + IMAGE_NAME: temporal + GITHUB_REPOSITORY: ${{ github.repository }} diff --git a/.github/workflows/build-and-publish.yml b/.github/workflows/build-and-publish.yml deleted file mode 100644 index 188a2c943..000000000 --- a/.github/workflows/build-and-publish.yml +++ /dev/null @@ -1,169 +0,0 @@ -name: Build and Publish (Reusable) - -on: - workflow_call: - inputs: - publish: - description: "Whether to publish the release and Docker image" - required: true - type: boolean - version: - description: "Version tag for the release (required if publish is true)" - required: false - type: string - registry_namespace: - description: "Registry namespace/organization" - required: false - type: string - default: "temporalio" - image_name: - description: "Image name" - required: false - type: string - default: "temporal" - secrets: - DOCKER_USERNAME: - required: false - DOCKER_PASSWORD: - required: false - -jobs: - build: - name: Build and Publish - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: "go.mod" - check-latest: true - cache: true - - - name: Get build date - id: date - run: echo "date=$(date '+%F-%T')" >> "$GITHUB_OUTPUT" - - - name: Get build unix timestamp - id: timestamp - run: echo "timestamp=$(date '+%s')" >> "$GITHUB_OUTPUT" - - - name: Get git branch - id: branch - run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" - - - name: Get build platform - id: platform - run: echo "platform=$(go version | cut -d ' ' -f 4)" >> "$GITHUB_OUTPUT" - - - name: Get Go version - id: go - run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" - - - name: Run GoReleaser (release) - if: inputs.publish - uses: goreleaser/goreleaser-action@v6 - with: - version: v2.12.7 - args: release - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - BUILD_DATE: ${{ steps.date.outputs.date }} - BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} - GIT_BRANCH: ${{ steps.branch.outputs.branch }} - BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} - GO_VERSION: ${{ steps.go.outputs.go }} - - - name: Run GoReleaser (snapshot) - if: ${{ !inputs.publish }} - uses: goreleaser/goreleaser-action@v6 - with: - version: v2.12.7 - args: release --snapshot --clean - env: - BUILD_DATE: ${{ steps.date.outputs.date }} - BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} - GIT_BRANCH: ${{ steps.branch.outputs.branch }} - BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} - GO_VERSION: ${{ steps.go.outputs.go }} - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Get build metadata - id: meta - env: - INPUT_VERSION: ${{ inputs.version }} - INPUT_PUBLISH: ${{ inputs.publish }} - INPUT_REGISTRY_NAMESPACE: ${{ inputs.registry_namespace }} - INPUT_IMAGE_NAME: ${{ inputs.image_name }} - REPO_OWNER: ${{ github.repository_owner }} - uses: actions/github-script@v8 - with: - script: | - const inputVersion = process.env.INPUT_VERSION; - const inputPublish = process.env.INPUT_PUBLISH; - const inputRegistryNamespace = process.env.INPUT_REGISTRY_NAMESPACE; - const repoOwner = process.env.REPO_OWNER; - - // Get git information - const { execSync } = require('child_process'); - const cliSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); - const imageShaTag = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); - - core.setOutput('cli_sha', cliSha); - core.setOutput('image_sha_tag', imageShaTag); - - // Determine version - let version; - if (inputPublish === 'true') { - // Get version from input, strip 'v' prefix - version = inputVersion.startsWith('v') ? inputVersion.slice(1) : inputVersion; - } else { - version = 'snapshot'; - } - core.setOutput('version', version); - - // Set namespace (defaults to repository owner) - const namespace = inputRegistryNamespace || repoOwner; - core.setOutput('image_namespace', namespace); - - - name: Build Docker image - if: ${{ !inputs.publish }} - run: | - docker buildx bake \ - --file docker-bake.hcl \ - cli - env: - CLI_SHA: ${{ steps.meta.outputs.cli_sha }} - IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} - VERSION: ${{ steps.meta.outputs.version }} - IMAGE_NAMESPACE: ${{ inputs.registry_namespace }} - IMAGE_NAME: ${{ inputs.image_name }} - GITHUB_REPOSITORY: ${{ github.repository }} - - - name: Log in to Docker Hub - if: inputs.publish - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Build and push Docker image - if: ${{ inputs.publish }} - run: | - docker buildx bake \ - --file docker-bake.hcl \ - --push \ - cli - env: - CLI_SHA: ${{ steps.meta.outputs.cli_sha }} - IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} - VERSION: ${{ steps.meta.outputs.version }} - IMAGE_NAMESPACE: ${{ steps.meta.outputs.image_namespace }} - IMAGE_NAME: ${{ inputs.image_name }} - GITHUB_REPOSITORY: ${{ github.repository }} diff --git a/.github/workflows/build-docker-image.yml b/.github/workflows/build-docker-image.yml deleted file mode 100644 index baa33d148..000000000 --- a/.github/workflows/build-docker-image.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Build Docker Image - -on: - pull_request: - push: - branches: - - main - -jobs: - build: - permissions: - contents: read - uses: ./.github/workflows/build-and-publish.yml - with: - publish: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 000000000..38be171a4 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,59 @@ +name: Build + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + check-latest: true + cache: true + + - name: Get build date + id: date + run: echo "date=$(date '+%F-%T')" >> "$GITHUB_OUTPUT" + + - name: Get build unix timestamp + id: timestamp + run: echo "timestamp=$(date '+%s')" >> "$GITHUB_OUTPUT" + + - name: Get git branch + id: branch + run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" + + - name: Get build platform + id: platform + run: echo "platform=$(go version | cut -d ' ' -f 4)" >> "$GITHUB_OUTPUT" + + - name: Get Go version + id: go + run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" + + - name: Run GoReleaser (snapshot) + uses: goreleaser/goreleaser-action@v6 + with: + version: v2.12.7 + args: release --snapshot --clean + env: + BUILD_DATE: ${{ steps.date.outputs.date }} + BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} + GIT_BRANCH: ${{ steps.branch.outputs.branch }} + BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} + GO_VERSION: ${{ steps.go.outputs.go }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index dfd79cedd..0bcd5d666 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,10 +11,50 @@ permissions: jobs: release: - uses: ./.github/workflows/build-and-publish.yml - with: - publish: true - version: ${{ github.ref_name }} - secrets: - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version-file: "go.mod" + check-latest: true + cache: true + + - name: Get build date + id: date + run: echo "date=$(date '+%F-%T')" >> "$GITHUB_OUTPUT" + + - name: Get build unix timestamp + id: timestamp + run: echo "timestamp=$(date '+%s')" >> "$GITHUB_OUTPUT" + + - name: Get git branch + id: branch + run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" + + - name: Get build platform + id: platform + run: echo "platform=$(go version | cut -d ' ' -f 4)" >> "$GITHUB_OUTPUT" + + - name: Get Go version + id: go + run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v6 + with: + version: v2.12.7 + args: release + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + BUILD_DATE: ${{ steps.date.outputs.date }} + BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} + GIT_BRANCH: ${{ steps.branch.outputs.branch }} + BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} + GO_VERSION: ${{ steps.go.outputs.go }} diff --git a/.github/workflows/tag-latest.yml b/.github/workflows/tag-latest.yml deleted file mode 100644 index 75f33ea1d..000000000 --- a/.github/workflows/tag-latest.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Tag Docker Image as Latest - -on: - workflow_dispatch: - inputs: - tag: - description: 'Docker image tag to promote to latest (e.g., 1.2.3 or v1.2.3)' - required: false - type: string - sha: - description: 'Docker image SHA to promote to latest (e.g., sha256:abc123...)' - required: false - type: string - -permissions: - contents: read - -jobs: - update-latest: - name: Update Latest Tag - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - persist-credentials: false - fetch-depth: 0 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - - name: Tag image as latest - env: - INPUT_TAG: ${{ inputs.tag }} - INPUT_SHA: ${{ inputs.sha }} - run: node .github/scripts/tag-latest.js diff --git a/Dockerfile b/Dockerfile index 46f601a8b..ca9d986f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,16 +1,9 @@ -# Intermediate stage to normalize goreleaser output paths -# This copies both architecture binaries and renames them to clean paths, -# allowing the final stage to select the correct binary using TARGETARCH -FROM scratch AS dist -COPY dist/nix_linux_amd64_v1/temporal /dist/amd64/temporal -COPY dist/nix_linux_arm64_v8.0/temporal /dist/arm64/temporal - FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412 ARG TARGETARCH RUN apk add --no-cache ca-certificates tzdata -COPY --from=dist /dist/${TARGETARCH}/temporal /usr/local/bin/temporal +COPY dist/${TARGETARCH}/temporal /usr/local/bin/temporal RUN adduser -u 1000 -D temporal USER temporal diff --git a/docker-bake.hcl b/docker-bake.hcl index fa2130e2c..66156cf2e 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -20,13 +20,18 @@ variable "VERSION" { default = "dev" } +variable "TAG_LATEST" { + default = false +} + target "cli" { dockerfile = "Dockerfile" context = "." - tags = [ + tags = compact([ "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${IMAGE_SHA_TAG}", "${IMAGE_NAMESPACE}/${IMAGE_NAME}:${VERSION}", - ] + TAG_LATEST ? "${IMAGE_NAMESPACE}/${IMAGE_NAME}:latest" : "", + ]) platforms = ["linux/amd64", "linux/arm64"] labels = { "org.opencontainers.image.title" = "temporal" From c31c2f9014114c887d0670d7f66ae8a8e94c3074 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:44:12 -0600 Subject: [PATCH 29/31] add option to publish a docker image --- .../workflows/build-and-publish-docker.yml | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/build-and-publish-docker.yml b/.github/workflows/build-and-publish-docker.yml index 1a9075f71..09eed90fd 100644 --- a/.github/workflows/build-and-publish-docker.yml +++ b/.github/workflows/build-and-publish-docker.yml @@ -7,6 +7,11 @@ on: description: 'Version tag for the Docker image (e.g., 1.2.3 or v1.2.3)' required: true type: string + publish: + description: 'Push the image to Docker Hub' + required: false + type: boolean + default: true tag_latest: description: 'Also tag this image as latest' required: false @@ -78,12 +83,14 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Log in to Docker Hub + if: inputs.publish uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image + if: inputs.publish run: | docker buildx bake \ --file docker-bake.hcl \ @@ -97,3 +104,18 @@ jobs: IMAGE_NAMESPACE: temporalio IMAGE_NAME: temporal GITHUB_REPOSITORY: ${{ github.repository }} + + - name: Build Docker image (no push) + if: ${{ !inputs.publish }} + run: | + docker buildx bake \ + --file docker-bake.hcl \ + cli + env: + CLI_SHA: ${{ steps.meta.outputs.cli_sha }} + IMAGE_SHA_TAG: ${{ steps.meta.outputs.image_sha_tag }} + VERSION: ${{ steps.meta.outputs.version }} + TAG_LATEST: ${{ steps.meta.outputs.tag_latest }} + IMAGE_NAMESPACE: temporalio + IMAGE_NAME: temporal + GITHUB_REPOSITORY: ${{ github.repository }} From c19e100c233a6b474814fcc986f505b87ce6a496 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Tue, 2 Dec 2025 18:52:59 -0600 Subject: [PATCH 30/31] remove go releaser builds on pr and main --- .github/workflows/build.yml | 59 ------------------------------------- 1 file changed, 59 deletions(-) delete mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 38be171a4..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,59 +0,0 @@ -name: Build - -on: - pull_request: - push: - branches: - - main - -permissions: - contents: read - -jobs: - build: - name: Build - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - fetch-depth: 0 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version-file: "go.mod" - check-latest: true - cache: true - - - name: Get build date - id: date - run: echo "date=$(date '+%F-%T')" >> "$GITHUB_OUTPUT" - - - name: Get build unix timestamp - id: timestamp - run: echo "timestamp=$(date '+%s')" >> "$GITHUB_OUTPUT" - - - name: Get git branch - id: branch - run: echo "branch=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT" - - - name: Get build platform - id: platform - run: echo "platform=$(go version | cut -d ' ' -f 4)" >> "$GITHUB_OUTPUT" - - - name: Get Go version - id: go - run: echo "go=$(go version | cut -d ' ' -f 3)" >> "$GITHUB_OUTPUT" - - - name: Run GoReleaser (snapshot) - uses: goreleaser/goreleaser-action@v6 - with: - version: v2.12.7 - args: release --snapshot --clean - env: - BUILD_DATE: ${{ steps.date.outputs.date }} - BUILD_TS_UNIX: ${{ steps.timestamp.outputs.timestamp }} - GIT_BRANCH: ${{ steps.branch.outputs.branch }} - BUILD_PLATFORM: ${{ steps.platform.outputs.platform }} - GO_VERSION: ${{ steps.go.outputs.go }} From 0f2c2043ca595bbf10d2e9d997b147818d30edc2 Mon Sep 17 00:00:00 2001 From: "alex.stanfield" <13949480+chaptersix@users.noreply.github.com> Date: Tue, 2 Dec 2025 19:01:17 -0600 Subject: [PATCH 31/31] fix bug. collect proper cli sha --- .../workflows/build-and-publish-docker.yml | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build-and-publish-docker.yml b/.github/workflows/build-and-publish-docker.yml index 09eed90fd..00bd416d8 100644 --- a/.github/workflows/build-and-publish-docker.yml +++ b/.github/workflows/build-and-publish-docker.yml @@ -30,9 +30,8 @@ jobs: uses: actions/checkout@v6 with: persist-credentials: false - fetch-depth: 0 - - name: Get build metadata + - name: Get build metadata from release id: meta env: INPUT_VERSION: ${{ inputs.version }} @@ -43,19 +42,22 @@ jobs: const inputVersion = process.env.INPUT_VERSION; const inputTagLatest = process.env.INPUT_TAG_LATEST; - const { execSync } = require('child_process'); - const cliSha = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim(); - const imageShaTag = execSync('git rev-parse --short HEAD', { encoding: 'utf8' }).trim(); + const version = inputVersion.startsWith('v') ? inputVersion.slice(1) : inputVersion; + const releaseTag = inputVersion.startsWith('v') ? inputVersion : `v${inputVersion}`; + + const { data: release } = await github.rest.repos.getReleaseByTag({ + owner: context.repo.owner, + repo: context.repo.repo, + tag: releaseTag + }); + + const cliSha = release.target_commitish; + const imageShaTag = cliSha.substring(0, 7); core.setOutput('cli_sha', cliSha); core.setOutput('image_sha_tag', imageShaTag); - - const version = inputVersion.startsWith('v') ? inputVersion.slice(1) : inputVersion; core.setOutput('version', version); - - const releaseTag = inputVersion.startsWith('v') ? inputVersion : `v${inputVersion}`; core.setOutput('release_tag', releaseTag); - core.setOutput('tag_latest', inputTagLatest === 'true'); - name: Download release assets