mirror of
https://github.com/temporalio/temporal.git
synced 2026-08-30 18:41:49 -07:00
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:
47
.github/actions/build-binaries/action.yml
vendored
Normal file
47
.github/actions/build-binaries/action.yml
vendored
Normal 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 }}
|
||||
133
.github/actions/build-docker-images/action.yml
vendored
Normal file
133
.github/actions/build-docker-images/action.yml
vendored
Normal 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
|
||||
1
.github/actions/build-docker-images/scripts/.gitignore
vendored
Normal file
1
.github/actions/build-docker-images/scripts/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
docker-build-helper
|
||||
561
.github/actions/build-docker-images/scripts/main.go
vendored
Normal file
561
.github/actions/build-docker-images/scripts/main.go
vendored
Normal 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
108
.github/actions/trivy-scan/action.yml
vendored
Normal 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
|
||||
23
.github/docker/targets/admin-tools.Dockerfile
vendored
23
.github/docker/targets/admin-tools.Dockerfile
vendored
@@ -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
62
.github/workflows/build-and-publish.yml
vendored
Normal 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
|
||||
2
.github/workflows/check-pr-placeholders.yml
vendored
2
.github/workflows/check-pr-placeholders.yml
vendored
@@ -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({
|
||||
|
||||
4
.github/workflows/create-tag.yml
vendored
4
.github/workflows/create-tag.yml
vendored
@@ -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 }}
|
||||
|
||||
105
.github/workflows/docker-build-manual.yml
vendored
Normal file
105
.github/workflows/docker-build-manual.yml
vendored
Normal 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"
|
||||
62
.github/workflows/features-integration.yml
vendored
62
.github/workflows/features-integration.yml
vendored
@@ -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
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
temporal-server-repo-path: ${{github.event.pull_request.head.repo.full_name}}
|
||||
temporal-server-repo-ref: ${{github.event.pull_request.head.ref}}
|
||||
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
|
||||
|
||||
2
.github/workflows/flaky-tests-report.yml
vendored
2
.github/workflows/flaky-tests-report.yml
vendored
@@ -34,7 +34,7 @@ jobs:
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
||||
32
.github/workflows/goreleaser.yml
vendored
32
.github/workflows/goreleaser.yml
vendored
@@ -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 }}
|
||||
24
.github/workflows/linters.yml
vendored
24
.github/workflows/linters.yml
vendored
@@ -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
|
||||
|
||||
31
.github/workflows/promote-admin-tools-image.yml
vendored
Normal file
31
.github/workflows/promote-admin-tools-image.yml
vendored
Normal 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 }}
|
||||
211
.github/workflows/promote-docker-image.yml
vendored
Normal file
211
.github/workflows/promote-docker-image.yml
vendored
Normal 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`);
|
||||
31
.github/workflows/promote-server-image.yml
vendored
Normal file
31
.github/workflows/promote-server-image.yml
vendored
Normal 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
25
.github/workflows/release.yml
vendored
Normal 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
|
||||
30
.github/workflows/run-tests.yml
vendored
30
.github/workflows/run-tests.yml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}",
|
||||
33
docker/targets/admin-tools.Dockerfile
Normal file
33
docker/targets/admin-tools.Dockerfile
Normal 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"]
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user