Skip to content

Commit af209f5

Browse files
Merge pull request #25179 from Honny1/artifact-add-append
Create `--append` flag to add file to existing artifact using `podman artifact add` command
2 parents 62cde17 + fdd442c commit af209f5

File tree

8 files changed

+305
-80
lines changed

8 files changed

+305
-80
lines changed

cmd/podman/artifact/add.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ var (
2727
type artifactAddOptions struct {
2828
ArtifactType string
2929
Annotations []string
30+
Append bool
3031
}
3132

3233
var (
@@ -41,12 +42,15 @@ func init() {
4142
flags := addCmd.Flags()
4243

4344
annotationFlagName := "annotation"
44-
flags.StringArrayVar(&addOpts.Annotations, annotationFlagName, nil, "set an `annotation` for the specified artifact")
45+
flags.StringArrayVar(&addOpts.Annotations, annotationFlagName, nil, "set an `annotation` for the specified files of artifact")
4546
_ = addCmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone)
4647

4748
addTypeFlagName := "type"
4849
flags.StringVar(&addOpts.ArtifactType, addTypeFlagName, "", "Use type to describe an artifact")
4950
_ = addCmd.RegisterFlagCompletionFunc(addTypeFlagName, completion.AutocompleteNone)
51+
52+
appendFlagName := "append"
53+
flags.BoolVarP(&addOpts.Append, appendFlagName, "a", false, "Append files to an existing artifact")
5054
}
5155

5256
func add(cmd *cobra.Command, args []string) error {
@@ -58,6 +62,8 @@ func add(cmd *cobra.Command, args []string) error {
5862
}
5963
opts.Annotations = annots
6064
opts.ArtifactType = addOpts.ArtifactType
65+
opts.Append = addOpts.Append
66+
6167
report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], opts)
6268
if err != nil {
6369
return err

docs/source/markdown/podman-artifact-add.1.md.in

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@ added.
2121

2222
@@option annotation.manifest
2323

24+
Note: Set annotations for each file being added.
25+
26+
#### **--append**, **-a**
27+
28+
Append files to an existing artifact. This option cannot be used with the **--type** option.
29+
2430
#### **--help**
2531

2632
Print usage statement.

pkg/domain/entities/artifact.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
type ArtifactAddOptions struct {
1313
Annotations map[string]string
1414
ArtifactType string
15+
Append bool
1516
}
1617

1718
type ArtifactExtractOptions struct {

pkg/domain/infra/abi/artifact.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,7 @@ func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []str
162162
addOptions := types.AddOptions{
163163
Annotations: opts.Annotations,
164164
ArtifactType: opts.ArtifactType,
165+
Append: opts.Append,
165166
}
166167

167168
artifactDigest, err := artStore.Add(ctx, name, paths, &addOptions)

pkg/libartifact/store/store.go

Lines changed: 84 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ var (
3333
ErrEmptyArtifactName = errors.New("artifact name cannot be empty")
3434
)
3535

36+
const ManifestSchemaVersion = 2
37+
3638
type ArtifactStore struct {
3739
SystemContext *types.SystemContext
3840
storePath string
@@ -164,23 +166,70 @@ func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimag
164166
// Add takes one or more local files and adds them to the local artifact store. The empty
165167
// string input is for possible custom artifact types.
166168
func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, options *libartTypes.AddOptions) (*digest.Digest, error) {
167-
annots := maps.Clone(options.Annotations)
168169
if len(dest) == 0 {
169170
return nil, ErrEmptyArtifactName
170171
}
171172

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+
}
173183

174184
// Check if artifact already exists
175185
artifacts, err := as.getArtifacts(ctx, nil)
176186
if err != nil {
177187
return nil, err
178188
}
179189

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{}{}
184233
}
185234

186235
ir, err := layout.NewReference(as.storePath, dest)
@@ -194,14 +243,9 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
194243
}
195244
defer imageDest.Close()
196245

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.
197248
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-
205249
// get the new artifact into the local store
206250
newBlobDigest, newBlobSize, err := layout.PutBlobFromLocalFile(ctx, imageDest, path)
207251
if err != nil {
@@ -212,35 +256,25 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
212256
return nil, err
213257
}
214258

215-
annots[specV1.AnnotationTitle] = filepath.Base(path)
216-
259+
annotations := maps.Clone(options.Annotations)
260+
annotations[specV1.AnnotationTitle] = filepath.Base(path)
217261
newLayer := specV1.Descriptor{
218262
MediaType: detectedType,
219263
Digest: newBlobDigest,
220264
Size: newBlobSize,
221-
Annotations: annots,
265+
Annotations: annotations,
222266
}
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)
233268
}
234269

235-
artifactManifest.ArtifactType = options.ArtifactType
236-
237270
rawData, err := json.Marshal(artifactManifest)
238271
if err != nil {
239272
return nil, err
240273
}
241274
if err := imageDest.PutManifest(ctx, rawData, nil); err != nil {
242275
return nil, err
243276
}
277+
244278
unparsed := newUnparsedArtifactImage(ir, artifactManifest)
245279
if err := imageDest.Commit(ctx, unparsed); err != nil {
246280
return nil, err
@@ -253,6 +287,27 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
253287
if err := createEmptyStanza(filepath.Join(as.storePath, specV1.ImageBlobsDir, artifactManifestDigest.Algorithm().String(), artifactManifest.Config.Digest.Encoded())); err != nil {
254288
logrus.Errorf("failed to check or write empty stanza file: %v", err)
255289
}
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+
}
256311
return &artifactManifestDigest, nil
257312
}
258313

@@ -431,7 +486,7 @@ func (as ArtifactStore) readIndex() (*specV1.Index, error) { //nolint:unused
431486
func (as ArtifactStore) createEmptyManifest() error {
432487
index := specV1.Index{
433488
MediaType: specV1.MediaTypeImageIndex,
434-
Versioned: specs.Versioned{SchemaVersion: 2},
489+
Versioned: specs.Versioned{SchemaVersion: ManifestSchemaVersion},
435490
}
436491
rawData, err := json.Marshal(&index)
437492
if err != nil {

pkg/libartifact/types/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ type GetArtifactOptions struct{}
88
type AddOptions struct {
99
Annotations map[string]string `json:"annotations,omitempty"`
1010
ArtifactType string `json:",omitempty"`
11+
// append option is not compatible with ArtifactType option
12+
Append bool `json:",omitempty"`
1113
}
1214

1315
type ExtractOptions struct {

0 commit comments

Comments
 (0)