Skip to content

Commit 067f9f7

Browse files
committed
feat: Add global env
Supports configuring global `env` variables that will be applied to all builds. Modifies the `builder` function to accept a `buildContext` structure. This will simplify similar modifications in the future. Fixes #1305 Signed-off-by: Nathan Mittler <[email protected]>
1 parent bde269b commit 067f9f7

File tree

8 files changed

+111
-17
lines changed

8 files changed

+111
-17
lines changed

docs/configuration.md

+25
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,31 @@ You can also use the `KO_DEFAULTPLATFORMS` environment variable to set the defau
104104
KO_DEFAULTPLATFORMS=linux/arm64,linux/amd64
105105
```
106106

107+
### Setting build environment variables
108+
109+
By default, `ko` builds use the ambient environment from the system (i.e. `os.Environ()`).
110+
These values can be overridden globally or per-build (or both).
111+
112+
```yaml
113+
env:
114+
- FOO=foo
115+
builds:
116+
- id: foo
117+
dir: .
118+
main: ./foobar/foo
119+
env:
120+
- FOO=bar # Overrides the global value.
121+
- id: bar
122+
dir: ./bar
123+
main: .
124+
```
125+
126+
For a given build, the environment variables are merged in the following order:
127+
128+
- System `os.Environ` (lowest precedence)
129+
- Global `env`
130+
- Build `env` (highest precedence)
131+
107132
### Environment Variables (advanced)
108133

109134
For ease of use, backward compatibility and advanced use cases, `ko` supports the following environment variables to

pkg/build/gobuild.go

+31-12
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,16 @@ const (
6161
// GetBase takes an importpath and returns a base image reference and base image (or index).
6262
type GetBase func(context.Context, string) (name.Reference, Result, error)
6363

64-
type builder func(context.Context, string, string, v1.Platform, Config) (string, error)
64+
// buildContext provides parameters for a builder function.
65+
type buildContext struct {
66+
ip string
67+
dir string
68+
env []string
69+
platform v1.Platform
70+
config Config
71+
}
72+
73+
type builder func(context.Context, buildContext) (string, error)
6574

6675
type sbomber func(context.Context, string, string, string, oci.SignedEntity, string) ([]byte, types.MediaType, error)
6776

@@ -81,6 +90,7 @@ type gobuild struct {
8190
disableOptimizations bool
8291
trimpath bool
8392
buildConfigs map[string]Config
93+
env []string
8494
platformMatcher *platformMatcher
8595
dir string
8696
labels map[string]string
@@ -103,6 +113,7 @@ type gobuildOpener struct {
103113
disableOptimizations bool
104114
trimpath bool
105115
buildConfigs map[string]Config
116+
env []string
106117
platforms []string
107118
labels map[string]string
108119
dir string
@@ -131,6 +142,7 @@ func (gbo *gobuildOpener) Open() (Interface, error) {
131142
disableOptimizations: gbo.disableOptimizations,
132143
trimpath: gbo.trimpath,
133144
buildConfigs: gbo.buildConfigs,
145+
env: gbo.env,
134146
labels: gbo.labels,
135147
dir: gbo.dir,
136148
platformMatcher: matcher,
@@ -251,8 +263,8 @@ func getGoBinary() string {
251263
return defaultGoBin
252264
}
253265

254-
func build(ctx context.Context, ip string, dir string, platform v1.Platform, config Config) (string, error) {
255-
buildArgs, err := createBuildArgs(config)
266+
func build(ctx context.Context, buildCtx buildContext) (string, error) {
267+
buildArgs, err := createBuildArgs(buildCtx.config)
256268
if err != nil {
257269
return "", err
258270
}
@@ -261,9 +273,9 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
261273
args = append(args, "build")
262274
args = append(args, buildArgs...)
263275

264-
env, err := buildEnv(platform, os.Environ(), config.Env)
276+
env, err := buildEnv(buildCtx.platform, os.Environ(), buildCtx.env, buildCtx.config.Env)
265277
if err != nil {
266-
return "", fmt.Errorf("could not create env for %s: %w", ip, err)
278+
return "", fmt.Errorf("could not create env for %s: %w", buildCtx.ip, err)
267279
}
268280

269281
tmpDir := ""
@@ -282,7 +294,7 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
282294
}
283295

284296
// TODO(#264): if KOCACHE is unset, default to filepath.Join(os.TempDir(), "ko").
285-
tmpDir = filepath.Join(dir, "bin", ip, platform.String())
297+
tmpDir = filepath.Join(dir, "bin", buildCtx.ip, buildCtx.platform.String())
286298
if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil {
287299
return "", fmt.Errorf("creating KOCACHE bin dir: %w", err)
288300
}
@@ -296,18 +308,18 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
296308
file := filepath.Join(tmpDir, "out")
297309

298310
args = append(args, "-o", file)
299-
args = append(args, ip)
311+
args = append(args, buildCtx.ip)
300312

301313
gobin := getGoBinary()
302314
cmd := exec.CommandContext(ctx, gobin, args...)
303-
cmd.Dir = dir
315+
cmd.Dir = buildCtx.dir
304316
cmd.Env = env
305317

306318
var output bytes.Buffer
307319
cmd.Stderr = &output
308320
cmd.Stdout = &output
309321

310-
log.Printf("Building %s for %s", ip, platform)
322+
log.Printf("Building %s for %s", buildCtx.ip, buildCtx.platform)
311323
if err := cmd.Run(); err != nil {
312324
if os.Getenv("KOCACHE") == "" {
313325
os.RemoveAll(tmpDir)
@@ -440,7 +452,7 @@ func cycloneDX() sbomber {
440452
// buildEnv creates the environment variables used by the `go build` command.
441453
// From `os/exec.Cmd`: If Env contains duplicate environment keys, only the last
442454
// value in the slice for each duplicate key is used.
443-
func buildEnv(platform v1.Platform, userEnv, configEnv []string) ([]string, error) {
455+
func buildEnv(platform v1.Platform, osEnv, globalEnv, configEnv []string) ([]string, error) {
444456
// Default env
445457
env := []string{
446458
"CGO_ENABLED=0",
@@ -464,7 +476,8 @@ func buildEnv(platform v1.Platform, userEnv, configEnv []string) ([]string, erro
464476
}
465477
}
466478

467-
env = append(env, userEnv...)
479+
env = append(env, osEnv...)
480+
env = append(env, globalEnv...)
468481
env = append(env, configEnv...)
469482
return env, nil
470483
}
@@ -836,7 +849,13 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
836849
}
837850
// Do the build into a temporary file.
838851
config := g.configForImportPath(ref.Path())
839-
file, err := g.build(ctx, ref.Path(), g.dir, *platform, config)
852+
file, err := g.build(ctx, buildContext{
853+
ip: ref.Path(),
854+
dir: g.dir,
855+
env: g.env,
856+
platform: *platform,
857+
config: config,
858+
})
840859
if err != nil {
841860
return nil, fmt.Errorf("build: %w", err)
842861
}

pkg/build/gobuild_test.go

+21-5
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,8 @@ func TestBuildEnv(t *testing.T) {
217217
tests := []struct {
218218
description string
219219
platform v1.Platform
220-
userEnv []string
220+
osEnv []string
221+
globalEnv []string
221222
configEnv []string
222223
expectedEnvs map[string]string
223224
}{{
@@ -233,13 +234,28 @@ func TestBuildEnv(t *testing.T) {
233234
},
234235
}, {
235236
description: "override a default value",
237+
osEnv: []string{"CGO_ENABLED=0"},
238+
configEnv: []string{"CGO_ENABLED=1"},
239+
expectedEnvs: map[string]string{
240+
"CGO_ENABLED": "1",
241+
},
242+
}, {
243+
description: "global override a default value",
244+
osEnv: []string{"CGO_ENABLED=0"},
245+
globalEnv: []string{"CGO_ENABLED=1"},
246+
expectedEnvs: map[string]string{
247+
"CGO_ENABLED": "1",
248+
},
249+
}, {
250+
description: "override a global value",
251+
globalEnv: []string{"CGO_ENABLED=0"},
236252
configEnv: []string{"CGO_ENABLED=1"},
237253
expectedEnvs: map[string]string{
238254
"CGO_ENABLED": "1",
239255
},
240256
}, {
241257
description: "override an envvar and add an envvar",
242-
userEnv: []string{"CGO_ENABLED=0"},
258+
osEnv: []string{"CGO_ENABLED=0"},
243259
configEnv: []string{"CGO_ENABLED=1", "GOPRIVATE=git.internal.example.com,source.developers.google.com"},
244260
expectedEnvs: map[string]string{
245261
"CGO_ENABLED": "1",
@@ -279,7 +295,7 @@ func TestBuildEnv(t *testing.T) {
279295
}}
280296
for _, test := range tests {
281297
t.Run(test.description, func(t *testing.T) {
282-
env, err := buildEnv(test.platform, test.userEnv, test.configEnv)
298+
env, err := buildEnv(test.platform, test.osEnv, test.globalEnv, test.configEnv)
283299
if err != nil {
284300
t.Fatalf("unexpected error running buildEnv(): %v", err)
285301
}
@@ -401,7 +417,7 @@ func fauxSBOM(context.Context, string, string, string, oci.SignedEntity, string)
401417
}
402418

403419
// A helper method we use to substitute for the default "build" method.
404-
func writeTempFile(_ context.Context, s string, _ string, _ v1.Platform, _ Config) (string, error) {
420+
func writeTempFile(_ context.Context, buildCtx buildContext) (string, error) {
405421
tmpDir, err := os.MkdirTemp("", "ko")
406422
if err != nil {
407423
return "", err
@@ -412,7 +428,7 @@ func writeTempFile(_ context.Context, s string, _ string, _ v1.Platform, _ Confi
412428
return "", err
413429
}
414430
defer file.Close()
415-
if _, err := file.WriteString(filepath.ToSlash(s)); err != nil {
431+
if _, err := file.WriteString(filepath.ToSlash(buildCtx.ip)); err != nil {
416432
return "", err
417433
}
418434
return file.Name(), nil

pkg/build/options.go

+9
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,15 @@ func WithConfig(buildConfigs map[string]Config) Option {
8585
}
8686
}
8787

88+
// WithEnv is a functional option for providing a global set of environment
89+
// variables across all builds.
90+
func WithEnv(env []string) Option {
91+
return func(gbo *gobuildOpener) error {
92+
gbo.env = env
93+
return nil
94+
}
95+
}
96+
8897
// WithPlatforms is a functional option for building certain platforms for
8998
// multi-platform base images. To build everything from the base, use "all",
9099
// otherwise use a list of platform specs, i.e.:

pkg/commands/options/build.go

+8
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ type BuildOptions struct {
4747
// DefaultPlatforms defines the default platforms when Platforms is not explicitly defined
4848
DefaultPlatforms []string
4949

50+
// Env allows setting environment variables globally and applying them to each build.
51+
Env []string
52+
5053
// WorkingDirectory allows for setting the working directory for invocations of the `go` tool.
5154
// Empty string means the current working directory.
5255
WorkingDirectory string
@@ -138,6 +141,11 @@ func (bo *BuildOptions) LoadConfig() error {
138141
bo.DefaultPlatforms = dp
139142
}
140143

144+
env := v.GetStringSlice("env")
145+
if len(env) > 0 {
146+
bo.Env = env
147+
}
148+
141149
if bo.BaseImage == "" {
142150
ref := v.GetString("defaultBaseImage")
143151
if _, err := name.ParseReference(ref); err != nil {

pkg/commands/options/build_test.go

+15
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,21 @@ func TestDefaultPlatformsAll(t *testing.T) {
6767
}
6868
}
6969

70+
func TestEnv(t *testing.T) {
71+
bo := &BuildOptions{
72+
WorkingDirectory: "testdata/config",
73+
}
74+
err := bo.LoadConfig()
75+
if err != nil {
76+
t.Fatal(err)
77+
}
78+
79+
wantEnv := []string{"FOO=bar"} // matches value in ./testdata/config/.ko.yaml
80+
if !reflect.DeepEqual(bo.Env, wantEnv) {
81+
t.Fatalf("wanted Env %s, got %s", wantEnv, bo.Env)
82+
}
83+
}
84+
7085
func TestBuildConfigWithWorkingDirectoryAndDirAndMain(t *testing.T) {
7186
bo := &BuildOptions{
7287
WorkingDirectory: "testdata/paths",
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
defaultBaseImage: alpine
22
defaultPlatforms: all
3+
env: FOO=bar

pkg/commands/resolver.go

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) {
8686

8787
opts := []build.Option{
8888
build.WithBaseImages(getBaseImage(bo)),
89+
build.WithEnv(bo.Env),
8990
build.WithPlatforms(bo.Platforms...),
9091
build.WithJobs(bo.ConcurrentBuilds),
9192
}

0 commit comments

Comments
 (0)