Skip to content

Commit

Permalink
Implement annotation-based rebase hints
Browse files Browse the repository at this point in the history
Adds crane.Rebase helper function for higher-level functionality
including annotation-based hints

`crane rebase` now prints the full pushed image reference, instead of
just the digest, and adds annotations to aid future rebasings.
  • Loading branch information
imjasonh committed Jun 22, 2021
1 parent acad0ed commit a3db8db
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
.project
bazel*
.idea
*.iml
*.iml
62 changes: 37 additions & 25 deletions cmd/crane/cmd/rebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ package cmd

import (
"fmt"
"log"

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

Expand All @@ -29,48 +30,59 @@ func NewCmdRebase(options *[]crane.Option) *cobra.Command {
rebaseCmd := &cobra.Command{
Use: "rebase",
Short: "Rebase an image onto a new base image",
Args: cobra.NoArgs,
RunE: func(*cobra.Command, []string) error {
origImg, err := crane.Pull(orig, *options...)
if err != nil {
return fmt.Errorf("pulling %s: %v", orig, err)
Args: cobra.MaximumNArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
if orig == "" {
orig = args[0]
} else if len(args) != 0 || args[0] != "" {
return fmt.Errorf("cannot use --original with positional argument")
}

oldBaseImg, err := crane.Pull(oldBase, *options...)
rebasedImg, err := crane.Rebase(orig, oldBase, newBase)
if err != nil {
return fmt.Errorf("pulling %s: %v", oldBase, err)
return fmt.Errorf("rebasing image: %v", err)
}

newBaseImg, err := crane.Pull(newBase, *options...)
// 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
// rebased image by digest instead.
if rebased == "" {
log.Println("pushing rebased image as", orig)
rebased = orig
}
digest, err := rebasedImg.Digest()
if err != nil {
return fmt.Errorf("pulling %s: %v", newBase, err)
return fmt.Errorf("digesting new image: %v", err)
}

img, err := mutate.Rebase(origImg, oldBaseImg, newBaseImg)
r, err := name.ParseReference(rebased)
if err != nil {
return fmt.Errorf("rebasing: %v", err)
}

if err := crane.Push(img, rebased, *options...); err != nil {
if err := crane.Push(rebasedImg, rebased, *options...); err != nil {
return fmt.Errorf("pushing %s: %v", rebased, err)
}
if _, ok := r.(name.Digest); ok {
rebased = r.Context().Digest(digest.String()).String()
}

digest, err := img.Digest()
if err := crane.Push(rebasedImg, rebased, *options...); err != nil {
return fmt.Errorf("pushing %s: %v", rebased, err)
}

rebasedRef, err := name.ParseReference(rebased)
if err != nil {
return fmt.Errorf("digesting rebased: %v", err)
return fmt.Errorf("parsing %q: %v", rebased, err)
}
fmt.Println(digest.String())

fmt.Println(rebasedRef.Context().Digest(digest.String()))
return nil
},
}
rebaseCmd.Flags().StringVarP(&orig, "original", "", "", "Original image to rebase")
rebaseCmd.Flags().StringVarP(&oldBase, "old_base", "", "", "Old base image to remove")
rebaseCmd.Flags().StringVarP(&newBase, "new_base", "", "", "New base image to insert")
rebaseCmd.Flags().StringVarP(&rebased, "rebased", "", "", "Tag to apply to rebased image")

rebaseCmd.MarkFlagRequired("original")
rebaseCmd.MarkFlagRequired("old_base")
rebaseCmd.MarkFlagRequired("new_base")
rebaseCmd.MarkFlagRequired("rebased")
rebaseCmd.Flags().StringVar(&orig, "original", "", "Original image to rebase; use positional arg instead")
rebaseCmd.Flags().StringVar(&oldBase, "old_base", "", "Old base image to remove")
rebaseCmd.Flags().StringVar(&newBase, "new_base", "", "New base image to insert")
rebaseCmd.Flags().StringVarP(&rebased, "tag", "t", "", "Tag to apply to rebased image")
return rebaseCmd
}
4 changes: 2 additions & 2 deletions cmd/crane/doc/crane_rebase.md

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

110 changes: 110 additions & 0 deletions pkg/crane/rebase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// 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 crane

import (
"fmt"
"log"

"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
)

const (
baseDigestAnnotation = "org.opencontainers.image.base.digest"
baseRefAnnotation = "org.opencontainers.image.base.ref.name"
)

// Rebase parses the references and uses them to perform a rebase.
//
// If oldBase or newBase are "", Rebase attempts to derive them using
// annotations in the original image. If those annotations are not found,
// Rebase returns an error.
//
// If rebasing is successful, base image annotations are set on the resulting
// image to facilitate implicit rebasing next time.
func Rebase(orig, oldBase, newBase string, opt ...Option) (v1.Image, error) {
o := makeOptions(opt...)
origRef, err := name.ParseReference(orig, o.name...)
if err != nil {
return nil, fmt.Errorf("parsing tag %q: %v", origRef, err)
}
origImg, err := remote.Image(origRef, o.remote...)
if err != nil {
return nil, err
}

m, err := origImg.Manifest()
if err != nil {
return nil, err
}
if newBase == "" && m.Annotations != nil {
newBase = m.Annotations[baseRefAnnotation]
if newBase != "" {
log.Printf("Detected new base from %q annotation: %s", baseRefAnnotation, newBase)
}
}
if newBase == "" {
return nil, fmt.Errorf("either newBase or %q annotation is required", baseRefAnnotation)
}
newBaseRef, err := name.ParseReference(newBase, o.name...)
if err != nil {
return nil, err
}
if oldBase == "" && m.Annotations != nil {
oldBase = m.Annotations[baseDigestAnnotation]
if oldBase != "" {
oldBase = newBaseRef.Context().Digest(oldBase).String()
log.Printf("Detected old base from %q annotation: %s", baseDigestAnnotation, oldBase)
}
}
if oldBase == "" {
return nil, fmt.Errorf("either oldBase or %q annotation is required", baseDigestAnnotation)
}

oldBaseRef, err := name.ParseReference(oldBase, o.name...)
if err != nil {
return nil, err
}
oldBaseImg, err := remote.Image(oldBaseRef, o.remote...)
if err != nil {
return nil, err
}
newBaseImg, err := remote.Image(newBaseRef, o.remote...)
if err != nil {
return nil, err
}

rebased, err := mutate.Rebase(origImg, oldBaseImg, newBaseImg)
if err != nil {
return nil, err
}

// Update base image annotations for the new image manifest.
d, err := newBaseImg.Digest()
if err != nil {
return nil, err
}
newDigest := d.String()
newTag := newBaseRef.String()
log.Printf("Setting annotation %q: %q", baseDigestAnnotation, newDigest)
log.Printf("Setting annotation %q: %q", baseRefAnnotation, newTag)
return mutate.Annotations(rebased, map[string]string{
baseDigestAnnotation: newDigest,
baseRefAnnotation: newTag,
}), nil
}
1 change: 1 addition & 0 deletions pkg/v1/mutate/rebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ func Rebase(orig, oldBase, newBase v1.Image) (v1.Image, error) {
return nil, fmt.Errorf("failed to get digest of layer %d of %q: %v", i, orig, err)
}
if oldLayerDigest != origLayerDigest {
// TODO: this is a bad error message...
return nil, fmt.Errorf("image %q is not based on %q (layer %d mismatch)", orig, oldBase, i)
}
}
Expand Down

0 comments on commit a3db8db

Please sign in to comment.