From ada7c2b1c11ec89e85b878671f8a2d5aa61d121f Mon Sep 17 00:00:00 2001 From: Lewis Denny Date: Wed, 8 Oct 2025 22:39:36 +1000 Subject: [PATCH] feat(artifacts): Default to latest tag if not provided This change implements a feature where podman artifacts will default to using the 'latest' tag if no tag is provided by the user. This aligns the behavior of artifacts with that of container images, providing a more consistent user experience. Fixes: https://github.com/containers/podman/issues/27083 Requires: https://github.com/containers/container-libs/pull/409 Signed-off-by: Lewis Denny --- pkg/api/handlers/libpod/artifacts.go | 5 ++++ pkg/domain/infra/abi/artifact.go | 11 +++++++++ .../python/rest_api/test_v2_0_0_artifact.py | 10 +++++--- test/e2e/artifact_created_test.go | 2 +- test/e2e/artifact_test.go | 23 ++++++++----------- test/e2e/inspect_test.go | 2 +- 6 files changed, 34 insertions(+), 19 deletions(-) diff --git a/pkg/api/handlers/libpod/artifacts.go b/pkg/api/handlers/libpod/artifacts.go index 6622ba88209..2b9985b742e 100644 --- a/pkg/api/handlers/libpod/artifacts.go +++ b/pkg/api/handlers/libpod/artifacts.go @@ -346,6 +346,11 @@ func PushArtifact(w http.ResponseWriter, r *http.Request) { return } + if errors.Is(err, libartifact_types.ErrArtifactNotExist) { + utils.ArtifactNotFound(w, name, err) + return + } + utils.InternalServerError(w, err) return } diff --git a/pkg/domain/infra/abi/artifact.go b/pkg/domain/infra/abi/artifact.go index 0e1ccfefd22..7e71c9d9f35 100644 --- a/pkg/domain/infra/abi/artifact.go +++ b/pkg/domain/infra/abi/artifact.go @@ -15,6 +15,7 @@ import ( "github.com/sirupsen/logrus" "go.podman.io/common/libimage" "go.podman.io/common/pkg/libartifact/types" + "go.podman.io/image/v5/docker/reference" ) func (ir *ImageEngine) ArtifactInspect(ctx context.Context, name string, _ entities.ArtifactInspectOptions) (*entities.ArtifactInspectReport, error) { @@ -57,6 +58,16 @@ func (ir *ImageEngine) ArtifactList(ctx context.Context, _ entities.ArtifactList } func (ir *ImageEngine) ArtifactPull(ctx context.Context, name string, opts entities.ArtifactPullOptions) (*entities.ArtifactPullReport, error) { + named, err := reference.Parse(name) + if err != nil { + return nil, fmt.Errorf("parsing reference %q: %w", name, err) + } + namedRef, ok := named.(reference.Named) + if !ok { + return nil, fmt.Errorf("reference %q is not a Named reference", name) + } + name = reference.TagNameOnly(namedRef).String() + pullOptions := &libimage.CopyOptions{} pullOptions.AuthFilePath = opts.AuthFilePath pullOptions.CertDirPath = opts.CertDirPath diff --git a/test/apiv2/python/rest_api/test_v2_0_0_artifact.py b/test/apiv2/python/rest_api/test_v2_0_0_artifact.py index 919a578d7cf..2d125a66a0a 100644 --- a/test/apiv2/python/rest_api/test_v2_0_0_artifact.py +++ b/test/apiv2/python/rest_api/test_v2_0_0_artifact.py @@ -11,7 +11,7 @@ class ArtifactTestCase(APITestCase): def test_add(self): - ARTIFACT_NAME = "quay.io/myimage/myartifact:latest" + ARTIFACT_NAME = "quay.io/myimage/myartifact" file = ArtifactFile() parameters: dict[str, str | list[str]] = { "name": ARTIFACT_NAME, @@ -43,6 +43,10 @@ def test_add(self): # Assert blob media type fallback detection is working self.assertEqual(artifact_layer["mediaType"], "application/octet-stream") + # Assert latest tag was added by default + self.assertEqual(inspect_response_json["Name"], "quay.io/myimage/myartifact:latest") + + def test_add_with_replace(self): ARTIFACT_NAME = "quay.io/myimage/newartifact:latest" @@ -128,7 +132,7 @@ def test_add_with_append(self): self.assertEqual(len(artifact_layers), 2) def test_add_with_artifactMIMEType_override(self): - ARTIFACT_NAME = "quay.io/myimage/myartifact_artifactType:latest" + ARTIFACT_NAME = "quay.io/myimage/myartifact_artifact_type:latest" file = ArtifactFile() parameters: dict[str, str | list[str]] = { "name": ARTIFACT_NAME, @@ -672,7 +676,7 @@ def test_push_missing_artifact(self): # Assert return error response is json and contains correct message self.assertIn( - "no descriptor found for reference", + "artifact does not exist", rjson["cause"], ) diff --git a/test/e2e/artifact_created_test.go b/test/e2e/artifact_created_test.go index 8c170776681..ca96da81c1b 100644 --- a/test/e2e/artifact_created_test.go +++ b/test/e2e/artifact_created_test.go @@ -47,7 +47,7 @@ var _ = Describe("Podman artifact created timestamp", func() { // Inspect artifact a := podmanTest.InspectArtifact(artifactName) - Expect(a.Name).To(Equal(artifactName)) + Expect(a.Name).To(Equal(artifactName + ":latest")) // Check that created annotation exists and is in valid RFC3339 format createdStr, exists := a.Manifest.Annotations["org.opencontainers.image.created"] diff --git a/test/e2e/artifact_test.go b/test/e2e/artifact_test.go index fd640aca113..afff72da9f6 100644 --- a/test/e2e/artifact_test.go +++ b/test/e2e/artifact_test.go @@ -99,12 +99,12 @@ var _ = Describe("Podman artifact", func() { a := podmanTest.InspectArtifact(artifact1Name) - Expect(a.Name).To(Equal(artifact1Name)) + Expect(a.Name).To(Equal(artifact1Name + ":latest")) // Adding an artifact with an existing name should fail addAgain := podmanTest.Podman([]string{"artifact", "add", artifact1Name, artifact1File}) addAgain.WaitWithDefaultTimeout() - Expect(addAgain).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact already exists", artifact1Name))) + Expect(addAgain).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact already exists", artifact1Name+":latest"))) }) It("podman artifact add with options", func() { @@ -124,7 +124,7 @@ var _ = Describe("Podman artifact", func() { 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.Name).To(Equal(artifact1Name + ":latest")) 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")) @@ -149,7 +149,6 @@ var _ = Describe("Podman artifact", func() { failSession = podmanTest.Podman([]string{"artifact", "add", "--annotation", annotation3, artifact3Name, artifact1File, artifact2File}) failSession.WaitWithDefaultTimeout() Expect(failSession).Should(ExitWithError(125, "Error: duplicate layers org.opencontainers.image.title labels within an artifact not allowed")) - }) It("podman artifact add multiple", func() { @@ -163,7 +162,7 @@ var _ = Describe("Podman artifact", func() { podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File1, artifact1File2) a := podmanTest.InspectArtifact(artifact1Name) - Expect(a.Name).To(Equal(artifact1Name)) + Expect(a.Name).To(Equal(artifact1Name + ":latest")) Expect(a.Manifest.Layers).To(HaveLen(2)) }) @@ -200,7 +199,7 @@ var _ = Describe("Podman artifact", func() { a := podmanTest.InspectArtifact(artifact1Name) - Expect(a.Name).To(Equal(artifact1Name)) + Expect(a.Name).To(Equal(artifact1Name + ":latest")) }) It("podman artifact push with authorization", func() { @@ -508,7 +507,7 @@ var _ = Describe("Podman artifact", func() { podmanTest.PodmanExitCleanly("artifact", "add", "--append", "--annotation", annotation1, artifact1Name, artifact3File) a = podmanTest.InspectArtifact(artifact1Name) - Expect(a.Name).To(Equal(artifact1Name)) + Expect(a.Name).To(Equal(artifact1Name + ":latest")) Expect(a.Manifest.Layers).To(HaveLen(3)) for _, l := range a.Manifest.Layers { @@ -554,7 +553,6 @@ var _ = Describe("Podman artifact", func() { artifact1Name := "localhost/test/artifact1" podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File) - f, err := os.OpenFile(artifact1File, os.O_APPEND|os.O_WRONLY, 0o644) Expect(err).ToNot(HaveOccurred()) _, err = f.WriteString("This is modification.") @@ -619,16 +617,13 @@ var _ = Describe("Podman artifact", func() { podmanTest.PodmanExitCleanly("artifact", "add", "--type", artifactType, artifact1Name, artifact1File) a := podmanTest.InspectArtifact(artifact1Name) - Expect(a.Name).To(Equal(artifact1Name)) + Expect(a.Name).To(Equal(artifact1Name + ":latest")) 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)) - + Expect(a.Name).To(Equal(artifact1Name + ":latest")) 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 type option")) @@ -647,7 +642,7 @@ var _ = Describe("Podman artifact", func() { // Inspect artifact a := podmanTest.InspectArtifact(artifact1Name) - Expect(a.Name).To(Equal(artifact1Name)) + Expect(a.Name).To(Equal(artifact1Name + ":latest")) // Check that created annotation exists and is in valid Unix nanosecond format createdStr, exists := a.Manifest.Annotations["org.opencontainers.image.created"] diff --git a/test/e2e/inspect_test.go b/test/e2e/inspect_test.go index 7451ad57c64..648f8a355dc 100644 --- a/test/e2e/inspect_test.go +++ b/test/e2e/inspect_test.go @@ -626,7 +626,7 @@ var _ = Describe("Podman inspect", func() { inspect = podmanTest.Podman([]string{"inspect", "--format", "{{.Name}}", artifactName}) inspect.WaitWithDefaultTimeout() Expect(inspect).Should(ExitCleanly()) - Expect(inspect.OutputToString()).To(Equal(artifactName)) + Expect(inspect.OutputToString()).To(Equal(artifactName + ":latest")) inspect2 := podmanTest.Podman([]string{"inspect", "--format", "{{.Digest}}", artifactName}) inspect2.WaitWithDefaultTimeout()