33
33
ErrEmptyArtifactName = errors .New ("artifact name cannot be empty" )
34
34
)
35
35
36
+ const ManifestSchemaVersion = 2
37
+
36
38
type ArtifactStore struct {
37
39
SystemContext * types.SystemContext
38
40
storePath string
@@ -164,23 +166,70 @@ func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimag
164
166
// Add takes one or more local files and adds them to the local artifact store. The empty
165
167
// string input is for possible custom artifact types.
166
168
func (as ArtifactStore ) Add (ctx context.Context , dest string , paths []string , options * libartTypes.AddOptions ) (* digest.Digest , error ) {
167
- annots := maps .Clone (options .Annotations )
168
169
if len (dest ) == 0 {
169
170
return nil , ErrEmptyArtifactName
170
171
}
171
172
172
- artifactManifestLayers := make ([]specV1.Descriptor , 0 )
173
+ if options .Append && len (options .ArtifactType ) > 0 {
174
+ return nil , errors .New ("append option is not compatible with ArtifactType option" )
175
+ }
176
+
177
+ // currently we don't allow override of the filename ; if a user requirement emerges,
178
+ // we could seemingly accommodate but broadens possibilities of something bad happening
179
+ // for things like `artifact extract`
180
+ if _ , hasTitle := options .Annotations [specV1 .AnnotationTitle ]; hasTitle {
181
+ return nil , fmt .Errorf ("cannot override filename with %s annotation" , specV1 .AnnotationTitle )
182
+ }
173
183
174
184
// Check if artifact already exists
175
185
artifacts , err := as .getArtifacts (ctx , nil )
176
186
if err != nil {
177
187
return nil , err
178
188
}
179
189
180
- // Check if artifact exists; in GetByName not getting an
181
- // error means it exists
182
- if _ , _ , err := artifacts .GetByNameOrDigest (dest ); err == nil {
183
- return nil , fmt .Errorf ("artifact %s already exists" , dest )
190
+ var artifactManifest specV1.Manifest
191
+ var oldDigest * digest.Digest
192
+ fileNames := map [string ]struct {}{}
193
+
194
+ if ! options .Append {
195
+ // Check if artifact exists; in GetByName not getting an
196
+ // error means it exists
197
+ _ , _ , err := artifacts .GetByNameOrDigest (dest )
198
+ if err == nil {
199
+ return nil , fmt .Errorf ("artifact %s already exists" , dest )
200
+ }
201
+ artifactManifest = specV1.Manifest {
202
+ Versioned : specs.Versioned {SchemaVersion : ManifestSchemaVersion },
203
+ MediaType : specV1 .MediaTypeImageManifest ,
204
+ ArtifactType : options .ArtifactType ,
205
+ // TODO This should probably be configurable once the CLI is capable
206
+ Config : specV1 .DescriptorEmptyJSON ,
207
+ Layers : make ([]specV1.Descriptor , 0 ),
208
+ }
209
+ } else {
210
+ artifact , _ , err := artifacts .GetByNameOrDigest (dest )
211
+ if err != nil {
212
+ return nil , err
213
+ }
214
+ artifactManifest = artifact .Manifest .Manifest
215
+ oldDigest , err = artifact .GetDigest ()
216
+ if err != nil {
217
+ return nil , err
218
+ }
219
+
220
+ for _ , layer := range artifactManifest .Layers {
221
+ if value , ok := layer .Annotations [specV1 .AnnotationTitle ]; ok && value != "" {
222
+ fileNames [value ] = struct {}{}
223
+ }
224
+ }
225
+ }
226
+
227
+ for _ , path := range paths {
228
+ fileName := filepath .Base (path )
229
+ if _ , ok := fileNames [fileName ]; ok {
230
+ return nil , fmt .Errorf ("file: %q already exists in artifact" , fileName )
231
+ }
232
+ fileNames [fileName ] = struct {}{}
184
233
}
185
234
186
235
ir , err := layout .NewReference (as .storePath , dest )
@@ -194,14 +243,9 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
194
243
}
195
244
defer imageDest .Close ()
196
245
246
+ // ImageDestination, in general, requires the caller to write a full image; here we may write only the added layers.
247
+ // This works for the oci/layout transport we hard-code.
197
248
for _ , path := range paths {
198
- // currently we don't allow override of the filename ; if a user requirement emerges,
199
- // we could seemingly accommodate but broadens possibilities of something bad happening
200
- // for things like `artifact extract`
201
- if _ , hasTitle := options .Annotations [specV1 .AnnotationTitle ]; hasTitle {
202
- return nil , fmt .Errorf ("cannot override filename with %s annotation" , specV1 .AnnotationTitle )
203
- }
204
-
205
249
// get the new artifact into the local store
206
250
newBlobDigest , newBlobSize , err := layout .PutBlobFromLocalFile (ctx , imageDest , path )
207
251
if err != nil {
@@ -212,35 +256,25 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
212
256
return nil , err
213
257
}
214
258
215
- annots [ specV1 . AnnotationTitle ] = filepath . Base ( path )
216
-
259
+ annotations := maps . Clone ( options . Annotations )
260
+ annotations [ specV1 . AnnotationTitle ] = filepath . Base ( path )
217
261
newLayer := specV1.Descriptor {
218
262
MediaType : detectedType ,
219
263
Digest : newBlobDigest ,
220
264
Size : newBlobSize ,
221
- Annotations : annots ,
265
+ Annotations : annotations ,
222
266
}
223
-
224
- artifactManifestLayers = append (artifactManifestLayers , newLayer )
225
- }
226
-
227
- artifactManifest := specV1.Manifest {
228
- Versioned : specs.Versioned {SchemaVersion : 2 },
229
- MediaType : specV1 .MediaTypeImageManifest ,
230
- // TODO This should probably be configurable once the CLI is capable
231
- Config : specV1 .DescriptorEmptyJSON ,
232
- Layers : artifactManifestLayers ,
267
+ artifactManifest .Layers = append (artifactManifest .Layers , newLayer )
233
268
}
234
269
235
- artifactManifest .ArtifactType = options .ArtifactType
236
-
237
270
rawData , err := json .Marshal (artifactManifest )
238
271
if err != nil {
239
272
return nil , err
240
273
}
241
274
if err := imageDest .PutManifest (ctx , rawData , nil ); err != nil {
242
275
return nil , err
243
276
}
277
+
244
278
unparsed := newUnparsedArtifactImage (ir , artifactManifest )
245
279
if err := imageDest .Commit (ctx , unparsed ); err != nil {
246
280
return nil , err
@@ -253,6 +287,27 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
253
287
if err := createEmptyStanza (filepath .Join (as .storePath , specV1 .ImageBlobsDir , artifactManifestDigest .Algorithm ().String (), artifactManifest .Config .Digest .Encoded ())); err != nil {
254
288
logrus .Errorf ("failed to check or write empty stanza file: %v" , err )
255
289
}
290
+
291
+ // Clean up after append. Remove previous artifact from store.
292
+ if oldDigest != nil {
293
+ lrs , err := layout .List (as .storePath )
294
+ if err != nil {
295
+ return nil , err
296
+ }
297
+
298
+ for _ , l := range lrs {
299
+ if oldDigest .String () == l .ManifestDescriptor .Digest .String () {
300
+ if _ , ok := l .ManifestDescriptor .Annotations [specV1 .AnnotationRefName ]; ok {
301
+ continue
302
+ }
303
+
304
+ if err := l .Reference .DeleteImage (ctx , as .SystemContext ); err != nil {
305
+ return nil , err
306
+ }
307
+ break
308
+ }
309
+ }
310
+ }
256
311
return & artifactManifestDigest , nil
257
312
}
258
313
@@ -431,7 +486,7 @@ func (as ArtifactStore) readIndex() (*specV1.Index, error) { //nolint:unused
431
486
func (as ArtifactStore ) createEmptyManifest () error {
432
487
index := specV1.Index {
433
488
MediaType : specV1 .MediaTypeImageIndex ,
434
- Versioned : specs.Versioned {SchemaVersion : 2 },
489
+ Versioned : specs.Versioned {SchemaVersion : ManifestSchemaVersion },
435
490
}
436
491
rawData , err := json .Marshal (& index )
437
492
if err != nil {
0 commit comments