Skip to content

Commit

Permalink
WIP: 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
  • Loading branch information
imjasonh committed Mar 11, 2021
1 parent 71a6fe9 commit 7b856c9
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 24 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
38 changes: 15 additions & 23 deletions cmd/crane/cmd/rebase.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"log"

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

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

oldBaseImg, err := crane.Pull(oldBase, *options...)
if err != nil {
log.Fatalf("pulling %s: %v", oldBase, err)
if orig == "" {
log.Fatal("--old_base or positional arg is required")
}

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

img, err := mutate.Rebase(origImg, oldBaseImg, newBaseImg)
if err != nil {
log.Fatalf("rebasing: %v", err)
if rebased == "" {
log.Println("pushing rebased image as", orig)
rebased = orig
}

if err := crane.Push(img, rebased, *options...); err != nil {
if err := crane.Push(rebasedImg, rebased, *options...); err != nil {
log.Fatalf("pushing %s: %v", rebased, err)
}

digest, err := img.Digest()
digest, err := rebasedImg.Digest()
if err != nil {
log.Fatalf("digesting rebased: %v", err)
}
Expand All @@ -67,10 +64,5 @@ func NewCmdRebase(options *[]crane.Option) *cobra.Command {
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")
return rebaseCmd
}
100 changes: 100 additions & 0 deletions pkg/crane/rebase.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
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 (
// TODO: better annotations
baseDigestAnnotation = "base-digest"
baseTagAnnotation = "base-tag"
)

// 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...)

m, err := origImg.Manifest()
if err != nil {
return nil, err
}
if newBase == "" && m.Annotations != nil {
newBase = m.Annotations[baseTagAnnotation]
if newBase != "" {
log.Printf("Detected new base from %q annotation: %s", baseTagAnnotation, newBase)
}
}
if newBase == "" {
return nil, fmt.Errorf("either newBase or %q annotation is required", baseTagAnnotation)
}
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
}
m, err = rebased.Manifest()
if err != nil {
return nil, err
}

// Update base image annotations for the new image manifest.
// - base-digest is the new base image digest.
// - base-tag is the new base image by tag.
d, err := newBaseImg.Digest()
if err != nil {
return nil, err
}
newDigest := d.String()
newTag := newBaseRef.String()
log.Printf("Set annotation %q: %s", baseDigestAnnotation, newDigest)
log.Printf("Set annotation %q: %s", baseTagAnnotation, newTag)
return mutate.Annotations(rebased, map[string]string{
baseDigestAnnotation: newDigest,
baseTagAnnotation: newTag,
})
}
32 changes: 32 additions & 0 deletions pkg/v1/mutate/mutate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,3 +644,35 @@ func (m mockLayer) Compressed() (io.ReadCloser, error) {
func (m mockLayer) Uncompressed() (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("uncompressed")), nil
}

func TestAnnotations(t *testing.T) {
img, err := random.Image(10, 10)
if err != nil {
t.Fatal(err)
}

old, err := img.Manifest()
if err != nil {
t.Fatal(err)
}

newimg, err := mutate.Annotations(img, map[string]string{
"foo": "bar",
"hello": "world",
})
if err != nil {
t.Fatal(err)
}

newmf, err := newimg.Manifest()
if err != nil {
t.Fatal(err)
}

if d := cmp.Diff(old, newmf); d == "" {
t.Fatal("no changes")
}
if err := validate.Image(newimg); err != nil {
t.Fatal(err)
}
}
28 changes: 28 additions & 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 Expand Up @@ -142,3 +143,30 @@ func createAddendums(startHistory, startLayer int, history []v1.History, layers

return adds
}

func Annotations(base v1.Image, anns map[string]string) (v1.Image, error) {
mf, err := base.Manifest()
if err != nil {
return nil, err
}
mf = mf.DeepCopy()

if mf.Annotations == nil {
mf.Annotations = map[string]string{}
}
for k, v := range anns {
mf.Annotations[k] = v
}

cfg, err := base.ConfigFile()
if err != nil {
return nil, err
}

return &image{
base: base,
configFile: cfg.DeepCopy(),
manifest: mf,
computed: true,
}, nil
}

0 comments on commit 7b856c9

Please sign in to comment.