@@ -17,6 +17,8 @@ package cmd
17
17
import (
18
18
"errors"
19
19
"fmt"
20
+ "github.com/google/go-containerregistry/pkg/v1/layout"
21
+ "github.com/google/go-containerregistry/pkg/v1/match"
20
22
"strings"
21
23
22
24
"github.com/google/go-containerregistry/pkg/crane"
@@ -37,33 +39,78 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
37
39
var newRef string
38
40
var newRepo string
39
41
var user string
42
+ var ociImageLayoutPath string
40
43
41
44
mutateCmd := & cobra.Command {
42
45
Use : "mutate" ,
43
46
Short : "Modify image labels and annotations. The container must be pushed to a registry, and the manifest is updated there." ,
44
- Args : cobra .ExactArgs (1 ),
45
47
RunE : func (_ * cobra.Command , args []string ) error {
48
+ var img v1.Image
49
+ var err error
46
50
// Pull image and get config.
47
- ref := args [0 ]
51
+ var ref string
52
+ if len (args ) > 0 {
53
+ ref = args [0 ]
54
+ }
55
+
56
+ if ref != "" && ociImageLayoutPath != "" {
57
+ return errors .New ("cannot specify both image and oci image layout" )
58
+ }
59
+
60
+ if ref != "" {
61
+ if len (annotations ) != 0 {
62
+ desc , err := crane .Head (ref , * options ... )
63
+ if err != nil {
64
+ return err
65
+ }
66
+ if desc .MediaType .IsIndex () {
67
+ return errors .New ("mutating annotations on an index is not yet supported" )
68
+ }
69
+ }
48
70
49
- if len (annotations ) != 0 {
50
- desc , err := crane .Head (ref , * options ... )
71
+ i , err := crane .Pull (ref , * options ... )
72
+ if err != nil {
73
+ return fmt .Errorf ("pulling %s: %w" , ref , err )
74
+ }
75
+ img = i
76
+ } else if ociImageLayoutPath != "" {
77
+ oil , err := layout .FromPath (ociImageLayoutPath )
51
78
if err != nil {
52
79
return err
53
80
}
54
- if desc .MediaType .IsIndex () {
55
- return errors .New ("mutating annotations on an index is not yet supported" )
81
+ ii , err := oil .ImageIndex ()
82
+ if err != nil {
83
+ return err
56
84
}
57
- }
58
85
59
- if newRepo != "" && newRef != "" {
60
- return errors .New ("repository can't be set when a tag is specified" )
61
- }
86
+ m , err := ii .IndexManifest ()
87
+ if err != nil {
88
+ return err
89
+ }
62
90
63
- img , err := crane .Pull (ref , * options ... )
64
- if err != nil {
65
- return fmt .Errorf ("pulling %s: %w" , ref , err )
91
+ desc := m .Manifests [0 ]
92
+
93
+ if len (annotations ) != 0 {
94
+ if err != nil {
95
+ return err
96
+ }
97
+ if desc .MediaType .IsIndex () {
98
+ return errors .New ("mutating annotations on an index is not yet supported" )
99
+ }
100
+ }
101
+
102
+ var i v1.Image
103
+ if desc .MediaType .IsImage () {
104
+ i , err = oil .Image (desc .Digest )
105
+ if err != nil {
106
+ return err
107
+ }
108
+ } else if desc .MediaType .IsIndex () {
109
+ return errors .New ("mutating layers on an index is not yet supported" )
110
+ }
111
+ img = i
66
112
}
113
+
67
114
if len (newLayers ) != 0 {
68
115
img , err = crane .Append (img , newLayers ... )
69
116
if err != nil {
@@ -122,35 +169,94 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
122
169
123
170
img = mutate .Annotations (img , annotations ).(v1.Image )
124
171
125
- // If the new ref isn't provided, write over the original image.
126
- // If that ref was provided by digest (e.g., output from
127
- // another crane command), then strip that and push the
128
- // mutated image by digest instead.
129
- if newRepo != "" {
130
- newRef = newRepo
131
- } else if newRef == "" {
132
- newRef = ref
133
- }
134
172
digest , err := img .Digest ()
135
173
if err != nil {
136
174
return fmt .Errorf ("digesting new image: %w" , err )
137
175
}
138
- if outFile != "" {
139
- if err := crane .Save (img , newRef , outFile ); err != nil {
140
- return fmt .Errorf ("writing output %q: %w" , outFile , err )
176
+ if ref != "" {
177
+ if newRepo != "" && newRef != "" {
178
+ return errors .New ("repository can't be set when a tag is specified" )
179
+ }
180
+
181
+ // If the new ref isn't provided, write over the original image.
182
+ // If that ref was provided by digest (e.g., output from
183
+ // another crane command), then strip that and push the
184
+ // mutated image by digest instead.
185
+ if newRepo != "" {
186
+ newRef = newRepo
187
+ } else if newRef == "" {
188
+ newRef = ref
189
+ }
190
+
191
+ if outFile != "" {
192
+ if err := crane .Save (img , newRef , outFile ); err != nil {
193
+ return fmt .Errorf ("writing output %q: %w" , outFile , err )
194
+ }
195
+ } else {
196
+ r , err := name .ParseReference (newRef )
197
+ if err != nil {
198
+ return fmt .Errorf ("parsing %s: %w" , newRef , err )
199
+ }
200
+ if _ , ok := r .(name.Digest ); ok || newRepo != "" {
201
+ newRef = r .Context ().Digest (digest .String ()).String ()
202
+ }
203
+ if err := crane .Push (img , newRef , * options ... ); err != nil {
204
+ return fmt .Errorf ("pushing %s: %w" , newRef , err )
205
+ }
206
+ fmt .Println (r .Context ().Digest (digest .String ()))
141
207
}
142
208
} else {
143
- r , err := name .ParseReference (newRef )
209
+ // TODO: this adds the new manifest to the index, but doesn't remove the old one
210
+ p , err := layout .FromPath (ociImageLayoutPath )
144
211
if err != nil {
145
- return fmt . Errorf ( "parsing %s: %w" , newRef , err )
212
+ return err
146
213
}
147
- if _ , ok := r .(name.Digest ); ok || newRepo != "" {
148
- newRef = r .Context ().Digest (digest .String ()).String ()
214
+
215
+ ii , err := p .ImageIndex ()
216
+ if err != nil {
217
+ return err
149
218
}
150
- if err := crane .Push (img , newRef , * options ... ); err != nil {
151
- return fmt .Errorf ("pushing %s: %w" , newRef , err )
219
+
220
+ m , err := ii .IndexManifest ()
221
+ if err != nil {
222
+ return err
223
+ }
224
+
225
+ desc := m .Manifests [0 ]
226
+
227
+ i , err := p .Image (desc .Digest )
228
+ if err != nil {
229
+ return err
230
+ }
231
+
232
+ im , err := i .Manifest ()
233
+ if err != nil {
234
+ return err
235
+ }
236
+
237
+ icd := im .Config .Digest
238
+
239
+ err = p .ReplaceImage (img , match .Digests (digest ))
240
+ if err != nil {
241
+ return err
242
+ }
243
+
244
+ fmt .Printf ("Removing old config layer: %s\n " , icd .String ())
245
+ err = p .RemoveBlob (icd )
246
+ if err != nil {
247
+ return err
152
248
}
153
- fmt .Println (r .Context ().Digest (digest .String ()))
249
+ fmt .Printf ("Removing old manifest from index: %s\n " , desc .Digest .String ())
250
+ err = p .RemoveDescriptors (match .Digests (desc .Digest ))
251
+ if err != nil {
252
+ return err
253
+ }
254
+ fmt .Printf ("Removing old manifest blob: %s\n " , desc .Digest .String ())
255
+ err = p .RemoveBlob (desc .Digest )
256
+ if err != nil {
257
+ return err
258
+ }
259
+
154
260
}
155
261
return nil
156
262
},
@@ -165,6 +271,7 @@ func NewCmdMutate(options *[]crane.Option) *cobra.Command {
165
271
mutateCmd .Flags ().StringVarP (& outFile , "output" , "o" , "" , "Path to new tarball of resulting image" )
166
272
mutateCmd .Flags ().StringSliceVar (& newLayers , "append" , []string {}, "Path to tarball to append to image" )
167
273
mutateCmd .Flags ().StringVarP (& user , "user" , "u" , "" , "New user to set" )
274
+ mutateCmd .Flags ().StringVarP (& ociImageLayoutPath , "oci-image-layout" , "" , "" , "A path to OCI Image Layout directory" )
168
275
return mutateCmd
169
276
}
170
277
0 commit comments