New Docker Build GH Actions (#8825)

## What changed?
* updates goreleaser to v2
* add GHA for build admin-tools and server images within this repo.
`docker-builds` will only be used for building pre 1.30 images
* added GHA to promote docker builds from temporaliotest to temproalio
docker image repos
* trivy security scanning gates for image promotion (pulled rom
docker-builds repo). The gate can be overridden

## Why?
* we decided to move away from building images in `docker-builds`. The
complexity is not needed
* updates goreleaser to v2 because the v1 definitions might be no longer
supported at some point and this is a good time to do it
* I used go scripts for the more complex flows instead of js or bash so
we don't introduce another language contributors need to be familiar
with. IMO the js or bash I did use it simple enough to understand.

## How did you test it?
It passes in CI. 

## Potential risks
* these build pipelines are only compatible with the new docker images. 
* merging this PR may break our nightly tests. Will double check before
merging
* flows that are not triggered by opening a PR are untested and
therefore not completely validated
This commit is contained in:
Alex Stanfield
2025-12-18 14:30:58 -06:00
committed by GitHub
parent d1a5d3368b
commit 85635674d7
24 changed files with 1479 additions and 104 deletions

View File

@@ -0,0 +1,47 @@
name: Build Binaries
description: Build Temporal binaries using GoReleaser
inputs:
snapshot:
description: "Use snapshot mode (true) or release mode (false). Only applies to release command (build always uses snapshot)."
required: false
default: "true"
single-arch:
description: "Single architecture to build (amd64 or arm64, empty for all). Only used with build command, ignored if release is true."
required: false
default: ""
release:
description: "Use release command (true) or build command (false). When true, single-arch is ignored and snapshot is respected."
required: false
default: "false"
runs:
using: composite
steps:
- name: Setup Go
uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
cache: true
- name: Run GoReleaser (release)
if: inputs.release == 'true'
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: v2.13.1
args: release ${{ inputs.snapshot == 'true' && '--snapshot --skip=publish' || '' }} --clean
env:
GITHUB_TOKEN: ${{ github.token }}
- name: Run GoReleaser (build)
if: inputs.release != 'true'
uses: goreleaser/goreleaser-action@v6
with:
distribution: goreleaser
version: v2.13.1
args: build --snapshot ${{ inputs.single-arch != '' && '--single-target' || '' }}
env:
GITHUB_TOKEN: ${{ github.token }}
GOOS: ${{ inputs.single-arch != '' && 'linux' || '' }}
GOARCH: ${{ inputs.single-arch }}

View File

@@ -0,0 +1,133 @@
name: Build Docker Images
description: |
Build Temporal Docker images from binaries.
Prerequisites:
- The build-binaries action must run before this action to produce the binaries.
inputs:
push:
description: "Push images to Docker Hub"
required: false
default: "false"
tag-latest:
description: "Tag images as latest"
required: false
default: "false"
platform:
description: "Single platform to build (e.g., linux/amd64)"
required: false
default: ""
load:
description: "Load image into local Docker daemon (only works with linux/amd64 - the runner architecture)"
required: false
default: "false"
cli-version:
description: "Temporal CLI version to download"
required: false
default: "1.5.1"
alpine-tag:
description: "Alpine base image tag with digest"
required: false
default: "3.23@sha256:c78ded0fee4493809c8ca71d4a6057a46237763d952fae15ea418f6d14137f2d"
dockerhub-username:
description: "Docker Hub username"
required: false
dockerhub-token:
description: "Docker Hub token"
required: false
runs:
using: composite
steps:
- name: Build docker-build-helper
shell: bash
working-directory: ${{ github.workspace }}/.github/actions/build-docker-images/scripts
run: |
go build -o docker-build-helper .
- name: Validate and sanitize branch name for Docker tag
id: sanitize-tag
shell: bash
working-directory: ${{ github.workspace }}
run: |
.github/actions/build-docker-images/scripts/docker-build-helper sanitize-tag
- name: Organize binaries for Docker
id: organize-binaries
shell: bash
working-directory: ${{ github.workspace }}
env:
PLATFORM: ${{ inputs.platform }}
run: |
.github/actions/build-docker-images/scripts/docker-build-helper organize-binaries
- name: Download Temporal CLI
shell: bash
working-directory: ${{ github.workspace }}
env:
AVAILABLE_ARCHS: ${{ steps.organize-binaries.outputs.available-archs }}
CLI_VERSION: ${{ inputs.cli-version }}
run: |
.github/actions/build-docker-images/scripts/docker-build-helper download-cli
- name: Extract server version from binary
id: extract-version
shell: bash
working-directory: ${{ github.workspace }}
run: |
.github/actions/build-docker-images/scripts/docker-build-helper extract-version
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to Docker Hub
if: inputs.push == 'true'
uses: docker/login-action@v3
with:
username: ${{ inputs.dockerhub-username }}
password: ${{ inputs.dockerhub-token }}
- name: Build Docker images
if: inputs.push != 'true'
shell: bash
working-directory: ${{ github.workspace }}
env:
IMAGE_REPO: temporaliotest
IMAGE_SHA_TAG: ${{ github.sha }}
IMAGE_BRANCH_TAG: ${{ steps.sanitize-tag.outputs.tag }}
TEMPORAL_SHA: ${{ github.sha }}
TAG_LATEST: ${{ inputs.tag-latest }}
ALPINE_TAG: ${{ inputs.alpine-tag }}
SERVER_VERSION: ${{ steps.extract-version.outputs.server-version }}
run: |
if [ -n "${{ inputs.platform }}" ]; then
docker buildx bake \
--set "*.platform=${{ inputs.platform }}" \
${{ inputs.load == 'true' && '--load' || '' }} \
-f docker/docker-bake.hcl \
server admin-tools
else
docker buildx bake \
${{ inputs.load == 'true' && '--load' || '' }} \
-f docker/docker-bake.hcl \
server admin-tools
fi
- name: Build and push Docker images
if: inputs.push == 'true'
shell: bash
working-directory: ${{ github.workspace }}
env:
IMAGE_REPO: temporaliotest
IMAGE_SHA_TAG: ${{ github.sha }}
IMAGE_BRANCH_TAG: ${{ steps.sanitize-tag.outputs.tag }}
TEMPORAL_SHA: ${{ github.sha }}
TAG_LATEST: ${{ inputs.tag-latest }}
ALPINE_TAG: ${{ inputs.alpine-tag }}
SERVER_VERSION: ${{ steps.extract-version.outputs.server-version }}
run: |
docker buildx bake \
--push \
-f docker/docker-bake.hcl \
server admin-tools

View File

@@ -0,0 +1 @@
docker-build-helper

View File

@@ -0,0 +1,561 @@
package main
import (
"fmt"
"io"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
)
var validArchs = []string{"amd64", "arm64"}
// defaultCliVersion should be updated to the latest cli version
const defaultCliVersion = "1.5.1"
func main() {
if len(os.Args) < 2 {
fmt.Fprintf(os.Stderr, "Usage: %s <command>\n", os.Args[0])
fmt.Fprintf(os.Stderr, "Commands:\n")
fmt.Fprintf(os.Stderr, " sanitize-tag - Sanitize branch name for Docker tag\n")
fmt.Fprintf(os.Stderr, " organize-binaries - Organize binaries for Docker\n")
fmt.Fprintf(os.Stderr, " download-cli - Download Temporal CLI\n")
fmt.Fprintf(os.Stderr, " extract-version - Extract version from temporal-server binary\n")
os.Exit(1)
}
command := os.Args[1]
switch command {
case "sanitize-tag":
if err := sanitizeTag(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
case "organize-binaries":
if err := organizeBinaries(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
case "download-cli":
if err := downloadCLI(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
case "extract-version":
if err := extractVersion(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
default:
fmt.Fprintf(os.Stderr, "Unknown command: %s\n", command)
os.Exit(1)
}
}
// sanitizeTag sanitizes branch names for Docker tags
func sanitizeTag() error {
// Get GITHUB_REF from environment
ref := os.Getenv("GITHUB_REF")
if ref == "" {
return fmt.Errorf("GITHUB_REF environment variable not set")
}
// Remove refs/heads/ or refs/tags/ prefix
ref = strings.TrimPrefix(ref, "refs/heads/")
ref = strings.TrimPrefix(ref, "refs/tags/")
// Sanitize ref name first
// Replace any non-alphanumeric (except .-_) with dash
reg := regexp.MustCompile(`[^a-zA-Z0-9._-]`)
sanitizedRef := reg.ReplaceAllString(ref, "-")
// Collapse multiple consecutive dashes
multiDashReg := regexp.MustCompile(`-+`)
sanitizedRef = multiDashReg.ReplaceAllString(sanitizedRef, "-")
// Remove leading and trailing dashes
sanitizedRef = strings.Trim(sanitizedRef, "-")
// Prefix with "branch-" for branch builds
safeTag := fmt.Sprintf("branch-%s", sanitizedRef)
// Docker tags must be lowercase
safeTag = strings.ToLower(safeTag)
// Truncate to 128 characters (Docker tag limit)
if len(safeTag) > 128 {
safeTag = safeTag[:128]
}
if safeTag == "" {
return fmt.Errorf("failed to generate valid Docker tag from branch name")
}
fmt.Printf("Original: %s\n", ref)
fmt.Printf("Sanitized: %s\n", safeTag)
// Set output for GitHub Actions
if err := setOutput("tag", safeTag); err != nil {
return fmt.Errorf("failed to set output: %w", err)
}
return nil
}
// organizeBinaries organizes binaries for Docker builds
func organizeBinaries() error {
// Determine target architectures based on PLATFORM environment variable
platform := os.Getenv("PLATFORM")
var archs []string
if platform != "" {
// Parse platform (e.g., "linux/amd64" -> "amd64")
parts := strings.Split(platform, "/")
if len(parts) != 2 {
return fmt.Errorf("invalid platform format: %s (expected format: os/arch)", platform)
}
arch := parts[1]
// Check if arch is in valid list
found := false
for _, validArch := range validArchs {
if arch == validArch {
found = true
break
}
}
if !found {
return fmt.Errorf("architecture %s not in supported list: %v", arch, validArchs)
}
archs = []string{arch}
fmt.Printf("Single architecture build: %s\n", arch)
} else {
// Default to all architectures
archs = []string{"amd64", "arm64"}
fmt.Println("Multi-architecture build: amd64, arm64")
}
// Admin tool binaries (for admin-tools image)
adminToolBinaries := []string{
"temporal-cassandra-tool",
"temporal-sql-tool",
"temporal-elasticsearch-tool",
"tdbg",
}
// Server binaries (for server image)
serverBinaries := []string{
"temporal-server",
}
// All binaries to copy
binaries := append(adminToolBinaries, serverBinaries...)
// Validate architecture and binary names
archReg := regexp.MustCompile(`^[a-z0-9]+$`)
for _, arch := range archs {
if !archReg.MatchString(arch) {
return fmt.Errorf("invalid architecture name: %s", arch)
}
}
binReg := regexp.MustCompile(`^[a-z0-9-]+$`)
for _, binary := range binaries {
if !binReg.MatchString(binary) {
return fmt.Errorf("invalid binary name: %s", binary)
}
}
// Create architecture directories
for _, arch := range archs {
dir := filepath.Join("docker", "build", arch)
if err := validatePath(dir, "docker/build"); err != nil {
return err
}
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("failed to create directory %s: %w", dir, err)
}
}
// Map GoReleaser dist structure to build structure
archMap := map[string]string{
"amd64": "amd64_v1",
"arm64": "arm64",
}
// Copy binaries
for _, binary := range binaries {
for _, arch := range archs {
distArch := archMap[arch]
distPath := filepath.Join("dist", fmt.Sprintf("%s_linux_%s", binary, distArch), binary)
buildPath := filepath.Join("docker", "build", arch, binary)
// Validate paths before file operations
if err := validatePath(distPath, "dist"); err != nil {
return fmt.Errorf("invalid dist path: %w", err)
}
if err := validatePath(buildPath, "docker/build"); err != nil {
return fmt.Errorf("invalid build path: %w", err)
}
if _, err := os.Stat(distPath); err == nil {
if err := copyFile(distPath, buildPath); err != nil {
return fmt.Errorf("failed to copy %s to %s: %w", distPath, buildPath, err)
}
if err := os.Chmod(buildPath, 0755); err != nil {
return fmt.Errorf("failed to chmod %s: %w", buildPath, err)
}
fmt.Printf("Copied %s -> %s\n", distPath, buildPath)
} else {
return fmt.Errorf("binary not found: %s for architecture %s (expected at %s)", binary, arch, distPath)
}
}
}
// Copy schema directory for admin-tools
schemaDir := filepath.Join("docker", "build", "temporal", "schema")
if err := validatePath(schemaDir, "docker/build"); err != nil {
return err
}
if err := os.MkdirAll(schemaDir, 0755); err != nil {
return fmt.Errorf("failed to create schema directory: %w", err)
}
// Copy all schema files recursively with path validation
if _, err := os.Stat("schema"); err == nil {
if err := copyRecursive("schema", schemaDir); err != nil {
return fmt.Errorf("failed to copy schema directory: %w", err)
}
fmt.Println("Copied schema directory")
}
// Validate required binaries for Docker images
fmt.Println("\nValidating required binaries for Docker images...")
// Check which architectures have binaries
var availableArchs []string
for _, arch := range archs {
testBinary := filepath.Join("docker", "build", arch, "temporal-server")
if _, err := os.Stat(testBinary); err == nil {
availableArchs = append(availableArchs, arch)
}
}
if len(availableArchs) == 0 {
return fmt.Errorf("❌ No binaries found for any architecture")
}
fmt.Printf("Found binaries for architectures: %s\n", strings.Join(availableArchs, ", "))
// Validate that each available architecture has all required binaries
missingFiles := false
for _, arch := range availableArchs {
for _, binary := range binaries {
binaryPath := filepath.Join("docker", "build", arch, binary)
if _, err := os.Stat(binaryPath); err != nil {
fmt.Fprintf(os.Stderr, "Error: Missing %s\n", binaryPath)
missingFiles = true
}
}
}
// Validate schema directory exists
if _, err := os.Stat(filepath.Join("docker", "build", "temporal", "schema")); err != nil {
fmt.Fprintln(os.Stderr, "Error: Missing docker/build/temporal/schema directory")
missingFiles = true
}
if missingFiles {
return fmt.Errorf("❌ Binary validation failed")
}
fmt.Println("✓ All required binaries present for available architectures")
// Export available architectures for Docker build
if err := setOutput("available-archs", strings.Join(availableArchs, ",")); err != nil {
return fmt.Errorf("failed to set output: %w", err)
}
return nil
}
// downloadCLI downloads the Temporal CLI for available architectures
func downloadCLI() error {
// Get available architectures from environment or input
availableArchsStr := os.Getenv("AVAILABLE_ARCHS")
if availableArchsStr == "" {
return fmt.Errorf("AVAILABLE_ARCHS environment variable not set")
}
availableArchs := strings.Split(availableArchsStr, ",")
// Filter to only valid architectures
var validAvailableArchs []string
for _, arch := range availableArchs {
arch = strings.TrimSpace(arch)
for _, validArch := range validArchs {
if arch == validArch {
validAvailableArchs = append(validAvailableArchs, arch)
break
}
}
}
if len(validAvailableArchs) == 0 {
return fmt.Errorf("no valid architectures found in: %s", availableArchsStr)
}
for _, arch := range validAvailableArchs {
if err := downloadCLIForArch(arch); err != nil {
return fmt.Errorf("failed to download CLI for %s: %w", arch, err)
}
}
return nil
}
func downloadCLIForArch(arch string) error {
cliVersion := os.Getenv("CLI_VERSION")
if cliVersion == "" {
cliVersion = defaultCliVersion
}
tarballName := fmt.Sprintf("temporal_cli_%s_linux_%s.tar.gz", cliVersion, arch)
downloadURL := fmt.Sprintf("https://github.com/temporalio/cli/releases/download/v%s/%s", cliVersion, tarballName)
fmt.Printf("Downloading Temporal CLI v%s for %s from %s\n", cliVersion, arch, downloadURL)
tempDir := filepath.Join(os.TempDir(), fmt.Sprintf("temporal-cli-%s", arch))
tarballPath := filepath.Join(os.TempDir(), tarballName)
// Download tarball
if err := downloadFile(downloadURL, tarballPath); err != nil {
return fmt.Errorf("failed to download: %w", err)
}
defer os.Remove(tarballPath)
// Create temp directory
if err := os.MkdirAll(tempDir, 0755); err != nil {
return fmt.Errorf("failed to create temp directory: %w", err)
}
defer os.RemoveAll(tempDir)
// Extract tarball
cmd := exec.Command("tar", "-xzf", tarballPath, "-C", tempDir)
if output, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to extract: %w\nOutput: %s", err, string(output))
}
// Move to build directory
destDir := filepath.Join("docker", "build", arch)
if err := validatePath(destDir, "docker/build"); err != nil {
return fmt.Errorf("invalid build directory path: %w", err)
}
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("failed to create build directory: %w", err)
}
sourcePath := filepath.Join(tempDir, "temporal")
destPath := filepath.Join(destDir, "temporal")
if err := validatePath(destPath, "docker/build"); err != nil {
return fmt.Errorf("invalid destination path: %w", err)
}
if err := os.Rename(sourcePath, destPath); err != nil {
// If rename fails (e.g., cross-device), try copy and delete
if err := copyFile(sourcePath, destPath); err != nil {
return fmt.Errorf("failed to copy binary: %w", err)
}
os.Remove(sourcePath)
}
if err := os.Chmod(destPath, 0755); err != nil {
return fmt.Errorf("failed to chmod binary: %w", err)
}
fmt.Printf("Installed Temporal CLI to %s\n", destPath)
return nil
}
// extractVersion extracts the version from the temporal-server binary
func extractVersion() error {
// Try to find the temporal-server binary in any available architecture directory
var binaryPath string
for _, arch := range validArchs {
candidatePath := filepath.Join("docker", "build", arch, "temporal-server")
if _, err := os.Stat(candidatePath); err == nil {
binaryPath = candidatePath
break
}
}
if binaryPath == "" {
return fmt.Errorf("temporal-server binary not found in docker/build/{amd64,arm64}/")
}
fmt.Printf("Extracting version from %s\n", binaryPath)
// Run the binary with --version flag
cmd := exec.Command(binaryPath, "--version")
output, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to run %s --version: %w", binaryPath, err)
}
// Parse the version from output like "temporal version 1.29.0"
outputStr := strings.TrimSpace(string(output))
versionRegex := regexp.MustCompile(`^temporal version (\d+\.\d+\.\d+)`)
matches := versionRegex.FindStringSubmatch(outputStr)
if len(matches) < 2 {
return fmt.Errorf("failed to parse version from output: %s", outputStr)
}
version := matches[1]
fmt.Printf("Extracted version: %s\n", version)
// Set output for GitHub Actions
if err := setOutput("server-version", version); err != nil {
return fmt.Errorf("failed to set output: %w", err)
}
return nil
}
// Helper functions
func setOutput(name, value string) error {
outputFile := os.Getenv("GITHUB_OUTPUT")
if outputFile == "" {
return fmt.Errorf("GITHUB_OUTPUT environment variable not set")
}
f, err := os.OpenFile(outputFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer f.Close()
_, err = fmt.Fprintf(f, "%s=%s\n", name, value)
return err
}
func validatePath(path, allowedPrefix string) error {
// Clean and resolve paths
normalized := filepath.Clean(path)
resolved, err := filepath.Abs(normalized)
if err != nil {
return fmt.Errorf("failed to resolve path: %w", err)
}
allowedResolved, err := filepath.Abs(allowedPrefix)
if err != nil {
return fmt.Errorf("failed to resolve allowed prefix: %w", err)
}
// Check for path traversal
if strings.Contains(normalized, "..") {
return fmt.Errorf("path traversal detected in: %s", path)
}
// Ensure path is within allowed directory
if !strings.HasPrefix(resolved, allowedResolved) {
return fmt.Errorf("path outside allowed directory: %s", path)
}
return nil
}
func copyFile(src, dst string) error {
sourceFile, err := os.Open(src)
if err != nil {
return err
}
defer sourceFile.Close()
destFile, err := os.Create(dst)
if err != nil {
return err
}
defer destFile.Close()
_, err = io.Copy(destFile, sourceFile)
return err
}
func copyRecursive(src, dst string) error {
// Validate paths
if err := validatePath(src, "schema"); err != nil {
return err
}
if err := validatePath(dst, "docker/build"); err != nil {
return err
}
srcInfo, err := os.Stat(src)
if err != nil {
return err
}
if srcInfo.IsDir() {
// Create destination directory
if err := os.MkdirAll(dst, 0755); err != nil {
return err
}
// Read directory entries
entries, err := os.ReadDir(src)
if err != nil {
return err
}
// Copy each entry
for _, entry := range entries {
// Validate item name to prevent directory traversal
if strings.Contains(entry.Name(), "..") || strings.Contains(entry.Name(), "/") || strings.Contains(entry.Name(), "\\") {
return fmt.Errorf("invalid file name: %s", entry.Name())
}
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if err := copyRecursive(srcPath, dstPath); err != nil {
return err
}
}
} else {
// Copy file
if err := copyFile(src, dst); err != nil {
return err
}
}
return nil
}
func downloadFile(url, fpath string) error {
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("bad status: %s", resp.Status)
}
out, err := os.Create(fpath)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, resp.Body)
return err
}

