diff --git a/cmd/crane/cmd/annotate.go b/cmd/crane/cmd/annotate.go new file mode 100644 index 000000000..0121f6ad8 --- /dev/null +++ b/cmd/crane/cmd/annotate.go @@ -0,0 +1,83 @@ +// 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 cmd + +import ( + "fmt" + + "github.com/google/go-containerregistry/pkg/crane" + v1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/spf13/cobra" +) + +// NewCmdAnnotate creates a new cobra.Command for the annotate subcommand. +func NewCmdAnnotate(options *[]crane.Option) *cobra.Command { + var annotations map[string]string + var newRef string + + annotateCmd := &cobra.Command{ + Use: "annotate", + Short: "Modify image or index annotations. The manifest is updated there on the registry.", + Args: cobra.ExactArgs(1), + RunE: func(c *cobra.Command, args []string) error { + ref := args[0] + + desc, err := crane.Head(ref, *options...) + if err != nil { + return err + } + + if newRef == "" { + newRef = ref + } + + if desc.MediaType.IsIndex() { + d, err := crane.Get(ref, *options...) + if err != nil { + return err + } + idx, err := d.ImageIndex() + if err != nil { + return err + } + annotated := mutate.Annotations(idx, annotations).(v1.ImageIndex) + err = crane.PushIndex(annotated, newRef, *options...) + if err != nil { + return err + } + fmt.Println("Index pushed with annotations.") + } else if desc.MediaType.IsImage() { + img, err := crane.Pull(ref, *options...) + if err != nil { + return err + } + annotated := mutate.Annotations(img, annotations).(v1.Image) + err = crane.Push(annotated, newRef, *options...) + if err != nil { + return err + } + fmt.Println("Image pushed with annotations.") + } else { + return fmt.Errorf("unsupported manifest type only indexes and images are currently supported: %s", desc.ArtifactType) + } + + return nil + }, + } + annotateCmd.Flags().StringToStringVarP(&annotations, "annotation", "a", nil, "New annotations to add") + annotateCmd.Flags().StringVarP(&newRef, "tag", "t", "", "New tag reference to apply to annotated image/index. If not provided, push by digest to the original repository.") + return annotateCmd +} diff --git a/cmd/crane/cmd/root.go b/cmd/crane/cmd/root.go index abfd1dc99..48405d131 100644 --- a/cmd/crane/cmd/root.go +++ b/cmd/crane/cmd/root.go @@ -108,6 +108,7 @@ func New(use, short string, options []crane.Option) *cobra.Command { } root.AddCommand( + NewCmdAnnotate(&options), NewCmdAppend(&options), NewCmdAuth(options, "crane", "auth"), NewCmdBlob(&options), diff --git a/cmd/crane/doc/crane.md b/cmd/crane/doc/crane.md index afd1b2493..62c7fb7ba 100644 --- a/cmd/crane/doc/crane.md +++ b/cmd/crane/doc/crane.md @@ -18,6 +18,7 @@ crane [flags] ### SEE ALSO +* [crane annotate](crane_annotate.md) - Modify image or index annotations. The manifest is updated there on the registry. * [crane append](crane_append.md) - Append contents of a tarball to a remote image * [crane auth](crane_auth.md) - Log in or access credentials * [crane blob](crane_blob.md) - Read a blob from the registry diff --git a/cmd/crane/doc/crane_annotate.md b/cmd/crane/doc/crane_annotate.md new file mode 100644 index 000000000..7e6db426a --- /dev/null +++ b/cmd/crane/doc/crane_annotate.md @@ -0,0 +1,29 @@ +## crane annotate + +Modify image or index annotations. The manifest is updated there on the registry. + +``` +crane annotate [flags] +``` + +### Options + +``` + -a, --annotation stringToString New annotations to add (default []) + -h, --help help for annotate + -t, --tag string New tag reference to apply to annotated image/index. If not provided, push by digest to the original repository. +``` + +### Options inherited from parent commands + +``` + --allow-nondistributable-artifacts Allow pushing non-distributable (foreign) layers + --insecure Allow image references to be fetched without TLS + --platform platform Specifies the platform in the form os/arch[/variant][:osversion] (e.g. linux/amd64). (default all) + -v, --verbose Enable debug logs +``` + +### SEE ALSO + +* [crane](crane.md) - Crane is a tool for managing container images + diff --git a/pkg/crane/push.go b/pkg/crane/push.go index 90a058502..e4a53d863 100644 --- a/pkg/crane/push.go +++ b/pkg/crane/push.go @@ -53,6 +53,16 @@ func Push(img v1.Image, dst string, opt ...Option) error { return remote.Write(tag, img, o.Remote...) } +// PushIndex pushes the v1.ImageIndex idx to a registry as dst. +func PushIndex(idx v1.ImageIndex, dst string, opt ...Option) error { + o := makeOptions(opt...) + tag, err := name.ParseReference(dst, o.Name...) + if err != nil { + return fmt.Errorf("parsing reference %q: %w", dst, err) + } + return remote.Put(tag, idx, o.Remote...) +} + // Upload pushes the v1.Layer to a given repo. func Upload(layer v1.Layer, repo string, opt ...Option) error { o := makeOptions(opt...)