diff --git a/README.md b/README.md index 24e515ce..c44cecf8 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,8 @@ configuration section in your `.ko.yaml`. builds: - id: foo main: ./foobar/foo + env: + - GOPRIVATE=git.internal.example.com,source.developers.google.com flags: - -tags - netgo @@ -144,6 +146,8 @@ builds: - -X main.version={{.Env.VERSION}} - id: bar main: ./foobar/bar/main.go + env: + - GOCACHE=/workspace/.gocache ldflags: - -s - -w @@ -156,7 +160,7 @@ with the intended import path. _Please note:_ Even though the configuration section is similar to the [GoReleaser `builds` section](https://goreleaser.com/customization/build/), -only the `flags` and `ldflags` fields are currently supported. Also, the +only the `env`, `flags` and `ldflags` fields are currently supported. Also, the templating support is currently limited to environment variables only. ## Naming Images diff --git a/pkg/build/config.go b/pkg/build/config.go index cb191648..5e1f4cf7 100644 --- a/pkg/build/config.go +++ b/pkg/build/config.go @@ -79,6 +79,9 @@ type Config struct { Ldflags StringArray `yaml:",omitempty"` Flags FlagArray `yaml:",omitempty"` + // Env allows setting environment variables for `go build` + Env []string `yaml:",omitempty"` + // Other GoReleaser fields that are not supported or do not make sense // in the context of ko, for reference or for future use: // Goos []string `yaml:",omitempty"` @@ -87,7 +90,6 @@ type Config struct { // Gomips []string `yaml:",omitempty"` // Targets []string `yaml:",omitempty"` // Binary string `yaml:",omitempty"` - // Env []string `yaml:",omitempty"` // Lang string `yaml:",omitempty"` // Asmflags StringArray `yaml:",omitempty"` // Gcflags StringArray `yaml:",omitempty"` diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go index 07017ec6..81d5d74b 100644 --- a/pkg/build/gobuild.go +++ b/pkg/build/gobuild.go @@ -378,24 +378,11 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con cmd := exec.CommandContext(ctx, "go", args...) cmd.Dir = dir - // Last one wins - defaultEnv := []string{ - "CGO_ENABLED=0", - "GOOS=" + platform.OS, - "GOARCH=" + platform.Architecture, + env, err := buildEnv(platform, config.Env) + if err != nil { + return "", fmt.Errorf("could not create env for %s: %v", ip, err) } - - if strings.HasPrefix(platform.Architecture, "arm") && platform.Variant != "" { - goarm, err := getGoarm(platform) - if err != nil { - return "", fmt.Errorf("goarm failure for %s: %v", ip, err) - } - if goarm != "" { - defaultEnv = append(defaultEnv, "GOARM="+goarm) - } - } - - cmd.Env = append(defaultEnv, os.Environ()...) + cmd.Env = env var output bytes.Buffer cmd.Stderr = &output @@ -410,6 +397,31 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con return file, nil } +// buildEnv creates the environment variables used by the `go build` command. +// From `os/exec.Cmd`: If Env contains duplicate environment keys, only the last +// value in the slice for each duplicate key is used. +func buildEnv(platform v1.Platform, configEnv []string) ([]string, error) { + defaultEnv := []string{ + "CGO_ENABLED=0", + "GOOS=" + platform.OS, + "GOARCH=" + platform.Architecture, + } + + if strings.HasPrefix(platform.Architecture, "arm") && platform.Variant != "" { + goarm, err := getGoarm(platform) + if err != nil { + return nil, fmt.Errorf("goarm failure: %v", err) + } + if goarm != "" { + defaultEnv = append(defaultEnv, "GOARM="+goarm) + } + } + + env := append(defaultEnv, os.Environ()...) + env = append(env, configEnv...) + return env, nil +} + func appFilename(importpath string) string { base := filepath.Base(importpath) diff --git a/pkg/build/gobuild_test.go b/pkg/build/gobuild_test.go index 8f8211f5..3d253ef5 100644 --- a/pkg/build/gobuild_test.go +++ b/pkg/build/gobuild_test.go @@ -232,6 +232,83 @@ func TestGoBuildIsSupportedRefWithModules(t *testing.T) { } } +func TestBuildEnv(t *testing.T) { + tests := []struct { + description string + platform v1.Platform + configEnv []string + expectedEnvs map[string]string + }{ + { + description: "defaults", + platform: v1.Platform{ + OS: "linux", + Architecture: "amd64", + }, + expectedEnvs: map[string]string{ + "GOOS": "linux", + "GOARCH": "amd64", + "CGO_ENABLED": "0", + }, + }, + { + description: "override a default value", + configEnv: []string{"CGO_ENABLED=1"}, + expectedEnvs: map[string]string{ + "CGO_ENABLED": "1", + }, + }, + { + description: "override an envvar and add an envvar", + configEnv: []string{"CGO_ENABLED=1", "GOPRIVATE=git.internal.example.com,source.developers.google.com"}, + expectedEnvs: map[string]string{ + "CGO_ENABLED": "1", + "GOPRIVATE": "git.internal.example.com,source.developers.google.com", + }, + }, + { + description: "arm variant", + platform: v1.Platform{ + Architecture: "arm", + Variant: "v7", + }, + expectedEnvs: map[string]string{ + "GOARCH": "arm", + "GOARM": "7", + }, + }, + { + description: "arm64 variant", + platform: v1.Platform{ + Architecture: "arm64", + Variant: "v8", + }, + expectedEnvs: map[string]string{ + "GOARCH": "arm64", + "GOARM": "7", + }, + }, + } + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + env, err := buildEnv(test.platform, test.configEnv) + if err != nil { + t.Fatalf("unexpected error running buildEnv(): %v", err) + } + envs := map[string]string{} + for _, e := range env { + split := strings.SplitN(e, "=", 2) + envs[split[0]] = split[1] + } + for key, val := range test.expectedEnvs { + if envs[key] != val { + t.Errorf("buildEnv(): expected %s=%s, got %s=%s", key, val, key, envs[key]) + } + } + }) + } +} + // A helper method we use to substitute for the default "build" method. func writeTempFile(_ context.Context, s string, _ string, _ v1.Platform, _ Config) (string, error) { tmpDir, err := ioutil.TempDir("", "ko")