108
.github/actions/trivy-scan/action.yml vendored Normal file
View File

@@ -0,0 +1,108 @@
name: "Trivy Security Scan"
description: "Run Trivy vulnerability scanner on Docker images"
inputs:
image-tags:
description: "Image tags (one per line or comma-separated). First tag will be used for scanning."
required: true
image-name:
description: "Image name without tag (e.g., temporaliotest/server)"
required: true
outputs:
scan-result:
description: "Result of the scan (pass or fail)"
value: ${{ steps.evaluate.outputs.result }}
critical-count:
description: "Number of critical vulnerabilities found"
value: ${{ steps.evaluate.outputs.critical-count }}
high-count:
description: "Number of high vulnerabilities found"
value: ${{ steps.evaluate.outputs.high-count }}
runs:
using: "composite"
steps:
- name: Set variables
id: vars
shell: bash
env:
IMAGE_NAME: ${{ inputs.image-name }}
IMAGE_TAGS: ${{ inputs.image-tags }}
run: |
# Get first tag
tag=$(echo "$IMAGE_TAGS" | head -1)
# Construct image reference
image_ref="${IMAGE_NAME}:${tag}"
echo "image-ref=${image_ref}" >> $GITHUB_OUTPUT
# Create safe name for artifacts
name=$(echo "$IMAGE_NAME" | sed 's/.*\///; s/[^a-zA-Z0-9._-]/-/g')
echo "safe-name=${name}" >> $GITHUB_OUTPUT
echo "artifact-name=trivy-${name}-results" >> $GITHUB_OUTPUT
- name: Install Trivy
uses: aquasecurity/setup-trivy@v0.2.4
with:
version: v0.65.0
- name: Scan Container Image
id: scan
shell: bash
env:
IMAGE_REF: ${{ steps.vars.outputs.image-ref }}
SAFE_NAME: ${{ steps.vars.outputs.safe-name }}
run: |
echo "Scanning $IMAGE_REF for CRITICAL,HIGH vulnerabilities..."
# Scan and output table format for logs
trivy image \
--severity CRITICAL,HIGH \
--format table \
--no-progress \
"$IMAGE_REF" | tee trivy-scan-${SAFE_NAME}.txt
echo ""
echo "Generating detailed JSON report..."
# Scan and output JSON format for parsing
trivy image \
--severity CRITICAL,HIGH \
--format json \
--no-progress \
"$IMAGE_REF" > trivy-scan-${SAFE_NAME}.json
- name: Evaluate scan results
id: evaluate
shell: bash
env:
SAFE_NAME: ${{ steps.vars.outputs.safe-name }}
FAIL_ON_VULNS: ${{ inputs.fail-on-vulnerabilities }}
run: |
# Count vulnerabilities by severity
CRITICAL_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="CRITICAL")] | length' trivy-scan-${SAFE_NAME}.json)
HIGH_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="HIGH")] | length' trivy-scan-${SAFE_NAME}.json)
MEDIUM_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="MEDIUM")] | length' trivy-scan-${SAFE_NAME}.json)
LOW_COUNT=$(jq '[.Results[]?.Vulnerabilities[]? | select(.Severity=="LOW")] | length' trivy-scan-${SAFE_NAME}.json)
echo "critical-count=${CRITICAL_COUNT}" >> $GITHUB_OUTPUT
echo "high-count=${HIGH_COUNT}" >> $GITHUB_OUTPUT
echo ""
echo "=== Vulnerability Summary ==="
echo "Critical: $CRITICAL_COUNT"
echo "High: $HIGH_COUNT"
echo "Medium: $MEDIUM_COUNT"
echo "Low: $LOW_COUNT"
echo "============================"
echo ""
# Set result status without failing
if [ "$CRITICAL_COUNT" -gt 0 ] || [ "$HIGH_COUNT" -gt 0 ]; then
echo "result=fail" >> $GITHUB_OUTPUT
echo "⚠️ Security vulnerabilities found!"
else
echo "result=pass" >> $GITHUB_OUTPUT
echo "✓ No critical or high vulnerabilities found."
fi

