Add ability to set layer media type for artifacts

in #25884, it was pointed out that the standard detection used to
determine the artifact's file type can be wrong.  in those cases, it
would be handy for the user to be able to override the media type of the
layer.  as such, added a new option called `--file-type`, which is
optional, and allows users to do just that.

`podman artifact add --file-type text/yaml
quay.io/artifact/config:latest ./config.yaml `

Fixes: #25884

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2025-04-17 09:42:08 -05:00
parent 51c4df1316
commit fdfed9979f
7 changed files with 42 additions and 7 deletions

View File

@ -19,8 +19,10 @@ var (
RunE: add, RunE: add,
Args: cobra.MinimumNArgs(2), Args: cobra.MinimumNArgs(2),
ValidArgsFunction: common.AutocompleteArtifactAdd, ValidArgsFunction: common.AutocompleteArtifactAdd,
Example: `podman artifact add quay.io/myimage/myartifact:latest /tmp/foobar.txt`, Example: `podman artifact add quay.io/myimage/myartifact:latest /tmp/foobar.txt
Annotations: map[string]string{registry.EngineMode: registry.ABIMode}, 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 ArtifactType string
Annotations []string Annotations []string
Append bool Append bool
FileType string
} }
var ( var (
@ -51,6 +54,10 @@ func init() {
appendFlagName := "append" appendFlagName := "append"
flags.BoolVarP(&addOpts.Append, appendFlagName, "a", false, "Append files to an existing artifact") 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 { func add(cmd *cobra.Command, args []string) error {
@ -63,6 +70,7 @@ func add(cmd *cobra.Command, args []string) error {
opts.Annotations = annots opts.Annotations = annots
opts.ArtifactType = addOpts.ArtifactType opts.ArtifactType = addOpts.ArtifactType
opts.Append = addOpts.Append opts.Append = addOpts.Append
opts.FileType = addOpts.FileType
report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], opts) report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], opts)
if err != nil { if err != nil {

View File

@ -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. 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** #### **--help**
Print usage statement. 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 $ 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 ## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-artifact(1)](podman-artifact.1.md)** **[podman(1)](podman.1.md)**, **[podman-artifact(1)](podman-artifact.1.md)**

View File

@ -13,6 +13,7 @@ type ArtifactAddOptions struct {
Annotations map[string]string Annotations map[string]string
ArtifactType string ArtifactType string
Append bool Append bool
FileType string
} }
type ArtifactExtractOptions struct { type ArtifactExtractOptions struct {

View File

@ -192,6 +192,7 @@ func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []str
Annotations: opts.Annotations, Annotations: opts.Annotations,
ArtifactType: opts.ArtifactType, ArtifactType: opts.ArtifactType,
Append: opts.Append, Append: opts.Append,
FileType: opts.FileType,
} }
artifactDigest, err := artStore.Add(ctx, name, paths, &addOptions) artifactDigest, err := artStore.Add(ctx, name, paths, &addOptions)

View File

@ -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. // 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. // This works for the oci/layout transport we hard-code.
for _, path := range paths { for _, path := range paths {
mediaType := options.FileType
// get the new artifact into the local store // get the new artifact into the local store
newBlobDigest, newBlobSize, err := layout.PutBlobFromLocalFile(ctx, imageDest, path) newBlobDigest, newBlobSize, err := layout.PutBlobFromLocalFile(ctx, imageDest, path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
detectedType, err := determineManifestType(path)
if err != nil { // If we did not receive an override for the layer's mediatype, use
return nil, err // detection to determine it.
if len(mediaType) < 1 {
mediaType, err = determineManifestType(path)
if err != nil {
return nil, err
}
} }
annotations := maps.Clone(options.Annotations) annotations := maps.Clone(options.Annotations)
annotations[specV1.AnnotationTitle] = filepath.Base(path) annotations[specV1.AnnotationTitle] = filepath.Base(path)
newLayer := specV1.Descriptor{ newLayer := specV1.Descriptor{
MediaType: detectedType, MediaType: mediaType,
Digest: newBlobDigest, Digest: newBlobDigest,
Size: newBlobSize, Size: newBlobSize,
Annotations: annotations, Annotations: annotations,

View File

@ -10,6 +10,9 @@ type AddOptions struct {
ArtifactType string `json:",omitempty"` ArtifactType string `json:",omitempty"`
// append option is not compatible with ArtifactType option // append option is not compatible with ArtifactType option
Append bool `json:",omitempty"` 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 // FilterBlobOptions options used to filter for a single blob in an artifact

View File

@ -88,6 +88,7 @@ var _ = Describe("Podman artifact", func() {
}) })
It("podman artifact add with options", func() { It("podman artifact add with options", func() {
yamlType := "text/yaml"
artifact1Name := "localhost/test/artifact1" artifact1Name := "localhost/test/artifact1"
artifact1File, err := createArtifactFile(1024) artifact1File, err := createArtifactFile(1024)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -96,13 +97,14 @@ var _ = Describe("Podman artifact", func() {
annotation1 := "color=blue" annotation1 := "color=blue"
annotation2 := "flavor=lemon" 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) a := podmanTest.InspectArtifact(artifact1Name)
Expect(a.Name).To(Equal(artifact1Name)) Expect(a.Name).To(Equal(artifact1Name))
Expect(a.Manifest.ArtifactType).To(Equal(artifactType)) Expect(a.Manifest.ArtifactType).To(Equal(artifactType))
Expect(a.Manifest.Layers[0].Annotations["color"]).To(Equal("blue")) 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].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 := podmanTest.Podman([]string{"artifact", "add", "--annotation", "org.opencontainers.image.title=foobar", "foobar", artifact1File})
failSession.WaitWithDefaultTimeout() failSession.WaitWithDefaultTimeout()