Skip to content

Commit 4bef8cf

Browse files
authored
Adding file config support for docker backend (#32)
1 parent 9f67bb2 commit 4bef8cf

5 files changed

Lines changed: 422 additions & 18 deletions

File tree

go.mod

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,15 @@ require (
2525
github.com/docker/go-units v0.5.0 // indirect
2626
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
2727
github.com/felixge/httpsnoop v1.0.4 // indirect
28+
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
2829
github.com/go-logr/logr v1.4.3 // indirect
2930
github.com/go-logr/stdr v1.2.2 // indirect
31+
github.com/go-playground/locales v0.14.1 // indirect
32+
github.com/go-playground/universal-translator v0.18.1 // indirect
33+
github.com/go-playground/validator/v10 v10.30.1 // indirect
3034
github.com/golang/protobuf v1.5.0 // indirect
3135
github.com/gorilla/mux v1.8.1 // indirect
36+
github.com/leodido/go-urn v1.4.0 // indirect
3237
github.com/mattn/go-colorable v0.1.13 // indirect
3338
github.com/mattn/go-isatty v0.0.19 // indirect
3439
github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect
@@ -50,8 +55,11 @@ require (
5055
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.39.0 // indirect
5156
go.opentelemetry.io/otel/metric v1.39.0 // indirect
5257
go.opentelemetry.io/otel/trace v1.39.0 // indirect
58+
golang.org/x/crypto v0.46.0 // indirect
5359
golang.org/x/sys v0.39.0 // indirect
60+
golang.org/x/text v0.32.0 // indirect
5461
golang.org/x/time v0.14.0 // indirect
5562
google.golang.org/protobuf v1.36.10 // indirect
63+
gopkg.in/yaml.v3 v3.0.1 // indirect
5664
gotest.tools/v3 v3.5.2 // indirect
5765
)

go.sum

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU
5353
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
5454
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
5555
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
56+
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
57+
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
5658
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
5759
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
5860
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
@@ -64,6 +66,12 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
6466
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
6567
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
6668
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
69+
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
70+
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
71+
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
72+
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
73+
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
74+
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
6775
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
6876
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
6977
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@@ -110,6 +118,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
110118
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
111119
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
112120
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
121+
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
122+
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
113123
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
114124
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
115125
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -203,6 +213,8 @@ go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pq
203213
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
204214
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
205215
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
216+
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
217+
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
206218
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
207219
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
208220
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -238,6 +250,8 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
238250
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
239251
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
240252
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
253+
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
254+
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
241255
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
242256
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
243257
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

internal/config/config.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package config
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"os"
8+
"reflect"
9+
"strings"
10+
11+
"github.com/go-playground/validator/v10"
12+
"gopkg.in/yaml.v3"
13+
)
14+
15+
// FileConfig represents the top-level YAML configuration file.
16+
type FileConfig struct {
17+
WorkerID string `yaml:"worker_id"`
18+
Cleanup *bool `yaml:"cleanup"`
19+
Backend BackendConfig `yaml:"backend"`
20+
}
21+
22+
// BackendConfig contains the backend selection.
23+
// At most one backend field may be non-nil; configuring multiple backends simultaneously is an error.
24+
type BackendConfig struct {
25+
Docker *DockerConfig `yaml:"docker"`
26+
}
27+
28+
// DockerConfig holds Docker-backend-specific configuration.
29+
type DockerConfig struct {
30+
Volumes []string `yaml:"volumes"`
31+
Environment []EnvEntry `yaml:"environment" validate:"dive"`
32+
}
33+
34+
// EnvEntry represents a single environment variable in the config file.
35+
// If Value is nil, the variable is inherited from the host process environment.
36+
type EnvEntry struct {
37+
Name string `yaml:"name" validate:"required,no_whitespace"`
38+
Value *string `yaml:"value"`
39+
}
40+
41+
// configValidator is the package-level validator instance, initialized once.
42+
var configValidator = newConfigValidator()
43+
44+
func newConfigValidator() *validator.Validate {
45+
v := validator.New()
46+
47+
// no_whitespace rejects strings that contain spaces or tabs.
48+
_ = v.RegisterValidation("no_whitespace", func(fl validator.FieldLevel) bool {
49+
return !strings.ContainsAny(fl.Field().String(), " \t")
50+
})
51+
52+
// Struct-level validator for BackendConfig: at most one backend field may be non-nil.
53+
// Uses reflection so that future backend fields are automatically covered.
54+
v.RegisterStructValidation(func(sl validator.StructLevel) {
55+
cfg := sl.Current()
56+
configured := 0
57+
for i := 0; i < cfg.NumField(); i++ {
58+
if cfg.Field(i).Kind() == reflect.Ptr && !cfg.Field(i).IsNil() {
59+
configured++
60+
}
61+
}
62+
if configured > 1 {
63+
sl.ReportError(sl.Current().Interface(), "Backend", "Backend", "only_one_backend", "")
64+
}
65+
}, BackendConfig{})
66+
67+
return v
68+
}
69+
70+
// Load reads and validates a YAML config file.
71+
func Load(path string) (*FileConfig, error) {
72+
data, err := os.ReadFile(path)
73+
if err != nil {
74+
return nil, fmt.Errorf("failed to read config file: %w", err)
75+
}
76+
77+
var cfg FileConfig
78+
decoder := yaml.NewDecoder(bytes.NewReader(data))
79+
decoder.KnownFields(true)
80+
if err := decoder.Decode(&cfg); err != nil {
81+
return nil, fmt.Errorf("failed to parse config file: %w", err)
82+
}
83+
84+
if err := configValidator.Struct(cfg); err != nil {
85+
return nil, fmt.Errorf("invalid config: %w", formatValidationErrors(err))
86+
}
87+
88+
return &cfg, nil
89+
}
90+
91+
// formatValidationErrors converts validator.ValidationErrors into a human-readable error.
92+
func formatValidationErrors(err error) error {
93+
var validationErrors validator.ValidationErrors
94+
if !errors.As(err, &validationErrors) {
95+
return err
96+
}
97+
98+
msgs := make([]string, 0, len(validationErrors))
99+
for _, e := range validationErrors {
100+
switch e.Tag() {
101+
case "required":
102+
msgs = append(msgs, fmt.Sprintf("%s is required", e.Namespace()))
103+
case "no_whitespace":
104+
msgs = append(msgs, fmt.Sprintf("%s must not contain whitespace", e.Namespace()))
105+
case "only_one_backend":
106+
msgs = append(msgs, "at most one backend may be configured")
107+
default:
108+
msgs = append(msgs, fmt.Sprintf("%s failed validation %q", e.Namespace(), e.Tag()))
109+
}
110+
}
111+
return fmt.Errorf("%s", strings.Join(msgs, "; "))
112+
}
113+
114+
// ResolveEnv converts environment entries to a map, resolving host-inherited values.
115+
func ResolveEnv(entries []EnvEntry) map[string]string {
116+
result := make(map[string]string, len(entries))
117+
for _, entry := range entries {
118+
if entry.Value != nil {
119+
result[entry.Name] = *entry.Value
120+
} else {
121+
result[entry.Name] = os.Getenv(entry.Name)
122+
}
123+
}
124+
return result
125+
}

0 commit comments

Comments
 (0)