View File

@@ -1,23 +0,0 @@
FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412
ARG TARGETARCH
RUN apk add --no-cache \
ca-certificates \
tzdata && addgroup -g 1000 temporal && \
adduser -u 1000 -G temporal -D temporal
# Copy all admin tool binaries:
# - temporal (CLI)
# - temporal-server
# - temporal-cassandra-tool
# - temporal-sql-tool
# - temporal-elasticsearch-tool
# - tdbg
COPY --chmod=755 ./build/${TARGETARCH}/* /usr/local/bin/
COPY ./build/temporal/schema /etc/temporal/schema
USER temporal
CMD ["sh", "-c", "trap exit INT HUP TERM; sleep infinity"]

62
.github/workflows/build-and-publish.yml vendored Normal file
View File

@@ -0,0 +1,62 @@
name: Build and Publish
on:
push:
branches:
- main
- cloud/*
- feature/*
- release/*
permissions:
contents: read
jobs:
build-and-push-docker:
runs-on: ubuntu-latest
# Only push for main, cloud, release branches (not feature)
if: |
github.ref == 'refs/heads/main' ||
startsWith(github.ref, 'refs/heads/cloud/') ||
startsWith(github.ref, 'refs/heads/release/')
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Build binaries
uses: ./.github/actions/build-binaries
with:
snapshot: true
- name: Build and push Docker images
uses: ./.github/actions/build-docker-images
with:
push: true
tag-latest: ${{ github.ref == 'refs/heads/main' }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
# For feature branches, just build (no push)
build-docker-feature:
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/heads/feature/')
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Build binaries
uses: ./.github/actions/build-binaries
with:
snapshot: true
- name: Build Docker images
uses: ./.github/actions/build-docker-images
with:
push: false
tag-latest: false

View File

@@ -13,7 +13,7 @@ jobs:
steps:
- name: Validate PR description for placeholder lines or empty sections
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const pr = await github.rest.pulls.get({

View File

@@ -44,7 +44,7 @@ jobs:
owner: ${{ github.repository_owner }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
persist-credentials: true
token: ${{ steps.generate_token.outputs.token }}
@@ -190,7 +190,7 @@ jobs:
owner: ${{ github.repository_owner }}
- name: Checkout
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
persist-credentials: true
token: ${{ steps.generate_token.outputs.token }}

View File

@@ -0,0 +1,105 @@
name: Manual Docker Build
on:
workflow_dispatch:
inputs:
ref:
description: "Git ref (branch, tag, or SHA) to build from"
required: true
default: "main"
cli-version:
description: "Temporal CLI version to include in images"
required: true
default: "1.5.1"
alpine-tag:
description: "Alpine base image tag with digest (e.g., 3.23@sha256:...)"
required: true
default: "3.23@sha256:c78ded0fee4493809c8ca71d4a6057a46237763d952fae15ea418f6d14137f2d"
push:
description: "Push images to Docker Hub"
required: true
type: boolean
default: false
tag-latest:
description: "Tag images as latest"
required: true
type: boolean
default: false
platform:
description: "Platform to build (leave empty for multi-arch, or specify linux/amd64 or linux/arm64)"
required: false
default: ""
snapshot:
description: "Build in snapshot mode (for non-release builds)"
required: true
type: boolean
default: true
permissions:
contents: read
jobs:
build-docker:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ inputs.ref }}
fetch-depth: 0
- name: Determine single-arch parameter
id: arch-param
run: |
if [ -n "${{ inputs.platform }}" ]; then
# Extract arch from platform (e.g., linux/amd64 -> amd64)
ARCH=$(echo "${{ inputs.platform }}" | cut -d'/' -f2)
echo "single-arch=${ARCH}" >> "$GITHUB_OUTPUT"
else
echo "single-arch=" >> "$GITHUB_OUTPUT"
fi
- name: Build binaries
uses: ./.github/actions/build-binaries
with:
snapshot: ${{ inputs.snapshot }}
single-arch: ${{ steps.arch-param.outputs.single-arch }}
- name: Build Docker images
if: ${{ !inputs.push }}
uses: ./.github/actions/build-docker-images
with:
push: false
tag-latest: ${{ inputs.tag-latest }}
platform: ${{ inputs.platform }}
cli-version: ${{ inputs.cli-version }}
alpine-tag: ${{ inputs.alpine-tag }}
load: ${{ inputs.platform == 'linux/amd64' || inputs.platform == '' }}
- name: Build and push Docker images
if: ${{ inputs.push }}
uses: ./.github/actions/build-docker-images
with:
push: true
tag-latest: ${{ inputs.tag-latest }}
platform: ${{ inputs.platform }}
cli-version: ${{ inputs.cli-version }}
alpine-tag: ${{ inputs.alpine-tag }}
dockerhub-username: ${{ secrets.DOCKERHUB_USERNAME }}
dockerhub-token: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Output image tags
run: |
{
echo "### Docker Images Built"
echo ""
echo "**Git Ref:** ${{ inputs.ref }}"
echo "**CLI Version:** ${{ inputs.cli-version }}"
echo "**Platform:** ${{ inputs.platform || 'linux/amd64,linux/arm64' }}"
echo "**Pushed to Docker Hub:** ${{ inputs.push }}"
echo "**Tagged as latest:** ${{ inputs.tag-latest }}"
echo ""
echo "**Image Tags:**"
echo "- temporaliotest/server:sha-${GITHUB_SHA:0:7}"
echo "- temporaliotest/admin-tools:sha-${GITHUB_SHA:0:7}"
} >> "$GITHUB_STEP_SUMMARY"

View File

@@ -14,13 +14,69 @@ concurrency: # Auto-cancel existing runs in the PR when a new commit is pushed
jobs:
build-docker-image:
runs-on: ubuntu-latest
permissions:
actions: write
contents: read
uses: temporalio/docker-builds/.github/workflows/docker-build-only.yml@main
with:
temporal-server-repo-path: ${{github.event.pull_request.head.repo.full_name}}
temporal-server-repo-ref: ${{github.event.pull_request.head.ref}}
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
fetch-depth: 0
- name: Build binaries
uses: ./.github/actions/build-binaries
with:
snapshot: true
single-arch: amd64
- name: Build Docker images
uses: ./.github/actions/build-docker-images
with:
push: false
tag-latest: false
platform: linux/amd64
load: true
- name: Get Docker image tag
id: image-tag
uses: actions/github-script@v8
with:
script: |
const ref = context.ref.replace('refs/heads/', '').replace('refs/tags/', '');
const branchName = `branch-${ref}`;
let safeTag = branchName.replace(/[^a-zA-Z0-9._-]/g, '-');
safeTag = safeTag.toLowerCase();
safeTag = safeTag.replace(/^[^a-z0-9]+/, '');
safeTag = safeTag.substring(0, 128);
core.setOutput('tag', safeTag);
- name: Save Docker image as artifact
run: |
docker save temporaliotest/server:${{ steps.image-tag.outputs.tag }} -o /tmp/temporal-server.tar
docker save temporaliotest/admin-tools:${{ steps.image-tag.outputs.tag }} -o /tmp/temporal-admin-tools.tar
echo ${{ steps.image-tag.outputs.tag }} > /tmp/image_tag
- name: Prepare artifact
working-directory: ${{ github.workspace }}
run: |
# Upload-artifact has no good way to flatten paths, so we need to move the compose file
# to avoid some disgustingly long inner path inside the artifact zip.
cp ./develop/docker-compose/docker-compose.yml /tmp/docker-compose.yml
echo -n "${{env.IMAGE_SHA_TAG}}" > /tmp/image_sha_tag
- name: Upload Docker artifact
uses: actions/upload-artifact@v4
with:
name: temporal-server-docker
path: |
/tmp/temporal-server.tar
/tmp/temporal-admin-tools.tar
/tmp/image_tag
/tmp/docker-compose.yml
retention-days: 7
feature-tests-ts:
needs: build-docker-image

View File

@@ -34,7 +34,7 @@ jobs:
owner: ${{ github.repository_owner }}
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0

View File

@@ -1,32 +0,0 @@
name: goreleaser
on:
release:
types:
- released
permissions:
contents: write
jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
check-latest: true
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -7,11 +7,11 @@ jobs:
lint-actions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
check-latest: true
@@ -26,11 +26,11 @@ jobs:
lint-protos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
check-latest: true
@@ -46,11 +46,11 @@ jobs:
lint-api:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
submodules: true
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
check-latest: true
@@ -67,11 +67,11 @@ jobs:
lint-workflows:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
check-latest: true
@@ -86,11 +86,11 @@ jobs:
fmt-imports:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
check-latest: true
@@ -111,11 +111,11 @@ jobs:
golangci:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: 'go.mod'
check-latest: true

View File

@@ -0,0 +1,31 @@
name: Promote Admin Tools Image
on:
workflow_dispatch:
inputs:
source-tag:
description: "Source tag from temporaliotest registry (e.g. sha-abc123)"
required: true
target-tags:
description: "Target tags for temporalio registry (comma or newline separated, e.g., 1.29.1, latest)"
required: true
override-security-scan:
description: "Override security scan failures (use with caution)"
type: boolean
default: false
required: false
permissions:
contents: read
jobs:
promote:
uses: ./.github/workflows/promote-docker-image.yml
with:
image-name: admin-tools
source-tag: ${{ inputs.source-tag }}
target-tags: ${{ inputs.target-tags }}
override-security-scan: ${{ inputs.override-security-scan }}
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

View File

@@ -0,0 +1,211 @@
name: Promote Docker Image
on:
workflow_call:
inputs:
image-name:
description: "Image name (e.g., server, admin-tools)"
required: true
type: string
source-tag:
description: "Source tag from temporaliotest registry (e.g. sha-abc123)"
required: true
type: string
target-tags:
description: "Target tags for temporalio registry (comma or newline separated, e.g., 1.29.1, latest)"
required: true
type: string
override-security-scan:
description: "Override security scan failures (use with caution)"
type: boolean
default: false
secrets:
DOCKERHUB_USERNAME:
required: true
DOCKERHUB_TOKEN:
required: true
jobs:
validate-inputs:
runs-on: ubuntu-latest
outputs:
source-tag-safe: ${{ steps.validate.outputs.source-tag }}
target-tags-safe: ${{ steps.validate.outputs.target-tags }}
steps:
- name: Validate input tags
id: validate
uses: actions/github-script@v8
env:
SOURCE_TAG: ${{ inputs.source-tag }}
TARGET_TAGS: ${{ inputs.target-tags }}
with:
script: |
const sourceTag = process.env.SOURCE_TAG;
const targetTagsInput = process.env.TARGET_TAGS;
// Source tag: must be short SHA (sha-XXXXXXX) or full sha256 digest
// Examples: sha-b5b2dfe, sha256:082943409e71ae50d8dd8693593070eac8173f01fb5bfd4970ae59e52176753e
const sourceTagPattern = /^sha-[a-f0-9]{7,}$|^sha256:[a-f0-9]{64}$/;
// Target tag: semantic version pattern (major.minor.patch.build or shorter)
// Examples: 1.29.1, 1.29.1.1, 1.29, latest
const targetTagPattern = /^(\d+\.)*\d+$|^latest$/;
// Validate source tag format
if (!sourceTagPattern.test(sourceTag)) {
core.setFailed('Error: Invalid source tag format. Must be sha-XXXXXXX or sha256:...');
return;
}
// Parse and validate target tags (split by newline or comma)
const targetTags = targetTagsInput
.split(/[\n,]/)
.map(tag => tag.trim())
.filter(tag => tag.length > 0);
if (targetTags.length === 0) {
core.setFailed('Error: At least one target tag must be provided');
return;
}
for (const tag of targetTags) {
if (!targetTagPattern.test(tag)) {
core.setFailed(`Error: Invalid target tag format: "${tag}". Must be semantic version (e.g., 1.29.1, 1.29.1.1) or "latest"`);
return;
}
}
core.setOutput('source-tag', sourceTag);
core.setOutput('target-tags', JSON.stringify(targetTags));
core.info('✓ Tag validation passed');
core.info(` Source: ${sourceTag}`);
core.info(` Target tags: ${targetTags.join(', ')}`);
scan-image:
needs: validate-inputs
runs-on: ubuntu-latest
outputs:
scan-result: ${{ steps.scan.outputs.scan-result }}
critical-count: ${{ steps.scan.outputs.critical-count }}
high-count: ${{ steps.scan.outputs.high-count }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Pull source image
env:
SOURCE_TAG: ${{ needs.validate-inputs.outputs.source-tag-safe }}
IMAGE_NAME: ${{ inputs.image-name }}
run: |
echo "Pulling temporaliotest/${IMAGE_NAME}:${SOURCE_TAG}"
docker pull "temporaliotest/${IMAGE_NAME}:${SOURCE_TAG}"
- name: Scan image with Trivy
id: scan
uses: ./.github/actions/trivy-scan
with:
image-name: temporaliotest/${{ inputs.image-name }}
image-tags: ${{ needs.validate-inputs.outputs.source-tag-safe }}
- name: Upload Trivy scan results
if: always()
uses: actions/upload-artifact@v4
with:
name: trivy-${{ inputs.image-name }}-scan-results
path: trivy-scan-${{ inputs.image-name }}.json
retention-days: 30
check-security-gate:
needs: [scan-image]
runs-on: ubuntu-latest
outputs:
can-promote: ${{ steps.check.outputs.can-promote }}
steps:
- name: Evaluate security scan results
id: check
uses: actions/github-script@v8
env:
OVERRIDE: ${{ inputs.override-security-scan }}
SCAN_RESULT: ${{ needs.scan-image.outputs.scan-result }}
CRITICAL_COUNT: ${{ needs.scan-image.outputs.critical-count }}
HIGH_COUNT: ${{ needs.scan-image.outputs.high-count }}
IMAGE_NAME: ${{ inputs.image-name }}
with:
script: |
const override = process.env.OVERRIDE === 'true';
const scanResult = process.env.SCAN_RESULT;
const criticalCount = process.env.CRITICAL_COUNT;
const highCount = process.env.HIGH_COUNT;
const imageName = process.env.IMAGE_NAME;
core.info('=== Security Scan Results ===');
core.info(`${imageName} image: ${scanResult}`);
core.info(` Critical: ${criticalCount}`);
core.info(` High: ${highCount}`);
core.info('');
core.info(`Override enabled: ${override}`);
core.info('==============================');
if (scanResult === 'fail') {
if (override) {
core.info('');
core.warning('Security vulnerabilities detected but OVERRIDE is enabled!');
core.info('Proceeding with image promotion despite security issues.');
core.setOutput('can-promote', 'true');
} else {
core.info('');
core.error('Security vulnerabilities detected. Promotion BLOCKED.');
core.info("To proceed anyway, re-run with 'override-security-scan' enabled.");
core.setOutput('can-promote', 'false');
core.setFailed('Security vulnerabilities detected');
}
} else {
core.info('');
core.info('✓ Security scan passed. Proceeding with promotion.');
core.setOutput('can-promote', 'true');
}
promote:
needs: [validate-inputs, check-security-gate]
if: needs.check-security-gate.outputs.can-promote == 'true'
runs-on: ubuntu-latest
steps:
- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Pull, tag, and push image
uses: actions/github-script@v8
env:
SOURCE_TAG: ${{ needs.validate-inputs.outputs.source-tag-safe }}
TARGET_TAGS: ${{ needs.validate-inputs.outputs.target-tags-safe }}
IMAGE_NAME: ${{ inputs.image-name }}
with:
script: |
const { execSync } = require('child_process');
const sourceTag = process.env.SOURCE_TAG;
const targetTags = JSON.parse(process.env.TARGET_TAGS);
const imageName = process.env.IMAGE_NAME;
core.info(`Promoting ${imageName} image...`);
core.info(` From: temporaliotest/${imageName}:${sourceTag}`);
core.info(` To: ${targetTags.map(t => `temporalio/${imageName}:${t}`).join(', ')}`);
// Pull from test registry
core.info('Pulling source image...');
execSync(`docker pull temporaliotest/${imageName}:${sourceTag}`, { stdio: 'inherit' });
// Tag for each target tag
for (const targetTag of targetTags) {
core.info(`Tagging as temporalio/${imageName}:${targetTag}`);
execSync(`docker tag temporaliotest/${imageName}:${sourceTag} temporalio/${imageName}:${targetTag}`, { stdio: 'inherit' });
}
// Push all tags at once
core.info('Pushing all tags to production registry...');
execSync(`docker push --all-tags temporalio/${imageName}`, { stdio: 'inherit' });
core.info(`✓ ${imageName} image promoted successfully to all tags`);

View File

@@ -0,0 +1,31 @@
name: Promote Server Image
on:
workflow_dispatch:
inputs:
source-tag:
description: "Source tag from temporaliotest registry (e.g. sha-abc123)"
required: true
target-tags:
description: "Target tags for temporalio registry (comma or newline separated, e.g., 1.29.1, latest)"
required: true
override-security-scan:
description: "Override security scan failures (use with caution)"
type: boolean
default: false
required: false
permissions:
contents: read
jobs:
promote:
uses: ./.github/workflows/promote-docker-image.yml
with:
image-name: server
source-tag: ${{ inputs.source-tag }}
target-tags: ${{ inputs.target-tags }}
override-security-scan: ${{ inputs.override-security-scan }}
secrets:
DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKERHUB_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}

25
.github/workflows/release.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Release
on:
release:
types:
- released
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v6
with:
ref: ${{ github.ref }}
fetch-depth: 0
- name: Build binaries
uses: ./.github/actions/build-binaries
with:
snapshot: false
release: true

View File

@@ -108,7 +108,7 @@ jobs:
cat "$GITHUB_OUTPUT"
- name: Checkout Code
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ env.COMMIT }}
@@ -139,13 +139,13 @@ jobs:
name: Pre-build for cache
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
if: ${{ !inputs.run_single_functional_test && !inputs.run_single_unit_test }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ env.COMMIT }}
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
if: ${{ !inputs.run_single_functional_test && !inputs.run_single_unit_test }}
with:
go-version-file: "go.mod"
@@ -181,7 +181,7 @@ jobs:
needs: pre-build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
if: ${{ !inputs.run_single_functional_test && !inputs.run_single_unit_test }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -189,7 +189,7 @@ jobs:
# buf-breaking tries to compare HEAD against merge base so we need to be able to find it
fetch-depth: 100
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
if: ${{ !inputs.run_single_functional_test && !inputs.run_single_unit_test }}
with:
go-version-file: "go.mod"
@@ -229,12 +229,12 @@ jobs:
needs: [pre-build, set-up-single-test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ env.COMMIT }}
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
cache: false # do our own caching
@@ -319,7 +319,7 @@ jobs:
needs: [pre-build, set-up-single-test]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ env.COMMIT }}
@@ -334,7 +334,7 @@ jobs:
postgresql
down-flags: -v
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
cache: false # do our own caching
@@ -481,7 +481,7 @@ jobs:
with:
key: docker-${{ runner.os }}${{ runner.arch }}-${{ hashFiles(env.DOCKER_COMPOSE_FILE) }}
- uses: actions/checkout@v4
- uses: actions/checkout@v6
if: ${{ inputs.run_single_functional_test != true || (inputs.run_single_functional_test == true && contains(fromJSON(needs.set-up-single-test.outputs.dbs), env.PERSISTENCE_DRIVER)) }}
with:
token: ${{ secrets.GITHUB_TOKEN }}
@@ -495,7 +495,7 @@ jobs:
services: "${{ join(matrix.containers, '\n') }}"
down-flags: -v
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
if: ${{ inputs.run_single_functional_test != true || (inputs.run_single_functional_test == true && contains(fromJSON(needs.set-up-single-test.outputs.dbs), env.PERSISTENCE_DRIVER)) }}
with:
go-version-file: "go.mod"
@@ -645,7 +645,7 @@ jobs:
PERSISTENCE_DRIVER: ${{ matrix.persistence_driver }}
TEST_PARALLEL_FLAGS: ${{ matrix.parallel_flags }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ env.COMMIT }}
@@ -665,7 +665,7 @@ jobs:
services: "${{ join(matrix.containers, '\n') }}"
down-flags: -v
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
cache: false # do our own caching
@@ -806,7 +806,7 @@ jobs:
PERSISTENCE_DRIVER: ${{ matrix.persistence_driver }}
ES_VERSION: ${{ matrix.es_version }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
ref: ${{ env.COMMIT }}
@@ -826,7 +826,7 @@ jobs:
services: "${{ join(matrix.containers, '\n') }}"
down-flags: -v
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version-file: "go.mod"
cache: false # do our own caching

View File

@@ -1,18 +1,25 @@
# GoReleaser v2 configuration
version: 2
before:
hooks:
- go mod download
snapshot:
version_template: "{{ .Version }}-SNAPSHOT-{{ .ShortCommit }}"
archives:
- id: default
builds:
ids:
- temporal-server
- temporal-cassandra-tool
- temporal-sql-tool
- temporal-elasticsearch-tool
- tdbg
name_template: "{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}"
format_overrides:
- goos: windows
format: zip
formats: [zip]
files:
- ./config/*
@@ -79,11 +86,11 @@ builds:
- arm64
checksum:
name_template: 'checksums.txt'
name_template: "checksums.txt"
algorithm: sha256
changelog:
skip: true
disable: true
announce:
skip: "true"
skip: true

View File

@@ -1,18 +1,22 @@
variable "SERVER_VERSION" {
default = "1.29.1"
default = "unknown"
}
variable "CLI_VERSION" {
default = "1.5.0"
default = "unknown"
}
variable "IMAGE_REPO" {
default = "temporaliotest"
}
variable "IMAGE_SHA_TAG" {}
variable "IMAGE_SHA_TAG" {
default = ""
}
variable "IMAGE_BRANCH_TAG" {}
variable "IMAGE_BRANCH_TAG" {
default = ""
}
variable "SAFE_IMAGE_BRANCH_TAG" {
default = join("-", [for c in regexall("[a-z0-9]+", lower(IMAGE_BRANCH_TAG)) : c])
@@ -26,12 +30,19 @@ variable "TAG_LATEST" {
default = false
}
# Legacy targets (legacy-admin-tools, legacy-server) are for building images with server versions
# older than v1.27.0 (3 minor versions behind v1.30.0). Once support for pre-1.27.0 versions is
# no longer needed, these legacy targets can be removed and only the standard targets should be used.
# IMPORTANT: When updating ALPINE_TAG, also update the default value in:
# - docker/targets/admin-tools.Dockerfile
# - docker/targets/server.Dockerfile
variable "ALPINE_TAG" {
default = "3.23@sha256:c78ded0fee4493809c8ca71d4a6057a46237763d952fae15ea418f6d14137f2d"
}
target "admin-tools" {
context = "docker"
dockerfile = "targets/admin-tools.Dockerfile"
args = {
ALPINE_TAG = "${ALPINE_TAG}"
}
tags = compact([
"${IMAGE_REPO}/admin-tools:${IMAGE_SHA_TAG}",
"${IMAGE_REPO}/admin-tools:${SAFE_IMAGE_BRANCH_TAG}",
@@ -44,7 +55,6 @@ target "admin-tools" {
"org.opencontainers.image.url" = "https://github.com/temporalio/temporal"
"org.opencontainers.image.source" = "https://github.com/temporalio/temporal"
"org.opencontainers.image.licenses" = "MIT"
"org.opencontainers.image.version" = "${SERVER_VERSION}"
"org.opencontainers.image.revision" = "${TEMPORAL_SHA}"
"org.opencontainers.image.created" = timestamp()
"com.temporal.server.version" = "${SERVER_VERSION}"
@@ -53,7 +63,11 @@ target "admin-tools" {
}
target "server" {
context = "docker"
dockerfile = "targets/server.Dockerfile"
args = {
ALPINE_TAG = "${ALPINE_TAG}"
}
tags = compact([
"${IMAGE_REPO}/server:${IMAGE_SHA_TAG}",
"${IMAGE_REPO}/server:${SAFE_IMAGE_BRANCH_TAG}",

View File

@@ -0,0 +1,33 @@
# IMPORTANT: When updating ALPINE_TAG, also update the default value in:
# - docker/docker-bake.hcl (variable "ALPINE_TAG")
# - docker/targets/server.Dockerfile (ARG ALPINE_TAG)
ARG ALPINE_TAG=3.23@sha256:c78ded0fee4493809c8ca71d4a6057a46237763d952fae15ea418f6d14137f2d
FROM alpine:${ALPINE_TAG}
ARG TARGETARCH
RUN apk add --no-cache \
ca-certificates \
tzdata && addgroup -g 1000 temporal && \
adduser -u 1000 -G temporal -D temporal
# Copy all admin tool binaries:
# - temporal (CLI)
# - temporal-cassandra-tool
# - temporal-sql-tool
# - temporal-elasticsearch-tool
# - tdbg
COPY --chmod=755 \
./build/${TARGETARCH}/temporal \
./build/${TARGETARCH}/temporal-cassandra-tool \
./build/${TARGETARCH}/temporal-sql-tool \
./build/${TARGETARCH}/temporal-elasticsearch-tool \
./build/${TARGETARCH}/tdbg \
/usr/local/bin/
COPY ./build/temporal/schema /etc/temporal/schema
USER temporal
CMD ["sh", "-c", "trap exit INT HUP TERM; sleep infinity"]

View File

@@ -1,4 +1,9 @@
FROM alpine:3.22@sha256:4b7ce07002c69e8f3d704a9c5d6fd3053be500b7f1c69fc0d80990c2ad8dd412
# IMPORTANT: When updating ALPINE_TAG, also update the default value in:
# - docker/docker-bake.hcl (variable "ALPINE_TAG")
# - docker/targets/admin-tools.Dockerfile (ARG ALPINE_TAG)
ARG ALPINE_TAG=3.23@sha256:c78ded0fee4493809c8ca71d4a6057a46237763d952fae15ea418f6d14137f2d
FROM alpine:${ALPINE_TAG}
ARG TARGETARCH