Skip to content

Commit

Permalink
Add crane mutate to set labels and entrypoint (#982)
Browse files Browse the repository at this point in the history
  • Loading branch information
imjasonh committed Apr 22, 2021
1 parent 7cfaa51 commit 5f2fda4
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 3 deletions.
10 changes: 10 additions & 0 deletions cmd/crane/cmd/append.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (

"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/logs"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -60,6 +61,15 @@ func NewCmdAppend(options *[]crane.Option) *cobra.Command {
if err := crane.Push(img, newTag, *options...); err != nil {
return fmt.Errorf("pushing image %s: %v", newTag, err)
}
ref, err := name.ParseReference(newTag)
if err != nil {
return fmt.Errorf("parsing reference %s: %v", newTag, err)
}
d, err := img.Digest()
if err != nil {
return fmt.Errorf("digest: %v", err)
}
fmt.Println(ref.Context().Digest(d.String()))
}
return nil
},
Expand Down
107 changes: 107 additions & 0 deletions cmd/crane/cmd/mutate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// 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"
"log"
"strings"

"github.com/google/go-containerregistry/pkg/crane"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/spf13/cobra"
)

// NewCmdMutate creates a new cobra.Command for the mutate subcommand.
func NewCmdMutate(options *[]crane.Option) *cobra.Command {
var lbls []string
var entrypoint string
var newRef string

mutateCmd := &cobra.Command{
Use: "mutate",
Short: "Modify image labels and annotations",
Args: cobra.ExactArgs(1),
Run: func(_ *cobra.Command, args []string) {
// Pull image and get config.
ref := args[0]
img, err := crane.Pull(ref, *options...)
if err != nil {
log.Fatalf("pulling %s: %v", ref, err)
}
cfg, err := img.ConfigFile()
if err != nil {
log.Fatalf("getting config: %v", err)
}
cfg = cfg.DeepCopy()

// Set labels.
if cfg.Config.Labels == nil {
cfg.Config.Labels = map[string]string{}
}
labels := map[string]string{}
for _, l := range lbls {
parts := strings.SplitN(l, "=", 2)
if len(parts) == 1 {
log.Fatalf("parsing label %q, not enough parts", l)
}
labels[parts[0]] = parts[1]
}
for k, v := range labels {
cfg.Config.Labels[k] = v
}

// Set entrypoint.
if entrypoint != "" {
// NB: This doesn't attempt to do anything smart about splitting the string into multiple entrypoint elements.
cfg.Config.Entrypoint = []string{entrypoint}
}

// Mutate and write image.
img, err = mutate.Config(img, cfg.Config)
if err != nil {
log.Fatalf("mutating config: %v", err)
}

// If the new ref isn't provided, write over the original image.
// If that ref was provided by digest (e.g., output from
// another crane command), then strip that and push the
// mutated image by digest instead.
if newRef == "" {
newRef = ref
}
digest, err := img.Digest()
if err != nil {
log.Fatalf("digesting new image: %v", err)
}
r, err := name.ParseReference(newRef)
if err != nil {
log.Fatalf("parsing %s: %v", newRef, err)
}
if _, ok := r.(name.Digest); ok {
newRef = r.Context().Digest(digest.String()).String()
}
if err := crane.Push(img, newRef, *options...); err != nil {
log.Fatalf("pushing %s: %v", newRef, err)
}
fmt.Println(r.Context().Digest(digest.String()))
},
}
mutateCmd.Flags().StringSliceVarP(&lbls, "label", "l", nil, "New labels to add")
mutateCmd.Flags().StringVar(&entrypoint, "entrypoint", "", "New entrypoing to set")
mutateCmd.Flags().StringVarP(&newRef, "tag", "t", "", "New tag to apply to mutated image. If not provided, push by digest to the original image repository.")
return mutateCmd
}
1 change: 1 addition & 0 deletions cmd/crane/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ func New(use, short string, options []crane.Option) *cobra.Command {
NewCmdTag(&options),
NewCmdValidate(&options),
NewCmdVersion(),
NewCmdMutate(&options),
}

root.AddCommand(commands...)
Expand Down
1 change: 1 addition & 0 deletions cmd/crane/doc/crane.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions cmd/crane/doc/crane_mutate.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions cmd/crane/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ crane append -f <(tar -f - -c some-dir/) -t ${IMAGE}

By default, this produces an image with one layer containing the directory contents. Add `-b ${BASE_IMAGE}` to append the layer to a base image instead.

You can extend this even further with `crane mutate`, to make an executable in the appended layer the image's entrypoint.

```
crane mutate ${IMAGE} --entrypoint=some-dir/entrypoint.sh
```

Because `crane append` emits the full image reference, these calls can even be chained together:

```
crane mutate $(
crane append -f <(tar -f - -c some-dir/) -t ${IMAGE}
) --entrypoint=some-dir/entrypoint.sh
```

This will bundle `some-dir/` into an image, push it, mutate its entrypoint to `some-dir/entrypoint.sh`, and push that new image by digest.

### Diff two configs

```
Expand Down
2 changes: 1 addition & 1 deletion pkg/crane/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func Pull(src string, opt ...Option) (v1.Image, error) {
o := makeOptions(opt...)
ref, err := name.ParseReference(src, o.name...)
if err != nil {
return nil, fmt.Errorf("parsing tag %q: %v", src, err)
return nil, fmt.Errorf("parsing reference %q: %v", src, err)
}

return remote.Image(ref, o.remote...)
Expand Down
4 changes: 2 additions & 2 deletions pkg/crane/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ func LoadTag(path, tag string, opt ...Option) (v1.Image, error) {
// Push pushes the v1.Image img to a registry as dst.
func Push(img v1.Image, dst string, opt ...Option) error {
o := makeOptions(opt...)
tag, err := name.NewTag(dst, o.name...)
tag, err := name.ParseReference(dst, o.name...)
if err != nil {
return fmt.Errorf("parsing tag %q: %v", dst, err)
return fmt.Errorf("parsing reference %q: %v", dst, err)
}
return remote.Write(tag, img, o.remote...)
}

0 comments on commit 5f2fda4

Please sign in to comment.