diff --git a/doc/ko_apply.md b/doc/ko_apply.md index 55c7cac738..7b1eda77fe 100644 --- a/doc/ko_apply.md +++ b/doc/ko_apply.md @@ -59,6 +59,7 @@ ko apply -f FILENAME [flags] -f, --filename strings Filename, directory, or URL to files to use to create the resource -h, --help help for apply --image-label strings Which labels (key=value) to add to the image. + --image-refs string Path to file where a list of the published image references will be written. --insecure-registry Whether to skip TLS verification on the registry --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure (DEPRECATED) -j, --jobs int The maximum number of concurrent builds (default GOMAXPROCS) diff --git a/doc/ko_build.md b/doc/ko_build.md index 1adc36c5a7..2ee7404ac7 100644 --- a/doc/ko_build.md +++ b/doc/ko_build.md @@ -48,6 +48,7 @@ ko build IMPORTPATH... [flags] --disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container. -h, --help help for build --image-label strings Which labels (key=value) to add to the image. + --image-refs string Path to file where a list of the published image references will be written. --insecure-registry Whether to skip TLS verification on the registry -j, --jobs int The maximum number of concurrent builds (default GOMAXPROCS) -L, --local Load into images to local docker daemon. diff --git a/doc/ko_create.md b/doc/ko_create.md index bace61694d..18a6504157 100644 --- a/doc/ko_create.md +++ b/doc/ko_create.md @@ -59,6 +59,7 @@ ko create -f FILENAME [flags] -f, --filename strings Filename, directory, or URL to files to use to create the resource -h, --help help for create --image-label strings Which labels (key=value) to add to the image. + --image-refs string Path to file where a list of the published image references will be written. --insecure-registry Whether to skip TLS verification on the registry --insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure (DEPRECATED) -j, --jobs int The maximum number of concurrent builds (default GOMAXPROCS) diff --git a/doc/ko_resolve.md b/doc/ko_resolve.md index 7ec68c95a3..2e23d9ac02 100644 --- a/doc/ko_resolve.md +++ b/doc/ko_resolve.md @@ -44,6 +44,7 @@ ko resolve -f FILENAME [flags] -f, --filename strings Filename, directory, or URL to files to use to create the resource -h, --help help for resolve --image-label strings Which labels (key=value) to add to the image. + --image-refs string Path to file where a list of the published image references will be written. --insecure-registry Whether to skip TLS verification on the registry -j, --jobs int The maximum number of concurrent builds (default GOMAXPROCS) -L, --local Load into images to local docker daemon. diff --git a/doc/ko_run.md b/doc/ko_run.md index d09e19857a..a7cce21bb4 100644 --- a/doc/ko_run.md +++ b/doc/ko_run.md @@ -35,6 +35,7 @@ ko run IMPORTPATH [flags] --disable-optimizations Disable optimizations when building Go code. Useful when you want to interactively debug the created container. -h, --help help for run --image-label strings Which labels (key=value) to add to the image. + --image-refs string Path to file where a list of the published image references will be written. --insecure-registry Whether to skip TLS verification on the registry -j, --jobs int The maximum number of concurrent builds (default GOMAXPROCS) -L, --local Load into images to local docker daemon. diff --git a/pkg/commands/options/publish.go b/pkg/commands/options/publish.go index e6551ef3d8..31be7e2f0e 100644 --- a/pkg/commands/options/publish.go +++ b/pkg/commands/options/publish.go @@ -59,6 +59,8 @@ type PublishOptions struct { OCILayoutPath string TarballFile string + ImageRefsFile string + // PreserveImportPaths preserves the full import path after KO_DOCKER_REPO. PreserveImportPaths bool // BaseImportPaths uses the base path without MD5 hash after KO_DOCKER_REPO. @@ -93,6 +95,9 @@ func AddPublishArg(cmd *cobra.Command, po *PublishOptions) { cmd.Flags().StringVar(&po.OCILayoutPath, "oci-layout-path", "", "Path to save the OCI image layout of the built images") cmd.Flags().StringVar(&po.TarballFile, "tarball", "", "File to save images tarballs") + cmd.Flags().StringVar(&po.ImageRefsFile, "image-refs", "", + "Path to file where a list of the published image references will be written.") + cmd.Flags().BoolVarP(&po.PreserveImportPaths, "preserve-import-paths", "P", po.PreserveImportPaths, "Whether to preserve the full import path after KO_DOCKER_REPO.") cmd.Flags().BoolVarP(&po.BaseImportPaths, "base-import-paths", "B", po.BaseImportPaths, diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index bca6f9d0bc..bfec4dd98d 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -215,7 +215,8 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) { publish.WithNamer(namer), publish.WithTags(po.Tags), publish.WithTagOnly(po.TagOnly), - publish.Insecure(po.InsecureRegistry)) + publish.Insecure(po.InsecureRegistry), + ) if err != nil { return nil, err } @@ -237,6 +238,17 @@ func makePublisher(po *options.PublishOptions) (publish.Interface, error) { return nil, err } + if po.ImageRefsFile != "" { + f, err := os.OpenFile(po.ImageRefsFile, os.O_RDWR|os.O_CREATE, 0600) + if err != nil { + return nil, err + } + innerPublisher, err = publish.NewRecorder(innerPublisher, f) + if err != nil { + return nil, err + } + } + // Wrap publisher in a memoizing publisher implementation. return publish.NewCaching(innerPublisher) } diff --git a/pkg/publish/recorder.go b/pkg/publish/recorder.go new file mode 100644 index 0000000000..ed6e4fced7 --- /dev/null +++ b/pkg/publish/recorder.go @@ -0,0 +1,65 @@ +// Copyright 2021 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package publish + +import ( + "context" + "io" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/ko/pkg/build" +) + +// recorder wraps a publisher implementation in a layer that recordes the published +// references to an io.Writer. +type recorder struct { + inner Interface + wc io.Writer +} + +// recorder implements Interface +var _ Interface = (*recorder)(nil) + +// NewRecorder wraps the provided publish.Interface in an implementation that +// records publish results to an io.Writer. +func NewRecorder(inner Interface, wc io.Writer) (Interface, error) { + return &recorder{ + inner: inner, + wc: wc, + }, nil +} + +// Publish implements Interface +func (r *recorder) Publish(ctx context.Context, br build.Result, ref string) (name.Reference, error) { + result, err := r.inner.Publish(ctx, br, ref) + if err != nil { + return nil, err + } + if _, err := r.wc.Write([]byte(result.String() + "\n")); err != nil { + return nil, err + } + return result, nil +} + +// Close implements Interface +func (r *recorder) Close() error { + if err := r.inner.Close(); err != nil { + return err + } + if c, ok := r.wc.(io.Closer); ok { + return c.Close() + } + return nil +} diff --git a/pkg/publish/recorder_test.go b/pkg/publish/recorder_test.go new file mode 100644 index 0000000000..ac7920f497 --- /dev/null +++ b/pkg/publish/recorder_test.go @@ -0,0 +1,72 @@ +// Copyright 2021 Google LLC All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package publish + +import ( + "bytes" + "context" + "fmt" + "testing" + + "github.com/google/go-containerregistry/pkg/name" + "github.com/google/ko/pkg/build" +) + +type cbPublish struct { + cb func(context.Context, build.Result, string) (name.Reference, error) +} + +var _ Interface = (*cbPublish)(nil) + +func (sp *cbPublish) Publish(ctx context.Context, br build.Result, ref string) (name.Reference, error) { + return sp.cb(ctx, br, ref) +} + +func (sp *cbPublish) Close() error { + return nil +} + +func TestRecorder(t *testing.T) { + num := 0 + inner := &cbPublish{cb: func(c context.Context, b build.Result, s string) (name.Reference, error) { + num++ + return name.ParseReference(fmt.Sprintf("ubuntu:%d", num)) + }} + + buf := bytes.NewBuffer(nil) + + recorder, err := NewRecorder(inner, buf) + if err != nil { + t.Fatalf("NewRecorder() = %v", err) + } + + if _, err := recorder.Publish(context.Background(), nil, ""); err != nil { + t.Errorf("recorder.Publish() = %v", err) + } + if _, err := recorder.Publish(context.Background(), nil, ""); err != nil { + t.Errorf("recorder.Publish() = %v", err) + } + if _, err := recorder.Publish(context.Background(), nil, ""); err != nil { + t.Errorf("recorder.Publish() = %v", err) + } + if err := recorder.Close(); err != nil { + t.Errorf("recorder.Close() = %v", err) + } + + want, got := "ubuntu:1\nubuntu:2\nubuntu:3\n", buf.String() + if got != want { + t.Errorf("buf.String() = %s, wanted %s", got, want) + } +}