0
0
mirror of https://github.com/thegeeklab/wp-docker-buildx.git synced 2024-11-21 13:50:39 +00:00

fix: use StringMap for build_args instead of StringSlice (#277)

BREAKING CHANGE: The type of the `build_args` property was changed from `list` to `map` to support the use of secrets for certain build arguments. During this refactoring, the `build_args_from_env` behavior was also changed to achieve the intended behavior. Environment variable names passed to this list will be used as build arguments if the environment variable is set.
This commit is contained in:
Robert Kaussow 2024-11-15 21:45:29 +01:00 committed by GitHub
parent b213fcad0a
commit a2bc0869af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 292 additions and 50 deletions

View File

@ -2,6 +2,7 @@ package docker
import ( import (
"fmt" "fmt"
"maps"
"os" "os"
"strings" "strings"
"time" "time"
@ -23,33 +24,33 @@ type Registry struct {
// Build defines Docker build parameters. // Build defines Docker build parameters.
type Build struct { type Build struct {
Ref string // Git commit ref Ref string // Git commit ref
Branch string // Git repository branch Branch string // Git repository branch
Containerfile string // Docker build Containerfile Containerfile string // Docker build Containerfile
Context string // Docker build context Context string // Docker build context
TagsAuto bool // Docker build auto tag TagsAuto bool // Docker build auto tag
TagsSuffix string // Docker build tags with suffix TagsSuffix string // Docker build tags with suffix
Tags cli.StringSlice // Docker build tags Tags cli.StringSlice // Docker build tags
ExtraTags cli.StringSlice // Docker build tags including registry ExtraTags cli.StringSlice // Docker build tags including registry
Platforms cli.StringSlice // Docker build target platforms Platforms cli.StringSlice // Docker build target platforms
Args cli.StringSlice // Docker build args Args map[string]string // Docker build args
ArgsEnv cli.StringSlice // Docker build args from env ArgsEnv cli.StringSlice // Docker build args from env
Target string // Docker build target Target string // Docker build target
Pull bool // Docker build pull Pull bool // Docker build pull
CacheFrom []string // Docker build cache-from CacheFrom []string // Docker build cache-from
CacheTo string // Docker build cache-to CacheTo string // Docker build cache-to
Compress bool // Docker build compress Compress bool // Docker build compress
Repo string // Docker build repository Repo string // Docker build repository
NoCache bool // Docker build no-cache NoCache bool // Docker build no-cache
AddHost cli.StringSlice // Docker build add-host AddHost cli.StringSlice // Docker build add-host
Quiet bool // Docker build quiet Quiet bool // Docker build quiet
Output string // Docker build output folder Output string // Docker build output folder
NamedContext cli.StringSlice // Docker build named context NamedContext cli.StringSlice // Docker build named context
Labels cli.StringSlice // Docker build labels Labels cli.StringSlice // Docker build labels
Provenance string // Docker build provenance attestation Provenance string // Docker build provenance attestation
SBOM string // Docker build sbom attestation SBOM string // Docker build sbom attestation
Secrets []string // Docker build secrets Secrets []string // Docker build secrets
Dryrun bool // Docker build dryrun Dryrun bool // Docker build dryrun
} }
// helper function to create the docker login command. // helper function to create the docker login command.
@ -100,10 +101,12 @@ func (b *Build) Run() *plugin_exec.Cmd {
"-f", b.Containerfile, "-f", b.Containerfile,
} }
defaultBuildArgs := []string{ defaultBuildArgs := map[string]string{
fmt.Sprintf("DOCKER_IMAGE_CREATED=%s", time.Now().Format(time.RFC3339)), "DOCKER_IMAGE_CREATED": time.Now().Format(time.RFC3339),
} }
maps.Copy(b.Args, defaultBuildArgs)
args = append(args, b.Context) args = append(args, b.Context)
if !b.Dryrun && b.Output == "" && len(b.Tags.Value()) > 0 { if !b.Dryrun && b.Output == "" && len(b.Tags.Value()) > 0 {
args = append(args, "--push") args = append(args, "--push")
@ -130,11 +133,11 @@ func (b *Build) Run() *plugin_exec.Cmd {
} }
for _, arg := range b.ArgsEnv.Value() { for _, arg := range b.ArgsEnv.Value() {
b.addProxyValue(arg) b.addArgFromEnv(arg)
} }
for _, arg := range append(defaultBuildArgs, b.Args.Value()...) { for key, value := range b.Args {
args = append(args, "--build-arg", arg) args = append(args, "--build-arg", fmt.Sprintf("%s=%s", key, value))
} }
for _, host := range b.AddHost.Value() { for _, host := range b.AddHost.Value() {
@ -203,9 +206,19 @@ func (b *Build) AddProxyBuildArgs() {
func (b *Build) addProxyValue(key string) { func (b *Build) addProxyValue(key string) {
value := b.getProxyValue(key) value := b.getProxyValue(key)
if len(value) > 0 && !b.hasProxyBuildArg(key) { if value != "" && !b.hasProxyBuildArg(key) {
b.Args = *cli.NewStringSlice(append(b.Args.Value(), fmt.Sprintf("%s=%s", key, value))...) b.Args[key] = value
b.Args = *cli.NewStringSlice(append(b.Args.Value(), fmt.Sprintf("%s=%s", strings.ToUpper(key), value))...) b.Args[strings.ToUpper(key)] = value
}
}
func (b *Build) addArgFromEnv(key string) {
if value, ok := b.Args[key]; ok && value != "" {
return
}
if value, ok := os.LookupEnv(key); ok && value != "" {
b.Args[key] = value
} }
} }
@ -215,7 +228,7 @@ func (b *Build) addProxyValue(key string) {
func (b *Build) getProxyValue(key string) string { func (b *Build) getProxyValue(key string) string {
value := os.Getenv(key) value := os.Getenv(key)
if len(value) > 0 { if value != "" {
return value return value
} }
@ -224,12 +237,12 @@ func (b *Build) getProxyValue(key string) string {
// helper function that looks to see if a proxy value was set in the build args. // helper function that looks to see if a proxy value was set in the build args.
func (b *Build) hasProxyBuildArg(key string) bool { func (b *Build) hasProxyBuildArg(key string) bool {
keyUpper := strings.ToUpper(key) if _, ok := b.Args[key]; ok {
return true
}
for _, s := range b.Args.Value() { if _, ok := b.Args[strings.ToUpper(key)]; ok {
if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { return true
return true
}
} }
return false return false

197
docker/docker_test.go Normal file
View File

@ -0,0 +1,197 @@
package docker
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHasProxyBuildArg(t *testing.T) {
tests := []struct {
name string
args map[string]string
key string
expected bool
}{
{
name: "lowercase key exists",
args: map[string]string{"http_proxy": "http://proxy.example.com"},
key: "http_proxy",
expected: true,
},
{
name: "uppercase key exists",
args: map[string]string{"HTTP_PROXY": "http://proxy.example.com"},
key: "http_proxy",
expected: true,
},
{
name: "key does not exist",
args: map[string]string{},
key: "http_proxy",
expected: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := &Build{Args: tt.args}
result := b.hasProxyBuildArg(tt.key)
assert.Equal(t, tt.expected, result)
})
}
}
func TestGetProxyValue(t *testing.T) {
tests := []struct {
name string
envVars map[string]string
key string
expected string
}{
{
name: "lowercase env var exists",
envVars: map[string]string{
"http_proxy": "http://proxy.local",
},
key: "http_proxy",
expected: "http://proxy.local",
},
{
name: "uppercase env var exists",
envVars: map[string]string{
"HTTP_PROXY": "http://proxy.upper.local",
},
key: "http_proxy",
expected: "http://proxy.upper.local",
},
{
name: "both cases exist, lowercase preferred",
envVars: map[string]string{
"http_proxy": "http://proxy.lower.local",
"HTTP_PROXY": "http://proxy.upper.local",
},
key: "http_proxy",
expected: "http://proxy.lower.local",
},
{
name: "no env vars exist",
envVars: map[string]string{},
key: "http_proxy",
expected: "",
},
{
name: "different proxy type",
envVars: map[string]string{
"https_proxy": "https://secure.proxy.local",
"HTTPS_PROXY": "https://secure.proxy.upper.local",
},
key: "https_proxy",
expected: "https://secure.proxy.local",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Set test environment variables
for k, v := range tt.envVars {
t.Setenv(k, v)
}
b := &Build{}
result := b.getProxyValue(tt.key)
assert.Equal(t, tt.expected, result)
})
}
}
func TestAddArgFromEnv(t *testing.T) {
tests := []struct {
name string
envVars map[string]string
key string
existing map[string]string
expected map[string]string
}{
{
name: "add new env var",
envVars: map[string]string{
"NEW_VAR": "test_value",
},
key: "NEW_VAR",
expected: map[string]string{
"NEW_VAR": "test_value",
},
},
{
name: "empty env var",
envVars: map[string]string{},
key: "MISSING_VAR",
expected: map[string]string{},
},
{
name: "env var with empty value",
envVars: map[string]string{
"EMPTY_VAR": "",
},
key: "EMPTY_VAR",
expected: map[string]string{},
},
{
name: "multiple env vars",
envVars: map[string]string{
"VAR1": "value1",
"VAR2": "value2",
},
key: "VAR1",
expected: map[string]string{
"VAR1": "value1",
},
},
{
name: "special characters in value",
envVars: map[string]string{
"SPECIAL_VAR": "!@#$%^&*()",
},
key: "SPECIAL_VAR",
expected: map[string]string{
"SPECIAL_VAR": "!@#$%^&*()",
},
},
{
name: "preserve existing args",
envVars: map[string]string{
"TEST_VAR": "new_value",
},
key: "TEST_VAR",
existing: map[string]string{
"TEST_VAR": "old_value",
},
expected: map[string]string{
"TEST_VAR": "old_value",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Clear environment before each test
for k := range tt.envVars {
t.Setenv(k, "")
}
// Set test environment variables
for k, v := range tt.envVars {
t.Setenv(k, v)
}
b := &Build{Args: make(map[string]string)}
if tt.existing != nil {
b.Args = tt.existing
}
b.addArgFromEnv(tt.key)
assert.Equal(t, tt.expected, b.Args)
})
}
}

View File

@ -34,13 +34,38 @@ properties:
- name: build_args - name: build_args
description: | description: |
Custom build arguments for the build. Custom build arguments for the build. Example:
type: list
```yaml
steps:
- name: Build
image: quay.io/thegeeklab/wp-docker-buildx
settings:
repo: example/repo
build_args:
FOO: bar
API_KEY:
from_secret: API_KEY
```
type: map
required: false required: false
- name: build_args_from_env - name: build_args_from_env
description: | description: |
Forward environment variables as custom arguments to the build. Forward environment variables to the build as build arguments. If the same key
already exists in `build_args`, it will not be overwritten. Example:
```yaml
steps:
- name: Build
image: quay.io/thegeeklab/wp-docker-buildx
environment:
CUSTOM_ENVIRONMENT: custom_value
settings:
repo: example/repo
build_args_from_env:
- CUSTOM_ENVIRONMENT
```
type: list type: list
required: false required: false

View File

@ -210,6 +210,13 @@ func (p *Plugin) FlagsFromContext() error {
p.Settings.Build.Secrets = secrets.Get() p.Settings.Build.Secrets = secrets.Get()
args, ok := p.Context.Generic("args").(*plugin_types.StringMapFlag)
if !ok {
return fmt.Errorf("%w: failed to read args input", ErrTypeAssertionFailed)
}
p.Settings.Build.Args = args.Get()
return nil return nil
} }

View File

@ -227,12 +227,12 @@ func Flags(settings *Settings, category string) []cli.Flag {
Destination: &settings.Build.ExtraTags, Destination: &settings.Build.ExtraTags,
Category: category, Category: category,
}, },
&cli.StringSliceFlag{ &cli.GenericFlag{
Name: "args", Name: "args",
EnvVars: []string{"PLUGIN_BUILD_ARGS"}, EnvVars: []string{"PLUGIN_BUILD_ARGS"},
Usage: "custom build arguments for the build", Usage: "custom build arguments for the build",
Destination: &settings.Build.Args, Value: &plugin_types.StringMapFlag{},
Category: category, Category: category,
}, },
&cli.StringSliceFlag{ &cli.StringSliceFlag{
Name: "args-from-env", Name: "args-from-env",