From ae594dcc74f5b2e6a0924661a8a16710a89d46e0 Mon Sep 17 00:00:00 2001 From: Halvard Skogsrud Date: Fri, 27 Aug 2021 02:33:01 +1000 Subject: [PATCH] Set build config via BuildOptions (#421) * Set build config via BuildOptions Enables programmatically overriding build configs when ko is embedded in another tool. Related: #340, #419 * Use local registry for base images in unit tests Tests create a local registry (using ggcr) with a dummy base image. This speeds up tests, since they don't need to hit gcr.io to fetch the default distroless base image. * Update function comment to refer to random image --- pkg/commands/options/build.go | 4 + pkg/commands/publisher_test.go | 9 ++- pkg/commands/resolver.go | 5 +- pkg/commands/resolver_test.go | 137 +++++++++++++++++++++++++-------- 4 files changed, 120 insertions(+), 35 deletions(-) diff --git a/pkg/commands/options/build.go b/pkg/commands/options/build.go index 20e32cde3f..5533655ff9 100644 --- a/pkg/commands/options/build.go +++ b/pkg/commands/options/build.go @@ -17,6 +17,7 @@ limitations under the License. package options import ( + "github.com/google/ko/pkg/build" "github.com/spf13/cobra" ) @@ -39,6 +40,9 @@ type BuildOptions struct { UserAgent string InsecureRegistry bool + + // BuildConfigs enables programmatic overriding of build config set in `.ko.yaml`. + BuildConfigs map[string]build.Config } func AddBuildOptions(cmd *cobra.Command, bo *BuildOptions) { diff --git a/pkg/commands/publisher_test.go b/pkg/commands/publisher_test.go index e188f03ebc..b0dd603373 100644 --- a/pkg/commands/publisher_test.go +++ b/pkg/commands/publisher_test.go @@ -29,7 +29,13 @@ import ( ) func TestPublishImages(t *testing.T) { - repo := "registry.example.com/repository" + namespace := "base" + s, err := registryServerWithImage(namespace) + if err != nil { + t.Fatalf("could not create test registry server: %v", err) + } + repo := s.Listener.Addr().String() + baseImage := fmt.Sprintf("%s/%s", repo, namespace) sampleAppDir, err := sampleAppRelDir() if err != nil { t.Fatalf("sampleAppRelDir(): %v", err) @@ -58,6 +64,7 @@ func TestPublishImages(t *testing.T) { for _, test := range tests { ctx := context.Background() bo := &options.BuildOptions{ + BaseImage: baseImage, ConcurrentBuilds: 1, } builder, err := NewBuilder(ctx, bo) diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index b2e95635f3..7f6ad811e6 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -106,7 +106,10 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) { opts = append(opts, build.WithLabel(parts[0], parts[1])) } - if len(buildConfigs) > 0 { + // prefer buildConfigs from BuildOptions + if bo.BuildConfigs != nil { + opts = append(opts, build.WithConfig(bo.BuildConfigs)) + } else if len(buildConfigs) > 0 { opts = append(opts, build.WithConfig(buildConfigs)) } diff --git a/pkg/commands/resolver_test.go b/pkg/commands/resolver_test.go index 53a4e67a59..3a322e2776 100644 --- a/pkg/commands/resolver_test.go +++ b/pkg/commands/resolver_test.go @@ -22,6 +22,8 @@ import ( "fmt" "io" "io/ioutil" + "log" + "net/http/httptest" "path" "strings" "testing" @@ -29,7 +31,9 @@ import ( "github.com/docker/docker/api/types" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/google/go-containerregistry/pkg/crane" "github.com/google/go-containerregistry/pkg/name" + "github.com/google/go-containerregistry/pkg/registry" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/daemon" "github.com/google/go-containerregistry/pkg/v1/empty" @@ -156,24 +160,73 @@ kind: Bar } } -func TestNewBuilderCanBuild(t *testing.T) { - ctx := context.Background() - bo := &options.BuildOptions{ - ConcurrentBuilds: 1, - } - builder, err := NewBuilder(ctx, bo) +func TestNewBuilder(t *testing.T) { + namespace := "base" + s, err := registryServerWithImage(namespace) if err != nil { - t.Fatalf("NewBuilder(): %v", err) + t.Fatalf("could not create test registry server: %v", err) } - res, err := builder.Build(ctx, "ko://github.com/google/ko/test") - if err != nil { - t.Fatalf("builder.Build(): %v", err) + baseImage := fmt.Sprintf("%s/%s", s.Listener.Addr().String(), namespace) + + tests := []struct { + description string + importpath string + bo *options.BuildOptions + wantQualifiedImportpath string + shouldBuildError bool + }{ + { + description: "test app with already qualified import path", + importpath: "ko://github.com/google/ko/test", + bo: &options.BuildOptions{ + BaseImage: baseImage, + ConcurrentBuilds: 1, + }, + wantQualifiedImportpath: "ko://github.com/google/ko/test", + shouldBuildError: false, + }, + { + description: "programmatic build config", + importpath: "./test", + bo: &options.BuildOptions{ + BaseImage: baseImage, + BuildConfigs: map[string]build.Config{ + "github.com/google/ko/test": { + ID: "id-can-be-anything", + // no easy way to assert on the output, so trigger error to ensure config is picked up + Flags: []string{"-invalid-flag-should-cause-error"}, + }, + }, + ConcurrentBuilds: 1, + WorkingDirectory: "../..", + }, + wantQualifiedImportpath: "ko://github.com/google/ko/test", + shouldBuildError: true, + }, } - gotDigest, err := res.Digest() - if err != nil { - t.Fatalf("res.Digest(): %v", err) + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + ctx := context.Background() + builder, err := NewBuilder(ctx, test.bo) + if err != nil { + t.Fatalf("NewBuilder(): %v", err) + } + qualifiedImportpath, err := builder.QualifyImport(test.importpath) + if err != nil { + t.Fatalf("builder.QualifyImport(%s): %v", test.importpath, err) + } + if qualifiedImportpath != test.wantQualifiedImportpath { + t.Fatalf("incorrect qualified import path, got %s, wanted %s", qualifiedImportpath, test.wantQualifiedImportpath) + } + _, err = builder.Build(ctx, qualifiedImportpath) + if err != nil && !test.shouldBuildError { + t.Fatalf("builder.Build(): %v", err) + } + if err == nil && test.shouldBuildError { + t.Fatalf("expected error got nil") + } + }) } - fmt.Println(gotDigest.String()) } func TestNewPublisherCanPublish(t *testing.T) { @@ -224,27 +277,45 @@ func TestNewPublisherCanPublish(t *testing.T) { }, } for _, test := range tests { - publisher, err := NewPublisher(test.po) - if err != nil { - t.Fatalf("%s: NewPublisher(): %v", test.description, err) - } - defer publisher.Close() - ref, err := publisher.Publish(context.Background(), empty.Image, build.StrictScheme+importpath) - if test.shouldError { - if err == nil || !strings.HasSuffix(err.Error(), test.wantError.Error()) { - t.Errorf("%s: got error %v, wanted %v", test.description, err, test.wantError) + t.Run(test.description, func(t *testing.T) { + publisher, err := NewPublisher(test.po) + if err != nil { + t.Fatalf("NewPublisher(): %v", err) } - continue - } - if err != nil { - t.Fatalf("%s: publisher.Publish(): %v", test.description, err) - } - fmt.Printf("ref: %+v\n", ref) - gotImageName := ref.Context().Name() - if gotImageName != test.wantImageName { - t.Errorf("%s: got %s, wanted %s", test.description, gotImageName, test.wantImageName) - } + defer publisher.Close() + ref, err := publisher.Publish(context.Background(), empty.Image, build.StrictScheme+importpath) + if test.shouldError { + if err == nil || !strings.HasSuffix(err.Error(), test.wantError.Error()) { + t.Errorf("%s: got error %v, wanted %v", test.description, err, test.wantError) + } + return + } + if err != nil { + t.Fatalf("publisher.Publish(): %v", err) + } + gotImageName := ref.Context().Name() + if gotImageName != test.wantImageName { + t.Errorf("got %s, wanted %s", gotImageName, test.wantImageName) + } + }) + } +} + +// registryServerWithImage starts a local registry and pushes a random image. +// Use this to speed up tests, by not having to reach out to gcr.io for the default base image. +// The registry uses a NOP logger to avoid spamming test logs. +// Remember to call `defer Close()` on the returned `httptest.Server`. +func registryServerWithImage(namespace string) (*httptest.Server, error) { + nopLog := log.New(ioutil.Discard, "", 0) + r := registry.New(registry.Logger(nopLog)) + s := httptest.NewServer(r) + imageName := fmt.Sprintf("%s/%s", s.Listener.Addr().String(), namespace) + image, err := random.Image(1024, 1) + if err != nil { + return nil, fmt.Errorf("random.Image(): %v", err) } + crane.Push(image, imageName) + return s, nil } func mustRepository(s string) name.Repository {