From c2452551b17b8629784b76ab82945ecfa79974ad Mon Sep 17 00:00:00 2001 From: Matt Moore Date: Sun, 19 Dec 2021 09:54:29 -0800 Subject: [PATCH] Add support for `--image-refs` This change adds a new `--image-refs=FILE` flag that can be used to direct `ko` to write a file containing a `\n` delimited list of published references. In the common case, this will contain the list of digest references, but if flags directing the use of tags are present this will reflect the style of reference requested. --- doc/ko_apply.md | 1 + doc/ko_build.md | 1 + doc/ko_create.md | 1 + doc/ko_resolve.md | 1 + doc/ko_run.md | 1 + pkg/commands/options/publish.go | 5 +++ pkg/commands/resolver.go | 14 ++++++- pkg/publish/recorder.go | 65 +++++++++++++++++++++++++++++ pkg/publish/recorder_test.go | 72 +++++++++++++++++++++++++++++++++ 9 files changed, 160 insertions(+), 1 deletion(-) create mode 100644 pkg/publish/recorder.go create mode 100644 pkg/publish/recorder_test.go 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..1eaea54418 --- /dev/null +++ b/pkg/publish/recorder.go @@ -0,0 +1,65 @@ +// Copyright 2018 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..4cd7481be6 --- /dev/null +++ b/pkg/publish/recorder_test.go @@ -0,0 +1,72 @@ +// Copyright 2018 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 = (*slowpublish)(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) + } +}