Skip to content

Commit

Permalink
feat: add build info to 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 01406de
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

Check failure on line 1250 in pkg/build/gobuild.go

View workflow job for this annotation

GitHub Actions / lint

Consider pre-allocating `platforms` (prealloc)
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 01406de

Please sign in to comment.