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