This commit is contained in:
commit
302df33baa
19
.gitea/workflows/test.yaml
Normal file
19
.gitea/workflows/test.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
name: Go Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: https://github.com/actions/checkout@v4
|
||||
- name: Set up Go
|
||||
uses: https://github.com/actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.24.3'
|
||||
- name: Run tests
|
||||
run: go test -v ./...
|
25
action.yaml
Normal file
25
action.yaml
Normal file
@ -0,0 +1,25 @@
|
||||
name: 'Build docker image'
|
||||
description: 'Docker builder for images + pushing, secret management, automatic versioning, and more'
|
||||
inputs:
|
||||
path:
|
||||
description: 'Path to image registry (e.g., ghcr.io/owner/repo)'
|
||||
required: true
|
||||
image-name:
|
||||
description: 'The name of the image to build'
|
||||
required: true
|
||||
secrets:
|
||||
desction: 'Secrets lists to pass to the build'
|
||||
required: false
|
||||
default: ''
|
||||
dockerfile:
|
||||
description: 'Path to the Dockerfile (default: Dockerfile)'
|
||||
required: false
|
||||
default: 'Dockerfile'
|
||||
outputs:
|
||||
full_image_name:
|
||||
description: 'Full image name'
|
||||
image_tag:
|
||||
description: 'Version of image'
|
||||
runs:
|
||||
using: 'go'
|
||||
main: 'main.go'
|
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module git.nanobyte.cz/actions/go-dockerbuild
|
||||
|
||||
go 1.24.3
|
||||
|
||||
require github.com/sethvargo/go-githubactions v1.3.1
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/sethvargo/go-githubactions v1.3.1 h1:rlwwLRUaunWLQ1aN2o5Y+3s0xhaTC30YObCnilRx448=
|
||||
github.com/sethvargo/go-githubactions v1.3.1/go.mod h1:7/4WeHgYfSz9U5vwuToCK9KPnELVHAhGtRwLREOQV80=
|
143
main.go
Normal file
143
main.go
Normal file
@ -0,0 +1,143 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
gha "github.com/sethvargo/go-githubactions"
|
||||
)
|
||||
|
||||
func getShortHash() string {
|
||||
// Get short git commit hash
|
||||
gitCmd := exec.Command("git", "rev-parse", "--short", "HEAD")
|
||||
gitCmd.Dir = "."
|
||||
gitCmd.Stderr = os.Stderr
|
||||
hashBytes, err := gitCmd.Output()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to get git hash: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
shortHash := string(hashBytes)
|
||||
shortHash = shortHash[:len(shortHash)-1] // remove trailing newline
|
||||
return shortHash
|
||||
}
|
||||
|
||||
func parseSecrets(secretsInput string) map[string]string {
|
||||
secrets := make(map[string]string)
|
||||
if secretsInput != "" {
|
||||
scanner := bufio.NewScanner(strings.NewReader(secretsInput))
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "=", 2)
|
||||
if len(parts) == 2 {
|
||||
key := strings.TrimSpace(parts[0])
|
||||
val := strings.Trim(strings.TrimSpace(parts[1]), `"`)
|
||||
secrets[key] = val
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return secrets
|
||||
|
||||
}
|
||||
|
||||
func buildDockerCommand(context string, dockerFile string, secrets map[string]string, tagLatest string, tagNightly string) *exec.Cmd {
|
||||
// Parse secrets and write to .secrets file
|
||||
buildArgs := []string{}
|
||||
if len(secrets) > 0 {
|
||||
f, err := os.CreateTemp("", ".secrets")
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create .secrets file: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer f.Close()
|
||||
for secretKey, secretVal := range secrets {
|
||||
_, err := fmt.Fprintf(f, "%s=%s\n", secretKey, secretVal)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to write secret: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
buildArgs = append(buildArgs, "--secret", "id=build_secrets,src="+f.Name())
|
||||
}
|
||||
|
||||
// Build command
|
||||
buildArgsExpanded := append([]string{"build", context, "-f", dockerFile, "-t", tagLatest, "-t", tagNightly}, buildArgs...)
|
||||
buildCmd := exec.Command("docker", buildArgsExpanded...)
|
||||
buildCmd.Env = append(os.Environ(), "DOCKER_BUILDKIT=1")
|
||||
buildCmd.Stdout = os.Stdout
|
||||
buildCmd.Stderr = os.Stderr
|
||||
return buildCmd
|
||||
}
|
||||
|
||||
func pushDockerCommand(tag string) *exec.Cmd {
|
||||
// Push nightly tag
|
||||
pushNightlyCmd := exec.Command("docker", "push", tag)
|
||||
pushNightlyCmd.Stdout = os.Stdout
|
||||
pushNightlyCmd.Stderr = os.Stderr
|
||||
return pushNightlyCmd
|
||||
}
|
||||
|
||||
func main() {
|
||||
basePath := gha.GetInput("path")
|
||||
imageName := gha.GetInput("image-name")
|
||||
dockerFile := gha.GetInput("dockerfile")
|
||||
context := gha.GetInput("context")
|
||||
secrets := parseSecrets(gha.GetInput("secrets"))
|
||||
date := time.Now().Format("2006-01-02")
|
||||
shortHash := getShortHash()
|
||||
|
||||
if basePath == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: 'path' input is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if imageName == "" {
|
||||
fmt.Fprintln(os.Stderr, "Error: 'image-name' input is required")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if basePath[len(basePath)-1] != '/' {
|
||||
basePath += "/"
|
||||
}
|
||||
|
||||
// Construct image names
|
||||
imageBase := fmt.Sprintf("%s%s", basePath, imageName)
|
||||
tagLatest := fmt.Sprintf("%s:latest", imageBase)
|
||||
tagNightly := fmt.Sprintf("%s:nightly-%s.%s", imageBase, date, shortHash)
|
||||
|
||||
// Build Docker command
|
||||
buildCmd := buildDockerCommand(context, dockerFile, secrets, tagLatest, tagNightly)
|
||||
fmt.Println("Running:", buildCmd.String())
|
||||
if err := buildCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Build failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Push nightly tag
|
||||
pushNightlyCmd := pushDockerCommand(tagNightly)
|
||||
fmt.Println("Running:", pushNightlyCmd.String())
|
||||
if err := pushNightlyCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Push nightly failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Push latest tag
|
||||
pushLatestCmd := pushDockerCommand(tagLatest)
|
||||
fmt.Println("Running:", pushLatestCmd.String())
|
||||
if err := pushLatestCmd.Run(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Push latest failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
gha.SetOutput("image_tag", fmt.Sprintf("%s:%s", imageBase, shortHash))
|
||||
gha.SetOutput("full_image_name", tagLatest)
|
||||
}
|
103
main_test.go
Normal file
103
main_test.go
Normal file
@ -0,0 +1,103 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Test parseSecrets with various input formats
|
||||
func TestParseSecrets(t *testing.T) {
|
||||
input := `
|
||||
SECRET1=foo
|
||||
SECRET2="bar"
|
||||
# This is a comment
|
||||
SECRET3= baz
|
||||
`
|
||||
expected := map[string]string{
|
||||
"SECRET1": "foo",
|
||||
"SECRET2": "bar",
|
||||
"SECRET3": "baz",
|
||||
}
|
||||
result := parseSecrets(input)
|
||||
if len(result) != len(expected) {
|
||||
t.Fatalf("expected %d secrets, got %d", len(expected), len(result))
|
||||
}
|
||||
for k, v := range expected {
|
||||
if result[k] != v {
|
||||
t.Errorf("expected %s=%s, got %s", k, v, result[k])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test getShortHash (requires git repo)
|
||||
func TestGetShortHash(t *testing.T) {
|
||||
cmd := exec.Command("git", "rev-parse", "--short", "HEAD")
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
t.Skip("Skipping getShortHash test: not a git repo", err)
|
||||
}
|
||||
expected := strings.TrimSpace(string(out))
|
||||
got := getShortHash()
|
||||
if got != expected {
|
||||
t.Errorf("expected hash %s, got %s", expected, got)
|
||||
}
|
||||
}
|
||||
|
||||
// Test buildDockerCommand returns a valid *exec.Cmd with context and dockerfile
|
||||
func TestBuildDockerCommandWithContextAndDockerfile(t *testing.T) {
|
||||
secrets := map[string]string{
|
||||
"FOO": "bar",
|
||||
}
|
||||
context := "./foo"
|
||||
dockerfile := "Dockerfile"
|
||||
tagLatest := "test:latest"
|
||||
tagNightly := "test:nightly"
|
||||
cmd := buildDockerCommand(context, dockerfile, secrets, tagLatest, tagNightly)
|
||||
if cmd == nil {
|
||||
t.Fatal("expected non-nil *exec.Cmd")
|
||||
}
|
||||
args := strings.Join(cmd.Args, " ")
|
||||
if !strings.Contains(args, context) {
|
||||
t.Errorf("docker build args missing context: %v", cmd.Args)
|
||||
}
|
||||
if !strings.Contains(args, "-f") || !strings.Contains(args, dockerfile) {
|
||||
t.Errorf("docker build args missing dockerfile: %v", cmd.Args)
|
||||
}
|
||||
if !strings.Contains(args, tagLatest) || !strings.Contains(args, tagNightly) {
|
||||
t.Errorf("docker build args missing tags: %v", cmd.Args)
|
||||
}
|
||||
if !strings.Contains(args, "--secret") {
|
||||
t.Errorf("docker build args missing --secret: %v", cmd.Args)
|
||||
}
|
||||
}
|
||||
|
||||
// Test buildDockerCommand returns a valid *exec.Cmd without secrets
|
||||
func TestBuildDockerCommandNoSecrets(t *testing.T) {
|
||||
secrets := map[string]string{}
|
||||
context := "."
|
||||
dockerfile := "Dockerfile"
|
||||
tagLatest := "test:latest"
|
||||
tagNightly := "test:nightly"
|
||||
cmd := buildDockerCommand(context, dockerfile, secrets, tagLatest, tagNightly)
|
||||
if cmd == nil {
|
||||
t.Fatal("expected non-nil *exec.Cmd")
|
||||
}
|
||||
args := strings.Join(cmd.Args, " ")
|
||||
if strings.Contains(args, "--secret") {
|
||||
t.Errorf("docker build args should not contain --secret when no secrets are provided: %v", cmd.Args)
|
||||
}
|
||||
}
|
||||
|
||||
// Test pushDockerCommand returns a valid *exec.Cmd
|
||||
func TestPushDockerCommand(t *testing.T) {
|
||||
tag := "test:latest"
|
||||
cmd := pushDockerCommand(tag)
|
||||
if cmd == nil {
|
||||
t.Fatal("expected non-nil *exec.Cmd")
|
||||
}
|
||||
args := strings.Join(cmd.Args, " ")
|
||||
if !strings.Contains(args, "push") || !strings.Contains(args, tag) {
|
||||
t.Errorf("docker push args missing: %v", cmd.Args)
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user