Skip to content

Commit

Permalink
Set build config via BuildOptions (ko-build#421)
Browse files Browse the repository at this point in the history
* Set build config via BuildOptions

Enables programmatically overriding build configs when ko is
embedded in another tool.

Related: ko-build#340, ko-build#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
  • Loading branch information
halvards authored Aug 26, 2021
1 parent 335c1ac commit ae594dc
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 35 deletions.
4 changes: 4 additions & 0 deletions pkg/commands/options/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ limitations under the License.
package options

import (
"github.com/google/ko/pkg/build"
"github.com/spf13/cobra"
)

Expand All @@ -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) {
Expand Down
9 changes: 8 additions & 1 deletion pkg/commands/publisher_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 4 additions & 1 deletion pkg/commands/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}

Expand Down
137 changes: 104 additions & 33 deletions pkg/commands/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,18 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http/httptest"
"path"
"strings"
"testing"

"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"
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit ae594dc

Please sign in to comment.