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 10, 2021
1 parent 71a6fe9 commit 7fcf227
Show file tree
Hide file tree
Showing 5 changed files with 153 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
36 changes: 13 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,29 @@ 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]
}

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

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 oldBase == "" && m.Annotations != nil {
oldBase = m.Annotations[baseDigestAnnotation]
if oldBase != "" {
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)
}
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)
}

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
}
newBaseRef, err := name.ParseReference(newBase, o.name...)
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 annotations for the new image manifest.
// - the current ("old") base image is the new base image by digest.
// - the future ("new") base image is the new base image by tag.
d, err := newBaseImg.Digest()
if err != nil {
return nil, err
}
if m.Annotations == nil {
m.Annotations = map[string]string{}
}
m.Annotations[baseDigestAnnotation] = newBaseRef.Context().Digest(d.String()).String()
m.Annotations[baseTagAnnotation] = newBaseRef.String()
log.Printf("Set annotation %q: %s", baseDigestAnnotation, m.Annotations[baseDigestAnnotation])
log.Printf("Set annotation %q: %s", baseTagAnnotation, m.Annotations[baseTagAnnotation])
return mutate.Manifest(rebased, m)
}
34 changes: 34 additions & 0 deletions pkg/v1/mutate/mutate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -644,3 +644,37 @@ func (m mockLayer) Compressed() (io.ReadCloser, error) {
func (m mockLayer) Uncompressed() (io.ReadCloser, error) {
return ioutil.NopCloser(strings.NewReader("uncompressed")), nil
}

func TestManifest(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)
}

newmf := old.DeepCopy()
newmf.Annotations = map[string]string{
"foo": "bar",
"hello": "world",
}
newimg, err := mutate.Manifest(img, newmf)
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)
}
}
14 changes: 14 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,16 @@ func createAddendums(startHistory, startLayer int, history []v1.History, layers

return adds
}

func Manifest(base v1.Image, m *v1.Manifest) (v1.Image, error) {
cfg, err := base.ConfigFile()
if err != nil {
return nil, err
}
return &image{
base: base,
manifest: m.DeepCopy(),
configFile: cfg.DeepCopy(),
computed: true,
}, nil
}

0 comments on commit 7fcf227

Please sign in to comment.