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..d8bde874c 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,6 +69,25 @@ 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 @@ -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/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"