diff --git a/cmd/podman/artifact/add.go b/cmd/podman/artifact/add.go index 7b5e4214ed..899a35440d 100644 --- a/cmd/podman/artifact/add.go +++ b/cmd/podman/artifact/add.go @@ -19,8 +19,10 @@ var ( RunE: add, Args: cobra.MinimumNArgs(2), ValidArgsFunction: common.AutocompleteArtifactAdd, - Example: `podman artifact add quay.io/myimage/myartifact:latest /tmp/foobar.txt`, - Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, + Example: `podman artifact add quay.io/myimage/myartifact:latest /tmp/foobar.txt +podman artifact add --file-type text/yaml quay.io/myimage/myartifact:latest /tmp/foobar.yaml +podman artifact add --append quay.io/myimage/myartifact:latest /tmp/foobar.tar.gz`, + Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, } ) @@ -28,6 +30,7 @@ type artifactAddOptions struct { ArtifactType string Annotations []string Append bool + FileType string } var ( @@ -51,6 +54,10 @@ func init() { appendFlagName := "append" flags.BoolVarP(&addOpts.Append, appendFlagName, "a", false, "Append files to an existing artifact") + + fileTypeFlagName := "file-type" + flags.StringVarP(&addOpts.FileType, fileTypeFlagName, "", "", "Set file type to use for the artifact (layer)") + _ = addCmd.RegisterFlagCompletionFunc(fileTypeFlagName, completion.AutocompleteNone) } func add(cmd *cobra.Command, args []string) error { @@ -63,6 +70,7 @@ func add(cmd *cobra.Command, args []string) error { opts.Annotations = annots opts.ArtifactType = addOpts.ArtifactType opts.Append = addOpts.Append + opts.FileType = addOpts.FileType report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], opts) if err != nil { diff --git a/docs/source/markdown/podman-artifact-add.1.md.in b/docs/source/markdown/podman-artifact-add.1.md.in index 17231f6e7b..73004b1112 100644 --- a/docs/source/markdown/podman-artifact-add.1.md.in +++ b/docs/source/markdown/podman-artifact-add.1.md.in @@ -27,6 +27,10 @@ Note: Set annotations for each file being added. Append files to an existing artifact. This option cannot be used with the **--type** option. +#### **--file-type** + +Set the media type of the artifact file instead of allowing detection to determine the type + #### **--help** Print usage statement. @@ -55,6 +59,16 @@ Set an annotation for an artifact $ podman artifact add --annotation date=2025-01-30 quay.io/myartifact/myml:latest /tmp/foobar1.ml ``` +Append a file to an existing artifact +``` +$ podman artifact add --append quay.io/myartifact/tarballs:latest /tmp/foobar.tar.gz +``` + +Override the media type of the artifact being added +``` +$ podman artifact add --file-type text/yaml quay.io/myartifact/descriptors:latest /tmp/info.yaml +``` + ## SEE ALSO **[podman(1)](podman.1.md)**, **[podman-artifact(1)](podman-artifact.1.md)** diff --git a/pkg/domain/entities/artifact.go b/pkg/domain/entities/artifact.go index 5ce8acad74..0d699c8c6c 100644 --- a/pkg/domain/entities/artifact.go +++ b/pkg/domain/entities/artifact.go @@ -13,6 +13,7 @@ type ArtifactAddOptions struct { Annotations map[string]string ArtifactType string Append bool + FileType string } type ArtifactExtractOptions struct { diff --git a/pkg/domain/infra/abi/artifact.go b/pkg/domain/infra/abi/artifact.go index a867a1d2ff..fefa161f26 100644 --- a/pkg/domain/infra/abi/artifact.go +++ b/pkg/domain/infra/abi/artifact.go @@ -192,6 +192,7 @@ func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []str Annotations: opts.Annotations, ArtifactType: opts.ArtifactType, Append: opts.Append, + FileType: opts.FileType, } artifactDigest, err := artStore.Add(ctx, name, paths, &addOptions) diff --git a/pkg/libartifact/store/store.go b/pkg/libartifact/store/store.go index 46867c9d33..7e6061832b 100644 --- a/pkg/libartifact/store/store.go +++ b/pkg/libartifact/store/store.go @@ -251,20 +251,26 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op // ImageDestination, in general, requires the caller to write a full image; here we may write only the added layers. // This works for the oci/layout transport we hard-code. for _, path := range paths { + mediaType := options.FileType // get the new artifact into the local store newBlobDigest, newBlobSize, err := layout.PutBlobFromLocalFile(ctx, imageDest, path) if err != nil { return nil, err } - detectedType, err := determineManifestType(path) - if err != nil { - return nil, err + + // If we did not receive an override for the layer's mediatype, use + // detection to determine it. + if len(mediaType) < 1 { + mediaType, err = determineManifestType(path) + if err != nil { + return nil, err + } } annotations := maps.Clone(options.Annotations) annotations[specV1.AnnotationTitle] = filepath.Base(path) newLayer := specV1.Descriptor{ - MediaType: detectedType, + MediaType: mediaType, Digest: newBlobDigest, Size: newBlobSize, Annotations: annotations, diff --git a/pkg/libartifact/types/config.go b/pkg/libartifact/types/config.go index cff62a3709..7591ffdef9 100644 --- a/pkg/libartifact/types/config.go +++ b/pkg/libartifact/types/config.go @@ -10,6 +10,9 @@ type AddOptions struct { ArtifactType string `json:",omitempty"` // append option is not compatible with ArtifactType option Append bool `json:",omitempty"` + // FileType describes the media type for the layer. It is an override + // for the standard detection + FileType string `json:",omitempty"` } // FilterBlobOptions options used to filter for a single blob in an artifact diff --git a/test/e2e/artifact_test.go b/test/e2e/artifact_test.go index f14540ea50..a7150fce05 100644 --- a/test/e2e/artifact_test.go +++ b/test/e2e/artifact_test.go @@ -88,6 +88,7 @@ var _ = Describe("Podman artifact", func() { }) It("podman artifact add with options", func() { + yamlType := "text/yaml" artifact1Name := "localhost/test/artifact1" artifact1File, err := createArtifactFile(1024) Expect(err).ToNot(HaveOccurred()) @@ -96,13 +97,14 @@ var _ = Describe("Podman artifact", func() { annotation1 := "color=blue" annotation2 := "flavor=lemon" - podmanTest.PodmanExitCleanly("artifact", "add", "--type", artifactType, "--annotation", annotation1, "--annotation", annotation2, artifact1Name, artifact1File) + podmanTest.PodmanExitCleanly("artifact", "add", "--file-type", yamlType, "--type", artifactType, "--annotation", annotation1, "--annotation", annotation2, artifact1Name, artifact1File) a := podmanTest.InspectArtifact(artifact1Name) Expect(a.Name).To(Equal(artifact1Name)) Expect(a.Manifest.ArtifactType).To(Equal(artifactType)) Expect(a.Manifest.Layers[0].Annotations["color"]).To(Equal("blue")) Expect(a.Manifest.Layers[0].Annotations["flavor"]).To(Equal("lemon")) + Expect(a.Manifest.Layers[0].MediaType).To(Equal(yamlType)) failSession := podmanTest.Podman([]string{"artifact", "add", "--annotation", "org.opencontainers.image.title=foobar", "foobar", artifact1File}) failSession.WaitWithDefaultTimeout()