From 6ffb3aea60e798b970af649be5798228cce7310a Mon Sep 17 00:00:00 2001 From: David Fridrich Date: Tue, 13 Jan 2026 14:55:22 +0100 Subject: [PATCH] fix: double go download buildpacks --- pkg/buildpacks/builder.go | 30 ++++++++++++++++++++++++++++++ pkg/buildpacks/builder_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/pkg/buildpacks/builder.go b/pkg/buildpacks/builder.go index a341b08226..fa81ee17b0 100644 --- a/pkg/buildpacks/builder.go +++ b/pkg/buildpacks/builder.go @@ -19,6 +19,7 @@ import ( "github.com/buildpacks/pack/pkg/project/types" "github.com/docker/docker/client" "github.com/heroku/color" + "golang.org/x/mod/modfile" "knative.dev/func/pkg/builders" "knative.dev/func/pkg/docker" @@ -186,6 +187,17 @@ func (b *Builder) Build(ctx context.Context, f fn.Function, platforms []fn.Platf opts.Env["BPE_DEFAULT_LISTEN_ADDRESS"] = "[::]:8080" } + // use "explicit default" go version in function's go.mod file via variable + // if not explicitly set via buildEnvs (in func.yaml). This ensures single + // go version in buildpacks. See https://github.com/knative/func/issues/3178 + if f.Runtime == "go" { + if _, ok := opts.Env["BP_GO_VERSION"]; !ok { + if goVersion := parseGoModVersion(f.Root); goVersion != "" { + opts.Env["BP_GO_VERSION"] = goVersion + } + } + } + var bindings = make([]string, 0, len(f.Build.Mounts)) for _, m := range f.Build.Mounts { bindings = append(bindings, fmt.Sprintf("%s:%s", m.Source, m.Destination)) @@ -297,6 +309,24 @@ func BuilderImage(f fn.Function, builderName string) (string, error) { return builders.Image(f, builderName, DefaultBuilderImages) } +// parseGoModVersion reads the go.mod file and returns the Go version directive. +// Returns empty string if go.mod doesn't exist or has no go directive. +func parseGoModVersion(root string) string { + goModPath := filepath.Join(root, "go.mod") + data, err := os.ReadFile(goModPath) + if err != nil { + return "" + } + f, err := modfile.Parse(goModPath, data, nil) + if err != nil { + return "" + } + if f.Go == nil { + return "" + } + return f.Go.Version +} + // Errors type ErrRuntimeRequired struct{} diff --git a/pkg/buildpacks/builder_test.go b/pkg/buildpacks/builder_test.go index d2af0bc70d..523f7a2278 100644 --- a/pkg/buildpacks/builder_test.go +++ b/pkg/buildpacks/builder_test.go @@ -232,6 +232,35 @@ func TestBuild_Errors(t *testing.T) { } } +// TestParseGoModVersion tests parsing of Go version from go.mod +func TestParseGoModVersion(t *testing.T) { + tests := []struct { + name string + content string + writeFile bool + expected string + }{ + {"valid go version", "module test\n\ngo 1.25", true, "1.25"}, + {"go version with toolchain", "module test\n\ngo 1.25\ntoolchain go1.25.4", true, "1.25"}, + {"no go directive", "module test", true, ""}, + {"no go.mod file", "", false, ""}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tempDir := t.TempDir() + if tt.writeFile { + if err := os.WriteFile(filepath.Join(tempDir, "go.mod"), []byte(tt.content), 0644); err != nil { + t.Fatal(err) + } + } + if got := parseGoModVersion(tempDir); got != tt.expected { + t.Errorf("parseGoModVersion() = %q, want %q", got, tt.expected) + } + }) + } +} + type mockImpl struct { BuildFn func(context.Context, pack.BuildOptions) error }