Skip to content

Commit

Permalink
Include build info in template params
Browse files Browse the repository at this point in the history
This restructures the build logic in order to expand the buildArgs to include:

- `Env`: the actual environment variables used to execute the build. This includes platform info (e.g. `GOOS`, `GOARCH`).
- `GoEnv`: the map of variables from `go env`, but overridden with any platform-specific values defined in `Env`.

Fixes #1301
  • Loading branch information
nmittler committed May 9, 2024
1 parent bde269b commit 9786ff3
Show file tree
Hide file tree
Showing 2 changed files with 169 additions and 16 deletions.
91 changes: 75 additions & 16 deletions pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package build

import (
"archive/tar"
"bufio"
"bytes"
"context"
"errors"
Expand Down Expand Up @@ -252,7 +253,14 @@ func getGoBinary() string {
}

func build(ctx context.Context, ip string, dir string, platform v1.Platform, config Config) (string, error) {
buildArgs, err := createBuildArgs(config)
// Merge the user and config environment variables.
mergedEnv, err := buildEnv(platform, os.Environ(), config.Env)
if err != nil {
return "", fmt.Errorf("could not create env for %s: %w", ip, err)
}

// Get the version of the GO binary used to build.
buildArgs, err := createBuildArgs(ctx, mergedEnv, config)
if err != nil {
return "", err
}
Expand All @@ -261,11 +269,6 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
args = append(args, "build")
args = append(args, buildArgs...)

env, err := buildEnv(platform, os.Environ(), config.Env)
if err != nil {
return "", fmt.Errorf("could not create env for %s: %w", ip, err)
}

tmpDir := ""

if dir := os.Getenv("KOCACHE"); dir != "" {
Expand Down Expand Up @@ -301,7 +304,7 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
gobin := getGoBinary()
cmd := exec.CommandContext(ctx, gobin, args...)
cmd.Dir = dir
cmd.Env = env
cmd.Env = mergedEnv

var output bytes.Buffer
cmd.Stderr = &output
Expand All @@ -310,13 +313,49 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
log.Printf("Building %s for %s", ip, platform)
if err := cmd.Run(); err != nil {
if os.Getenv("KOCACHE") == "" {
os.RemoveAll(tmpDir)
_ = os.RemoveAll(tmpDir)
}
return "", fmt.Errorf("go build: %w: %s", err, output.String())
}
return file, nil
}

func goenv(ctx context.Context) (map[string]string, error) {
gobin := getGoBinary()
cmd := exec.CommandContext(ctx, gobin, "env")
var output bytes.Buffer
cmd.Stdout = &output
cmd.Stderr = &output
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("go env: %w: %s", err, output.String())
}

env := make(map[string]string)
scanner := bufio.NewScanner(bytes.NewReader(output.Bytes()))

line := 0
for scanner.Scan() {
line++
kv := strings.SplitN(scanner.Text(), "=", 2)
if len(kv) != 2 {
return nil, fmt.Errorf("go env: failed parsing line: %d", line)
}
key := strings.TrimSpace(kv[0])
value := strings.TrimSpace(kv[1])

// Unquote the value. Handle single or double quoted strings.
if len(value) > 1 && ((value[0] == '\'' && value[len(value)-1] == '\'') ||
(value[0] == '"' && value[len(value)-1] == '"')) {
value = value[1 : len(value)-1]
}
env[key] = value
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("go env: failed parsing: %w", err)
}
return env, nil
}

func goversionm(ctx context.Context, file string, appPath string, appFileName string, se oci.SignedEntity, dir string) ([]byte, types.MediaType, error) {
gobin := getGoBinary()

Expand Down Expand Up @@ -708,18 +747,35 @@ func (g *gobuild) tarKoData(ref reference, platform *v1.Platform) (*bytes.Buffer
return buf, walkRecursive(tw, root, chroot, creationTime, platform)
}

func createTemplateData() map[string]interface{} {
func createTemplateData(ctx context.Context, mergedEnv []string) (map[string]interface{}, error) {
envVars := map[string]string{
"LDFLAGS": "",
}
for _, entry := range os.Environ() {
for _, entry := range mergedEnv {
kv := strings.SplitN(entry, "=", 2)
if len(kv) != 2 {
return nil, fmt.Errorf("invalid entry in os.Environ %q", entry)
}
envVars[kv[0]] = kv[1]
}

return map[string]interface{}{
"Env": envVars,
// Get the go environment.
goEnv, err := goenv(ctx)
if err != nil {
return nil, err
}

// Override go env with any matching values from the merged variables.
for k, v := range envVars {
if _, ok := goEnv[k]; ok {
goEnv[k] = v
}
}

return map[string]interface{}{
"Env": envVars,
"GoEnv": goEnv,
}, nil
}

func applyTemplating(list []string, data map[string]interface{}) ([]string, error) {
Expand All @@ -741,10 +797,13 @@ func applyTemplating(list []string, data map[string]interface{}) ([]string, erro
return result, nil
}

func createBuildArgs(buildCfg Config) ([]string, error) {
func createBuildArgs(ctx context.Context, mergedEnv []string, buildCfg Config) ([]string, error) {
var args []string

data := createTemplateData()
data, err := createTemplateData(ctx, mergedEnv)
if err != nil {
return nil, err
}

if len(buildCfg.Flags) > 0 {
flags, err := applyTemplating(buildCfg.Flags, data)
Expand Down Expand Up @@ -1063,7 +1122,7 @@ func (g *gobuild) buildAll(ctx context.Context, ref string, baseRef name.Referen
return nil, err
}

matches := []v1.Descriptor{}
var matches []v1.Descriptor
for _, desc := range im.Manifests {
// Nested index is pretty rare. We could support this in theory, but return an error for now.
if desc.MediaType != types.OCIManifestSchema1 && desc.MediaType != types.DockerManifestSchema2 {
Expand Down Expand Up @@ -1188,7 +1247,7 @@ func parseSpec(spec []string) (*platformMatcher, error) {
return &platformMatcher{spec: spec}, nil
}

platforms := []v1.Platform{}
var platforms []v1.Platform
for _, s := range spec {
p, err := v1.ParsePlatform(s)
if err != nil {
Expand Down
94 changes: 94 additions & 0 deletions pkg/build/gobuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,100 @@ func TestBuildEnv(t *testing.T) {
}
}

func TestGoEnv(t *testing.T) {
goVars, err := goenv(context.TODO())
if err != nil {
t.Fatalf("unexpected error running goenv(): %v", err)
}

// Just check some basic values.
if goVars["GOOS"] != runtime.GOOS {
t.Fatalf("goenv(): invalid GOOS value: '%s', expected: '%s'", goVars["GOOS"], runtime.GOOS)
}
if goVars["GOARCH"] != runtime.GOARCH {
t.Fatalf("goenv(): invalid GOARCH value: '%s', expected: '%s'", goVars["GOARCH"], runtime.GOARCH)
}
}

func TestCreateTemplateData(t *testing.T) {
tests := []struct {
name string
mergedEnv []string
expectError bool
expectedVars map[string]string
expectedGoVars map[string]string
}{
{
name: "bad env",
mergedEnv: []string{"bad"},
expectError: true,
},
{
name: "no env vars",
expectedVars: map[string]string{
"LDFLAGS": "",
},
expectedGoVars: map[string]string{
"GOOS": runtime.GOOS,
"GOARCH": runtime.GOARCH,
},
},
{
name: "env vars",
mergedEnv: []string{"foo=bar", "bar=baz"},
expectedVars: map[string]string{
"LDFLAGS": "",
"foo": "bar",
"bar": "baz",
},
expectedGoVars: map[string]string{
"GOOS": runtime.GOOS,
"GOARCH": runtime.GOARCH,
},
},
{
name: "go env vars",
mergedEnv: []string{"GOOS=testgoos", "GOARCH=testgoarch"},
expectedVars: map[string]string{
"LDFLAGS": "",
"GOOS": "testgoos",
"GOARCH": "testgoarch",
},
expectedGoVars: map[string]string{
"GOOS": "testgoos",
"GOARCH": "testgoarch",
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := createTemplateData(context.TODO(), test.mergedEnv)
if test.expectError {
if err == nil {
t.Fatalf("expected an error")
}
} else {
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
actualVars := actual["Env"].(map[string]string)
actualGoVars := actual["GoEnv"].(map[string]string)
for k, v := range test.expectedVars {
if actualVars[k] != v {
t.Fatalf("expected env var %s=%s, got %s", k, v, actual[k])
}
}
for k, v := range test.expectedGoVars {
if actualGoVars[k] != v {
t.Fatalf("expected go env var %s=%s, got %s", k, v, actualGoVars[k])
}
}
}
})
}
}

func TestBuildConfig(t *testing.T) {
tests := []struct {
description string
Expand Down

0 comments on commit 9786ff3

Please sign in to comment.