mirror of
https://github.com/containers/podman.git
synced 2025-08-24 18:12:25 +08:00
Merge pull request #25179 from Honny1/artifact-add-append
Create `--append` flag to add file to existing artifact using `podman artifact add` command
This commit is contained in:
@ -27,6 +27,7 @@ var (
|
||||
type artifactAddOptions struct {
|
||||
ArtifactType string
|
||||
Annotations []string
|
||||
Append bool
|
||||
}
|
||||
|
||||
var (
|
||||
@ -41,12 +42,15 @@ func init() {
|
||||
flags := addCmd.Flags()
|
||||
|
||||
annotationFlagName := "annotation"
|
||||
flags.StringArrayVar(&addOpts.Annotations, annotationFlagName, nil, "set an `annotation` for the specified artifact")
|
||||
flags.StringArrayVar(&addOpts.Annotations, annotationFlagName, nil, "set an `annotation` for the specified files of artifact")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(annotationFlagName, completion.AutocompleteNone)
|
||||
|
||||
addTypeFlagName := "type"
|
||||
flags.StringVar(&addOpts.ArtifactType, addTypeFlagName, "", "Use type to describe an artifact")
|
||||
_ = addCmd.RegisterFlagCompletionFunc(addTypeFlagName, completion.AutocompleteNone)
|
||||
|
||||
appendFlagName := "append"
|
||||
flags.BoolVarP(&addOpts.Append, appendFlagName, "a", false, "Append files to an existing artifact")
|
||||
}
|
||||
|
||||
func add(cmd *cobra.Command, args []string) error {
|
||||
@ -58,6 +62,8 @@ func add(cmd *cobra.Command, args []string) error {
|
||||
}
|
||||
opts.Annotations = annots
|
||||
opts.ArtifactType = addOpts.ArtifactType
|
||||
opts.Append = addOpts.Append
|
||||
|
||||
report, err := registry.ImageEngine().ArtifactAdd(registry.Context(), args[0], args[1:], opts)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -21,6 +21,12 @@ added.
|
||||
|
||||
@@option annotation.manifest
|
||||
|
||||
Note: Set annotations for each file being added.
|
||||
|
||||
#### **--append**, **-a**
|
||||
|
||||
Append files to an existing artifact. This option cannot be used with the **--type** option.
|
||||
|
||||
#### **--help**
|
||||
|
||||
Print usage statement.
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
type ArtifactAddOptions struct {
|
||||
Annotations map[string]string
|
||||
ArtifactType string
|
||||
Append bool
|
||||
}
|
||||
|
||||
type ArtifactExtractOptions struct {
|
||||
|
@ -162,6 +162,7 @@ func (ir *ImageEngine) ArtifactAdd(ctx context.Context, name string, paths []str
|
||||
addOptions := types.AddOptions{
|
||||
Annotations: opts.Annotations,
|
||||
ArtifactType: opts.ArtifactType,
|
||||
Append: opts.Append,
|
||||
}
|
||||
|
||||
artifactDigest, err := artStore.Add(ctx, name, paths, &addOptions)
|
||||
|
@ -33,6 +33,8 @@ var (
|
||||
ErrEmptyArtifactName = errors.New("artifact name cannot be empty")
|
||||
)
|
||||
|
||||
const ManifestSchemaVersion = 2
|
||||
|
||||
type ArtifactStore struct {
|
||||
SystemContext *types.SystemContext
|
||||
storePath string
|
||||
@ -164,12 +166,20 @@ func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimag
|
||||
// Add takes one or more local files and adds them to the local artifact store. The empty
|
||||
// string input is for possible custom artifact types.
|
||||
func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, options *libartTypes.AddOptions) (*digest.Digest, error) {
|
||||
annots := maps.Clone(options.Annotations)
|
||||
if len(dest) == 0 {
|
||||
return nil, ErrEmptyArtifactName
|
||||
}
|
||||
|
||||
artifactManifestLayers := make([]specV1.Descriptor, 0)
|
||||
if options.Append && len(options.ArtifactType) > 0 {
|
||||
return nil, errors.New("append option is not compatible with ArtifactType option")
|
||||
}
|
||||
|
||||
// currently we don't allow override of the filename ; if a user requirement emerges,
|
||||
// we could seemingly accommodate but broadens possibilities of something bad happening
|
||||
// for things like `artifact extract`
|
||||
if _, hasTitle := options.Annotations[specV1.AnnotationTitle]; hasTitle {
|
||||
return nil, fmt.Errorf("cannot override filename with %s annotation", specV1.AnnotationTitle)
|
||||
}
|
||||
|
||||
// Check if artifact already exists
|
||||
artifacts, err := as.getArtifacts(ctx, nil)
|
||||
@ -177,10 +187,49 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Check if artifact exists; in GetByName not getting an
|
||||
// error means it exists
|
||||
if _, _, err := artifacts.GetByNameOrDigest(dest); err == nil {
|
||||
return nil, fmt.Errorf("artifact %s already exists", dest)
|
||||
var artifactManifest specV1.Manifest
|
||||
var oldDigest *digest.Digest
|
||||
fileNames := map[string]struct{}{}
|
||||
|
||||
if !options.Append {
|
||||
// Check if artifact exists; in GetByName not getting an
|
||||
// error means it exists
|
||||
_, _, err := artifacts.GetByNameOrDigest(dest)
|
||||
if err == nil {
|
||||
return nil, fmt.Errorf("artifact %s already exists", dest)
|
||||
}
|
||||
artifactManifest = specV1.Manifest{
|
||||
Versioned: specs.Versioned{SchemaVersion: ManifestSchemaVersion},
|
||||
MediaType: specV1.MediaTypeImageManifest,
|
||||
ArtifactType: options.ArtifactType,
|
||||
// TODO This should probably be configurable once the CLI is capable
|
||||
Config: specV1.DescriptorEmptyJSON,
|
||||
Layers: make([]specV1.Descriptor, 0),
|
||||
}
|
||||
} else {
|
||||
artifact, _, err := artifacts.GetByNameOrDigest(dest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
artifactManifest = artifact.Manifest.Manifest
|
||||
oldDigest, err = artifact.GetDigest()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, layer := range artifactManifest.Layers {
|
||||
if value, ok := layer.Annotations[specV1.AnnotationTitle]; ok && value != "" {
|
||||
fileNames[value] = struct{}{}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
fileName := filepath.Base(path)
|
||||
if _, ok := fileNames[fileName]; ok {
|
||||
return nil, fmt.Errorf("file: %q already exists in artifact", fileName)
|
||||
}
|
||||
fileNames[fileName] = struct{}{}
|
||||
}
|
||||
|
||||
ir, err := layout.NewReference(as.storePath, dest)
|
||||
@ -194,14 +243,9 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
|
||||
}
|
||||
defer imageDest.Close()
|
||||
|
||||
// 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 {
|
||||
// currently we don't allow override of the filename ; if a user requirement emerges,
|
||||
// we could seemingly accommodate but broadens possibilities of something bad happening
|
||||
// for things like `artifact extract`
|
||||
if _, hasTitle := options.Annotations[specV1.AnnotationTitle]; hasTitle {
|
||||
return nil, fmt.Errorf("cannot override filename with %s annotation", specV1.AnnotationTitle)
|
||||
}
|
||||
|
||||
// get the new artifact into the local store
|
||||
newBlobDigest, newBlobSize, err := layout.PutBlobFromLocalFile(ctx, imageDest, path)
|
||||
if err != nil {
|
||||
@ -212,28 +256,17 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
|
||||
return nil, err
|
||||
}
|
||||
|
||||
annots[specV1.AnnotationTitle] = filepath.Base(path)
|
||||
|
||||
annotations := maps.Clone(options.Annotations)
|
||||
annotations[specV1.AnnotationTitle] = filepath.Base(path)
|
||||
newLayer := specV1.Descriptor{
|
||||
MediaType: detectedType,
|
||||
Digest: newBlobDigest,
|
||||
Size: newBlobSize,
|
||||
Annotations: annots,
|
||||
Annotations: annotations,
|
||||
}
|
||||
|
||||
artifactManifestLayers = append(artifactManifestLayers, newLayer)
|
||||
artifactManifest.Layers = append(artifactManifest.Layers, newLayer)
|
||||
}
|
||||
|
||||
artifactManifest := specV1.Manifest{
|
||||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
MediaType: specV1.MediaTypeImageManifest,
|
||||
// TODO This should probably be configurable once the CLI is capable
|
||||
Config: specV1.DescriptorEmptyJSON,
|
||||
Layers: artifactManifestLayers,
|
||||
}
|
||||
|
||||
artifactManifest.ArtifactType = options.ArtifactType
|
||||
|
||||
rawData, err := json.Marshal(artifactManifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -241,6 +274,7 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
|
||||
if err := imageDest.PutManifest(ctx, rawData, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
unparsed := newUnparsedArtifactImage(ir, artifactManifest)
|
||||
if err := imageDest.Commit(ctx, unparsed); err != nil {
|
||||
return nil, err
|
||||
@ -253,6 +287,27 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, paths []string, op
|
||||
if err := createEmptyStanza(filepath.Join(as.storePath, specV1.ImageBlobsDir, artifactManifestDigest.Algorithm().String(), artifactManifest.Config.Digest.Encoded())); err != nil {
|
||||
logrus.Errorf("failed to check or write empty stanza file: %v", err)
|
||||
}
|
||||
|
||||
// Clean up after append. Remove previous artifact from store.
|
||||
if oldDigest != nil {
|
||||
lrs, err := layout.List(as.storePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, l := range lrs {
|
||||
if oldDigest.String() == l.ManifestDescriptor.Digest.String() {
|
||||
if _, ok := l.ManifestDescriptor.Annotations[specV1.AnnotationRefName]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := l.Reference.DeleteImage(ctx, as.SystemContext); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return &artifactManifestDigest, nil
|
||||
}
|
||||
|
||||
@ -431,7 +486,7 @@ func (as ArtifactStore) readIndex() (*specV1.Index, error) { //nolint:unused
|
||||
func (as ArtifactStore) createEmptyManifest() error {
|
||||
index := specV1.Index{
|
||||
MediaType: specV1.MediaTypeImageIndex,
|
||||
Versioned: specs.Versioned{SchemaVersion: 2},
|
||||
Versioned: specs.Versioned{SchemaVersion: ManifestSchemaVersion},
|
||||
}
|
||||
rawData, err := json.Marshal(&index)
|
||||
if err != nil {
|
||||
|
@ -8,6 +8,8 @@ type GetArtifactOptions struct{}
|
||||
type AddOptions struct {
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
ArtifactType string `json:",omitempty"`
|
||||
// append option is not compatible with ArtifactType option
|
||||
Append bool `json:",omitempty"`
|
||||
}
|
||||
|
||||
type ExtractOptions struct {
|
||||
|
@ -3,17 +3,14 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/libartifact"
|
||||
. "github.com/containers/podman/v5/test/utils"
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
. "github.com/onsi/gomega/gexec"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -36,19 +33,19 @@ var _ = Describe("Podman artifact", func() {
|
||||
artifact1File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
add1 := podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File}...)
|
||||
add1 := podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
artifact2File, err := createArtifactFile(10240)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
artifact2Name := "localhost/test/artifact2"
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact2Name, artifact2File}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact2Name, artifact2File)
|
||||
|
||||
// Should be three items in the list
|
||||
listSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls"}...)
|
||||
listSession := podmanTest.PodmanExitCleanly("artifact", "ls")
|
||||
Expect(listSession.OutputToStringArray()).To(HaveLen(3))
|
||||
|
||||
// --format should work
|
||||
listFormatSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls", "--format", "{{.Repository}}"}...)
|
||||
listFormatSession := podmanTest.PodmanExitCleanly("artifact", "ls", "--format", "{{.Repository}}")
|
||||
output := listFormatSession.OutputToStringArray()
|
||||
|
||||
// There should be only 2 "lines" because the header should not be output
|
||||
@ -59,18 +56,18 @@ var _ = Describe("Podman artifact", func() {
|
||||
Expect(output).To(ContainElement(artifact2Name))
|
||||
|
||||
// Check default digest length (should be 12)
|
||||
defaultFormatSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls", "--format", "{{.Digest}}"}...)
|
||||
defaultFormatSession := podmanTest.PodmanExitCleanly("artifact", "ls", "--format", "{{.Digest}}")
|
||||
defaultOutput := defaultFormatSession.OutputToStringArray()[0]
|
||||
Expect(defaultOutput).To(HaveLen(12))
|
||||
|
||||
// Check with --no-trunc and verify the len of the digest is the same as the len what was returned when the artifact
|
||||
// was added
|
||||
noTruncSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls", "--no-trunc", "--format", "{{.Digest}}"}...)
|
||||
noTruncSession := podmanTest.PodmanExitCleanly("artifact", "ls", "--no-trunc", "--format", "{{.Digest}}")
|
||||
truncOutput := noTruncSession.OutputToStringArray()[0]
|
||||
Expect(truncOutput).To(HaveLen(len(add1.OutputToString())))
|
||||
|
||||
// check with --noheading and verify the header is not present through a line count AND substring match
|
||||
noHeaderSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls", "--noheading"}...)
|
||||
noHeaderSession := podmanTest.PodmanExitCleanly("artifact", "ls", "--noheading")
|
||||
noHeaderOutput := noHeaderSession.OutputToStringArray()
|
||||
Expect(noHeaderOutput).To(HaveLen(2))
|
||||
Expect(noHeaderOutput).ToNot(ContainElement("REPOSITORY"))
|
||||
@ -82,21 +79,16 @@ var _ = Describe("Podman artifact", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
inspectSingleSession := podmanTest.PodmanExitCleanly([]string{"artifact", "inspect", artifact1Name}...)
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
|
||||
a := libartifact.Artifact{}
|
||||
inspectOut := inspectSingleSession.OutputToString()
|
||||
err = json.Unmarshal([]byte(inspectOut), &a)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
|
||||
// Adding an artifact with an existing name should fail
|
||||
addAgain := podmanTest.Podman([]string{"artifact", "add", artifact1Name, artifact1File})
|
||||
addAgain.WaitWithDefaultTimeout()
|
||||
Expect(addAgain).ShouldNot(ExitCleanly())
|
||||
Expect(addAgain.ErrorToString()).To(Equal(fmt.Sprintf("Error: artifact %s already exists", artifact1Name)))
|
||||
Expect(addAgain).Should(ExitWithError(125, fmt.Sprintf("Error: artifact %s already exists", artifact1Name)))
|
||||
})
|
||||
|
||||
It("podman artifact add with options", func() {
|
||||
@ -108,11 +100,9 @@ var _ = Describe("Podman artifact", func() {
|
||||
annotation1 := "color=blue"
|
||||
annotation2 := "flavor=lemon"
|
||||
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "add", "--type", artifactType, "--annotation", annotation1, "--annotation", annotation2, artifact1Name, artifact1File}...)
|
||||
inspectSingleSession := podmanTest.PodmanExitCleanly([]string{"artifact", "inspect", artifact1Name}...)
|
||||
a := libartifact.Artifact{}
|
||||
err = json.Unmarshal([]byte(inspectSingleSession.OutputToString()), &a)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", "--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"))
|
||||
@ -120,8 +110,7 @@ var _ = Describe("Podman artifact", func() {
|
||||
|
||||
failSession := podmanTest.Podman([]string{"artifact", "add", "--annotation", "org.opencontainers.image.title=foobar", "foobar", artifact1File})
|
||||
failSession.WaitWithDefaultTimeout()
|
||||
Expect(failSession).Should(Exit(125))
|
||||
Expect(failSession.ErrorToString()).Should(Equal("Error: cannot override filename with org.opencontainers.image.title annotation"))
|
||||
Expect(failSession).Should(ExitWithError(125, "Error: cannot override filename with org.opencontainers.image.title annotation"))
|
||||
})
|
||||
|
||||
It("podman artifact add multiple", func() {
|
||||
@ -132,14 +121,9 @@ var _ = Describe("Podman artifact", func() {
|
||||
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File1, artifact1File2}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File1, artifact1File2)
|
||||
|
||||
inspectSingleSession := podmanTest.PodmanExitCleanly([]string{"artifact", "inspect", artifact1Name}...)
|
||||
|
||||
a := libartifact.Artifact{}
|
||||
inspectOut := inspectSingleSession.OutputToString()
|
||||
err = json.Unmarshal([]byte(inspectOut), &a)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
|
||||
Expect(a.Manifest.Layers).To(HaveLen(2))
|
||||
@ -156,20 +140,16 @@ var _ = Describe("Podman artifact", func() {
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact1Name := fmt.Sprintf("localhost:%s/test/artifact1", port)
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "push", "-q", "--tls-verify=false", artifact1Name}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "push", "-q", "--tls-verify=false", artifact1Name)
|
||||
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "rm", artifact1Name}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "rm", artifact1Name)
|
||||
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "pull", "--tls-verify=false", artifact1Name}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "pull", "--tls-verify=false", artifact1Name)
|
||||
|
||||
inspectSingleSession := podmanTest.PodmanExitCleanly([]string{"artifact", "inspect", artifact1Name}...)
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
|
||||
a := libartifact.Artifact{}
|
||||
inspectOut := inspectSingleSession.OutputToString()
|
||||
err = json.Unmarshal([]byte(inspectOut), &a)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
})
|
||||
|
||||
@ -177,37 +157,35 @@ var _ = Describe("Podman artifact", func() {
|
||||
// Trying to remove an image that does not exist should fail
|
||||
rmFail := podmanTest.Podman([]string{"artifact", "rm", "foobar"})
|
||||
rmFail.WaitWithDefaultTimeout()
|
||||
Expect(rmFail).Should(Exit(125))
|
||||
Expect(rmFail.ErrorToString()).Should(Equal(fmt.Sprintf("Error: no artifact found with name or digest of %s", "foobar")))
|
||||
Expect(rmFail).Should(ExitWithError(125, fmt.Sprintf("Error: no artifact found with name or digest of %s", "foobar")))
|
||||
|
||||
// Add an artifact to remove later
|
||||
artifact1File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
addArtifact1 := podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File}...)
|
||||
addArtifact1 := podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
// Removing that artifact should work
|
||||
rmWorks := podmanTest.PodmanExitCleanly([]string{"artifact", "rm", artifact1Name}...)
|
||||
rmWorks := podmanTest.PodmanExitCleanly("artifact", "rm", artifact1Name)
|
||||
// The digests printed by removal should be the same as the digest that was added
|
||||
Expect(addArtifact1.OutputToString()).To(Equal(rmWorks.OutputToString()))
|
||||
|
||||
// Inspecting that the removed artifact should fail
|
||||
inspectArtifact := podmanTest.Podman([]string{"artifact", "inspect", artifact1Name})
|
||||
inspectArtifact.WaitWithDefaultTimeout()
|
||||
Expect(inspectArtifact).Should(Exit(125))
|
||||
Expect(inspectArtifact.ErrorToString()).To(Equal(fmt.Sprintf("Error: no artifact found with name or digest of %s", artifact1Name)))
|
||||
Expect(inspectArtifact).Should(ExitWithError(125, fmt.Sprintf("Error: no artifact found with name or digest of %s", artifact1Name)))
|
||||
})
|
||||
|
||||
It("podman artifact inspect with full or partial digest", func() {
|
||||
artifact1File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
addArtifact1 := podmanTest.PodmanExitCleanly([]string{"artifact", "add", artifact1Name, artifact1File}...)
|
||||
addArtifact1 := podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
artifactDigest := addArtifact1.OutputToString()
|
||||
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "inspect", artifactDigest}...)
|
||||
podmanTest.PodmanExitCleanly([]string{"artifact", "inspect", artifactDigest[:12]}...)
|
||||
podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest)
|
||||
podmanTest.PodmanExitCleanly("artifact", "inspect", artifactDigest[:12])
|
||||
|
||||
})
|
||||
|
||||
@ -368,6 +346,162 @@ var _ = Describe("Podman artifact", func() {
|
||||
podmanTest.PodmanExitCleanly("artifact", "extract", "--digest", artifactDigest, ARTIFACT_EVIL, podmanTest.TempDir)
|
||||
Expect(readFileToString(filepath.Join(podmanTest.TempDir, digestToFilename(artifactDigest)))).To(Equal(artifactContent))
|
||||
})
|
||||
|
||||
It("podman artifact simple add --append", func() {
|
||||
artifact1File, err := createArtifactFile(1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact2File, err := createArtifactFile(2048)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact3File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
layersNames := map[string]int{
|
||||
filepath.Base(artifact1File): 0,
|
||||
filepath.Base(artifact2File): 0,
|
||||
filepath.Base(artifact3File): 0,
|
||||
}
|
||||
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
_ = podmanTest.InspectArtifact(artifact1Name)
|
||||
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", "--append", artifact1Name, artifact2File)
|
||||
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
|
||||
Expect(a.Manifest.Layers).To(HaveLen(2))
|
||||
|
||||
annotation1 := "color=blue"
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", "--append", "--annotation", annotation1, artifact1Name, artifact3File)
|
||||
|
||||
a = podmanTest.InspectArtifact(artifact1Name)
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
Expect(a.Manifest.Layers).To(HaveLen(3))
|
||||
|
||||
for _, l := range a.Manifest.Layers {
|
||||
layersNames[l.Annotations["org.opencontainers.image.title"]] += 1
|
||||
|
||||
if l.Annotations["org.opencontainers.image.title"] == filepath.Base(artifact3File) {
|
||||
Expect(l.Annotations["color"]).To(Equal("blue"))
|
||||
} else {
|
||||
Expect(l.Annotations).To(HaveLen(1))
|
||||
}
|
||||
}
|
||||
for _, count := range layersNames {
|
||||
Expect(count).To(Equal(1))
|
||||
}
|
||||
|
||||
listSession := podmanTest.PodmanExitCleanly([]string{"artifact", "ls"}...)
|
||||
Expect(listSession.OutputToStringArray()).To(HaveLen(2))
|
||||
})
|
||||
|
||||
It("podman artifact add --append multiple", func() {
|
||||
artifact1File, err := createArtifactFile(1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact2File, err := createArtifactFile(2048)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact3File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", "--append", artifact1Name, artifact2File, artifact3File)
|
||||
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
|
||||
Expect(a.Manifest.Layers).To(HaveLen(3))
|
||||
})
|
||||
|
||||
It("podman artifact add --append modified file", func() {
|
||||
artifact1File, err := createArtifactFile(1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
f, err := os.OpenFile(artifact1File, os.O_APPEND|os.O_WRONLY, 0644)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
_, err = f.WriteString("This is modification.")
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
f.Close()
|
||||
|
||||
appendFail := podmanTest.Podman([]string{"artifact", "add", "--append", artifact1Name, artifact1File})
|
||||
appendFail.WaitWithDefaultTimeout()
|
||||
Expect(appendFail).Should(ExitWithError(125, fmt.Sprintf("Error: file: \"%s\" already exists in artifact", filepath.Base(artifact1File))))
|
||||
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
|
||||
Expect(a.Manifest.Layers).To(HaveLen(1))
|
||||
Expect(a.TotalSizeBytes()).To(Equal(int64(524288)))
|
||||
})
|
||||
|
||||
It("podman artifact add file already exists in artifact", func() {
|
||||
artifact1File, err := createArtifactFile(2048)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
|
||||
addFail := podmanTest.Podman([]string{"artifact", "add", artifact1Name, artifact1File, artifact1File})
|
||||
addFail.WaitWithDefaultTimeout()
|
||||
Expect(addFail).Should(ExitWithError(125, fmt.Sprintf("Error: file: \"%s\" already exists in artifact", filepath.Base(artifact1File))))
|
||||
|
||||
inspectFail := podmanTest.Podman([]string{"artifact", "inspect", artifact1Name})
|
||||
inspectFail.WaitWithDefaultTimeout()
|
||||
Expect(inspectFail).Should(ExitWithError(125, fmt.Sprintf("Error: no artifact found with name or digest of %s", artifact1Name)))
|
||||
})
|
||||
|
||||
It("podman artifact add --append file already exists in artifact", func() {
|
||||
artifact1File, err := createArtifactFile(2048)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
|
||||
|
||||
appendFail := podmanTest.Podman([]string{"artifact", "add", "--append", artifact1Name, artifact1File})
|
||||
appendFail.WaitWithDefaultTimeout()
|
||||
Expect(appendFail).Should(ExitWithError(125, fmt.Sprintf("Error: file: \"%s\" already exists in artifact", filepath.Base(artifact1File))))
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
|
||||
Expect(a.Manifest.Layers).To(HaveLen(1))
|
||||
Expect(a.TotalSizeBytes()).To(Equal(int64(1048576)))
|
||||
})
|
||||
|
||||
It("podman artifact add with --append and --type", func() {
|
||||
artifact1Name := "localhost/test/artifact1"
|
||||
artifact1File, err := createArtifactFile(1024)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact2File, err := createArtifactFile(2048)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifact3File, err := createArtifactFile(4192)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
artifactType := "octet/foobar"
|
||||
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", "--type", artifactType, artifact1Name, artifact1File)
|
||||
|
||||
a := podmanTest.InspectArtifact(artifact1Name)
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
Expect(a.Manifest.ArtifactType).To(Equal(artifactType))
|
||||
|
||||
podmanTest.PodmanExitCleanly("artifact", "add", "--append", artifact1Name, artifact2File)
|
||||
|
||||
a = podmanTest.InspectArtifact(artifact1Name)
|
||||
Expect(a.Name).To(Equal(artifact1Name))
|
||||
Expect(a.Manifest.ArtifactType).To(Equal(artifactType))
|
||||
Expect(a.Manifest.Layers).To(HaveLen(2))
|
||||
|
||||
failSession := podmanTest.Podman([]string{"artifact", "add", "--type", artifactType, "--append", artifact1Name, artifact3File})
|
||||
failSession.WaitWithDefaultTimeout()
|
||||
Expect(failSession).Should(ExitWithError(125, "Error: append option is not compatible with ArtifactType option"))
|
||||
})
|
||||
})
|
||||
|
||||
func digestToFilename(digest string) string {
|
||||
|
@ -5,6 +5,7 @@ package integration
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
@ -26,6 +27,7 @@ import (
|
||||
"github.com/containers/common/pkg/cgroups"
|
||||
"github.com/containers/podman/v5/libpod/define"
|
||||
"github.com/containers/podman/v5/pkg/inspect"
|
||||
"github.com/containers/podman/v5/pkg/libartifact"
|
||||
. "github.com/containers/podman/v5/test/utils"
|
||||
"github.com/containers/podman/v5/utils"
|
||||
"github.com/containers/storage/pkg/ioutils"
|
||||
@ -492,6 +494,15 @@ func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData {
|
||||
return i
|
||||
}
|
||||
|
||||
// InspectArtifactToJSON takes the session output of an artifact inspect and returns json
|
||||
func (s *PodmanSessionIntegration) InspectArtifactToJSON() libartifact.Artifact {
|
||||
a := libartifact.Artifact{}
|
||||
inspectOut := s.OutputToString()
|
||||
err := json.Unmarshal([]byte(inspectOut), &a)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
return a
|
||||
}
|
||||
|
||||
// PodmanExitCleanly runs a podman command with args, and expects it to ExitCleanly within the default timeout.
|
||||
// It returns the session (to allow consuming output if desired).
|
||||
func (p *PodmanTestIntegration) PodmanExitCleanly(args ...string) *PodmanSessionIntegration {
|
||||
@ -518,6 +529,15 @@ func (p *PodmanTestIntegration) InspectContainer(name string) []define.InspectCo
|
||||
return session.InspectContainerToJSON()
|
||||
}
|
||||
|
||||
// InspectArtifact returns an artifact's inspect data in JSON format
|
||||
func (p *PodmanTestIntegration) InspectArtifact(name string) libartifact.Artifact {
|
||||
cmd := []string{"artifact", "inspect", name}
|
||||
session := p.Podman(cmd)
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session).Should(Exit(0))
|
||||
return session.InspectArtifactToJSON()
|
||||
}
|
||||
|
||||
// Pull a single field from a container using `podman inspect --format {{ field }}`,
|
||||
// and verify it against the given expected value.
|
||||
func (p *PodmanTestIntegration) CheckContainerSingleField(name, field, expected string) {
|
||||
|
Reference in New Issue
Block a user