Skip to content

Commit

Permalink
feat(mutate): added support for overriding manifest annotations (#1056)
Browse files Browse the repository at this point in the history
Signed-off-by: Batuhan Apaydın <batuhan.apaydin@trendyol.com>
Co-Authored-by: Jon Johnson <jonjohnson@google.com>

- improvements based on feedbacks
- check to make sure we're not trying to mutate annotations on an index
  • Loading branch information
developer-guy committed Jun 22, 2021
1 parent 5f53e4e commit 92e9e85
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 17 deletions.
49 changes: 42 additions & 7 deletions cmd/crane/cmd/mutate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
var lbls []string
var entrypoint string
var newRef string
var anntns []string

mutateCmd := &cobra.Command{
Use: "mutate",
Expand All @@ -38,6 +39,17 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
Run: func(_ *cobra.Command, args []string) {
// Pull image and get config.
ref := args[0]

if len(anntns) != 0 {
desc, err := crane.Head(ref, *options...)
if err != nil {
log.Fatalf("checking %s: %v", ref, err)
}
if desc.MediaType.IsIndex() {
log.Fatalf("mutating annotations on an index is not yet supported")
}
}

img, err := crane.Pull(ref, *options...)
if err != nil {
log.Fatalf("pulling %s: %v", ref, err)
Expand All @@ -52,18 +64,21 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
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]

labels, err := splitKeyVals(lbls)
if err != nil {
log.Fatal(err)
}

for k, v := range labels {
cfg.Config.Labels[k] = v
}

annotations, err := splitKeyVals(anntns)
if err != nil {
log.Fatal(err)
}

// Set entrypoint.
if entrypoint != "" {
// NB: This doesn't attempt to do anything smart about splitting the string into multiple entrypoint elements.
Expand All @@ -76,6 +91,12 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
log.Fatalf("mutating config: %v", err)
}

// Mutate and write image.
img, err = mutate.Annotations(img, annotations)
if err != nil {
log.Fatalf("mutating annotations: %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
Expand All @@ -100,8 +121,22 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
fmt.Println(r.Context().Digest(digest.String()))
},
}
mutateCmd.Flags().StringSliceVarP(&anntns, "annotation", "a", nil, "New annotations to add")
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
}

// splitKeyVals splits key value pairs which is in form hello=world
func splitKeyVals(kvPairs []string) (map[string]string, error) {
m := map[string]string{}
for _, l := range kvPairs {
parts := strings.SplitN(l, "=", 2)
if len(parts) == 1 {
return nil, fmt.Errorf("parsing label %q, not enough parts", l)
}
m[parts[0]] = parts[1]
}
return m, nil
}
9 changes: 5 additions & 4 deletions cmd/crane/doc/crane_mutate.md

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

23 changes: 17 additions & 6 deletions pkg/v1/mutate/image.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ type image struct {
base v1.Image
adds []Addendum

computed bool
configFile *v1.ConfigFile
manifest *v1.Manifest
mediaType *types.MediaType
diffIDMap map[v1.Hash]v1.Layer
digestMap map[v1.Hash]v1.Layer
computed bool
configFile *v1.ConfigFile
manifest *v1.Manifest
annotations map[string]string
mediaType *types.MediaType
diffIDMap map[v1.Hash]v1.Layer
digestMap map[v1.Hash]v1.Layer
}

var _ v1.Image = (*image)(nil)
Expand Down Expand Up @@ -139,6 +140,16 @@ func (i *image) compute() error {
}
}

if i.annotations != nil {
if manifest.Annotations == nil {
manifest.Annotations = map[string]string{}
}

for k, v := range i.annotations {
manifest.Annotations[k] = v
}
}

i.configFile = configFile
i.manifest = manifest
i.diffIDMap = diffIDMap
Expand Down
10 changes: 10 additions & 0 deletions pkg/v1/mutate/mutate.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ func Config(base v1.Image, cfg v1.Config) (v1.Image, error) {
return ConfigFile(base, cf)
}

// Annotations mutates the provided v1.Image to have the provided annotations
func Annotations(base v1.Image, annotations map[string]string) (v1.Image, error) {
image := &image{
base: base,
annotations: annotations,
}

return image, nil
}

// ConfigFile mutates the provided v1.Image to have the provided v1.ConfigFile
func ConfigFile(base v1.Image, cfg *v1.ConfigFile) (v1.Image, error) {
m, err := base.Manifest()
Expand Down
21 changes: 21 additions & 0 deletions pkg/v1/mutate/mutate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,27 @@ func TestMutateConfig(t *testing.T) {
}
}

func TestAnnotations(t *testing.T) {
source := sourceImage(t)

newAnnotations := map[string]string{
"im.the.first.annotation": "hello world",
}

result, err := mutate.Annotations(source, newAnnotations)
if err != nil {
t.Fatalf("failed to mutate annotations: %v", err)
}

if configDigestsAreEqual(t, source, result) {
t.Errorf("mutating the manifest annotations MUST mutate the config digest")
}

if err := validate.Image(result); err != nil {
t.Errorf("validate.Image() = %v", err)
}
}

func TestMutateCreatedAt(t *testing.T) {
source := sourceImage(t)
want := time.Now().Add(-2 * time.Minute)
Expand Down

0 comments on commit 92e9e85

Please sign in to comment.