initial commit
All checks were successful
Go Test / test (push) Successful in 25s

This commit is contained in:
Ondrej Vlach 2025-05-30 19:23:34 +02:00
commit 302df33baa
No known key found for this signature in database
GPG Key ID: 7F141CDACEDEE2DE
6 changed files with 297 additions and 0 deletions

View 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
View 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
View 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
View 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
View 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
View 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)
}
}