Skip to content

Commit 01406de

Browse files
committed
feat: add build info to template params
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
1 parent bde269b commit 01406de

File tree

2 files changed

+169
-16
lines changed

2 files changed

+169
-16
lines changed

pkg/build/gobuild.go

+75-16
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package build
1616

1717
import (
1818
"archive/tar"
19+
"bufio"
1920
"bytes"
2021
"context"
2122
"errors"
@@ -252,7 +253,14 @@ func getGoBinary() string {
252253
}
253254

254255
func build(ctx context.Context, ip string, dir string, platform v1.Platform, config Config) (string, error) {
255-
buildArgs, err := createBuildArgs(config)
256+
// Merge the user and config environment variables.
257+
mergedEnv, err := buildEnv(platform, os.Environ(), config.Env)
258+
if err != nil {
259+
return "", fmt.Errorf("could not create env for %s: %w", ip, err)
260+
}
261+
262+
// Get the version of the GO binary used to build.
263+
buildArgs, err := createBuildArgs(ctx, mergedEnv, config)
256264
if err != nil {
257265
return "", err
258266
}
@@ -261,11 +269,6 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
261269
args = append(args, "build")
262270
args = append(args, buildArgs...)
263271

264-
env, err := buildEnv(platform, os.Environ(), config.Env)
265-
if err != nil {
266-
return "", fmt.Errorf("could not create env for %s: %w", ip, err)
267-
}
268-
269272
tmpDir := ""
270273

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

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

323+
func goenv(ctx context.Context) (map[string]string, error) {
324+
gobin := getGoBinary()
325+
cmd := exec.CommandContext(ctx, gobin, "env")
326+
var output bytes.Buffer
327+
cmd.Stdout = &output
328+
cmd.Stderr = &output
329+
if err := cmd.Run(); err != nil {
330+
return nil, fmt.Errorf("go env: %w: %s", err, output.String())
331+
}
332+
333+
env := make(map[string]string)
334+
scanner := bufio.NewScanner(bytes.NewReader(output.Bytes()))
335+
336+
line := 0
337+
for scanner.Scan() {
338+
line++
339+
kv := strings.SplitN(scanner.Text(), "=", 2)
340+
if len(kv) != 2 {
341+
return nil, fmt.Errorf("go env: failed parsing line: %d", line)
342+
}
343+
key := strings.TrimSpace(kv[0])
344+
value := strings.TrimSpace(kv[1])
345+
346+
// Unquote the value. Handle single or double quoted strings.
347+
if len(value) > 1 && ((value[0] == '\'' && value[len(value)-1] == '\'') ||
348+
(value[0] == '"' && value[len(value)-1] == '"')) {
349+
value = value[1 : len(value)-1]
350+
}
351+
env[key] = value
352+
}
353+
if err := scanner.Err(); err != nil {
354+
return nil, fmt.Errorf("go env: failed parsing: %w", err)
355+
}
356+
return env, nil
357+
}
358+
320359
func goversionm(ctx context.Context, file string, appPath string, appFileName string, se oci.SignedEntity, dir string) ([]byte, types.MediaType, error) {
321360
gobin := getGoBinary()
322361

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

711-
func createTemplateData() map[string]interface{} {
750+
func createTemplateData(ctx context.Context, mergedEnv []string) (map[string]interface{}, error) {
712751
envVars := map[string]string{
713752
"LDFLAGS": "",
714753
}
715-
for _, entry := range os.Environ() {
754+
for _, entry := range mergedEnv {
716755
kv := strings.SplitN(entry, "=", 2)
756+
if len(kv) != 2 {
757+
return nil, fmt.Errorf("invalid entry in os.Environ %q", entry)
758+
}
717759
envVars[kv[0]] = kv[1]
718760
}
719761

720-
return map[string]interface{}{
721-
"Env": envVars,
762+
// Get the go environment.
763+
goEnv, err := goenv(ctx)
764+
if err != nil {
765+
return nil, err
722766
}
767+
768+
// Override go env with any matching values from the merged variables.
769+
for k, v := range envVars {
770+
if _, ok := goEnv[k]; ok {
771+
goEnv[k] = v
772+
}
773+
}
774+
775+
return map[string]interface{}{
776+
"Env": envVars,
777+
"GoEnv": goEnv,
778+
}, nil
723779
}
724780

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

744-
func createBuildArgs(buildCfg Config) ([]string, error) {
800+
func createBuildArgs(ctx context.Context, mergedEnv []string, buildCfg Config) ([]string, error) {
745801
var args []string
746802

747-
data := createTemplateData()
803+
data, err := createTemplateData(ctx, mergedEnv)
804+
if err != nil {
805+
return nil, err
806+
}
748807

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

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

1191-
platforms := []v1.Platform{}
1250+
var platforms []v1.Platform
11921251
for _, s := range spec {
11931252
p, err := v1.ParsePlatform(s)
11941253
if err != nil {

pkg/build/gobuild_test.go

+94
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,100 @@ func TestBuildEnv(t *testing.T) {
297297
}
298298
}
299299

300+
func TestGoEnv(t *testing.T) {
301+
goVars, err := goenv(context.TODO())
302+
if err != nil {
303+
t.Fatalf("unexpected error running goenv(): %v", err)
304+
}
305+
306+
// Just check some basic values.
307+
if goVars["GOOS"] != runtime.GOOS {
308+
t.Fatalf("goenv(): invalid GOOS value: '%s', expected: '%s'", goVars["GOOS"], runtime.GOOS)
309+
}
310+
if goVars["GOARCH"] != runtime.GOARCH {
311+
t.Fatalf("goenv(): invalid GOARCH value: '%s', expected: '%s'", goVars["GOARCH"], runtime.GOARCH)
312+
}
313+
}
314+
315+
func TestCreateTemplateData(t *testing.T) {
316+
tests := []struct {
317+
name string
318+
mergedEnv []string
319+
expectError bool
320+
expectedVars map[string]string
321+
expectedGoVars map[string]string
322+
}{
323+
{
324+
name: "bad env",
325+
mergedEnv: []string{"bad"},
326+
expectError: true,
327+
},
328+
{
329+
name: "no env vars",
330+
expectedVars: map[string]string{
331+
"LDFLAGS": "",
332+
},
333+
expectedGoVars: map[string]string{
334+
"GOOS": runtime.GOOS,
335+
"GOARCH": runtime.GOARCH,
336+
},
337+
},
338+
{
339+
name: "env vars",
340+
mergedEnv: []string{"foo=bar", "bar=baz"},
341+
expectedVars: map[string]string{
342+
"LDFLAGS": "",
343+
"foo": "bar",
344+
"bar": "baz",
345+
},
346+
expectedGoVars: map[string]string{
347+
"GOOS": runtime.GOOS,
348+
"GOARCH": runtime.GOARCH,
349+
},
350+
},
351+
{
352+
name: "go env vars",
353+
mergedEnv: []string{"GOOS=testgoos", "GOARCH=testgoarch"},
354+
expectedVars: map[string]string{
355+
"LDFLAGS": "",
356+
"GOOS": "testgoos",
357+
"GOARCH": "testgoarch",
358+
},
359+
expectedGoVars: map[string]string{
360+
"GOOS": "testgoos",
361+
"GOARCH": "testgoarch",
362+
},
363+
},
364+
}
365+
366+
for _, test := range tests {
367+
t.Run(test.name, func(t *testing.T) {
368+
actual, err := createTemplateData(context.TODO(), test.mergedEnv)
369+
if test.expectError {
370+
if err == nil {
371+
t.Fatalf("expected an error")
372+
}
373+
} else {
374+
if err != nil {
375+
t.Fatalf("unexpected error: %v", err)
376+
}
377+
actualVars := actual["Env"].(map[string]string)
378+
actualGoVars := actual["GoEnv"].(map[string]string)
379+
for k, v := range test.expectedVars {
380+
if actualVars[k] != v {
381+
t.Fatalf("expected env var %s=%s, got %s", k, v, actual[k])
382+
}
383+
}
384+
for k, v := range test.expectedGoVars {
385+
if actualGoVars[k] != v {
386+
t.Fatalf("expected go env var %s=%s, got %s", k, v, actualGoVars[k])
387+
}
388+
}
389+
}
390+
})
391+
}
392+
}
393+
300394
func TestBuildConfig(t *testing.T) {
301395
tests := []struct {
302396
description string

0 commit comments

Comments
 (0)