Skip to content

Commit 728c5c0

Browse files
committed
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: #27083 Signed-off-by: Lewis Roy <[email protected]>
1 parent 7fecff5 commit 728c5c0

File tree

8 files changed

+81
-31
lines changed

8 files changed

+81
-31
lines changed

pkg/api/handlers/libpod/artifacts.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,11 @@ func PushArtifact(w http.ResponseWriter, r *http.Request) {
346346
return
347347
}
348348

349+
if errors.Is(err, libartifact_types.ErrArtifactNotExist) {
350+
utils.ArtifactNotFound(w, name, err)
351+
return
352+
}
353+
349354
utils.InternalServerError(w, err)
350355
return
351356
}

pkg/domain/infra/abi/artifact.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/opencontainers/go-digest"
1616
"github.com/sirupsen/logrus"
1717
"go.podman.io/common/libimage"
18+
"go.podman.io/image/v5/docker/reference"
1819
)
1920

2021
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
5758
}
5859

5960
func (ir *ImageEngine) ArtifactPull(ctx context.Context, name string, opts entities.ArtifactPullOptions) (*entities.ArtifactPullReport, error) {
61+
named, err := reference.Parse(name)
62+
if err != nil {
63+
return nil, fmt.Errorf("parsing reference %q: %w", name, err)
64+
}
65+
namedRef, ok := named.(reference.Named)
66+
if !ok {
67+
return nil, fmt.Errorf("reference %q is not a Named reference", name)
68+
}
69+
name = reference.TagNameOnly(namedRef).String()
70+
6071
pullOptions := &libimage.CopyOptions{}
6172
pullOptions.AuthFilePath = opts.AuthFilePath
6273
pullOptions.CertDirPath = opts.CertDirPath

pkg/libartifact/artifact.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77

88
"github.com/containers/podman/v5/pkg/libartifact/types"
99
"github.com/opencontainers/go-digest"
10+
"go.podman.io/image/v5/docker/reference"
1011
"go.podman.io/image/v5/manifest"
1112
)
1213

@@ -58,9 +59,23 @@ type ArtifactList []*Artifact
5859
// GetByNameOrDigest returns an artifact, if present, by a given name
5960
// Returns an error if not found
6061
func (al ArtifactList) GetByNameOrDigest(nameOrDigest string) (*Artifact, bool, error) {
62+
named, err := reference.Parse(nameOrDigest)
63+
if err != nil {
64+
return nil, false, fmt.Errorf("parsing reference %q: %w", nameOrDigest, err)
65+
}
66+
67+
// Assert that named implements reference.Named interface
68+
namedRef, ok := named.(reference.Named)
69+
if !ok {
70+
return nil, false, fmt.Errorf("reference %q is not a Named reference", nameOrDigest)
71+
}
72+
73+
// Use TagNameOnly with the asserted Named reference
74+
refStr := reference.TagNameOnly(namedRef).String()
75+
6176
// This is the hot route through
6277
for _, artifact := range al {
63-
if artifact.Name == nameOrDigest {
78+
if artifact.Name == refStr {
6479
return artifact, false, nil
6580
}
6681
}

pkg/libartifact/store/store.go

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
specV1 "github.com/opencontainers/image-spec/specs-go/v1"
2727
"github.com/sirupsen/logrus"
2828
"go.podman.io/common/libimage"
29+
"go.podman.io/image/v5/docker/reference"
2930
"go.podman.io/image/v5/image"
3031
"go.podman.io/image/v5/manifest"
3132
"go.podman.io/image/v5/oci/layout"
@@ -101,14 +102,14 @@ func (as ArtifactStore) Remove(ctx context.Context, name string) (*digest.Digest
101102
return nil, err
102103
}
103104

104-
arty, nameIsDigest, err := artifacts.GetByNameOrDigest(name)
105+
arty, _, err := artifacts.GetByNameOrDigest(name)
105106
if err != nil {
106107
return nil, err
107108
}
108-
if nameIsDigest {
109-
name = arty.Name
110-
}
111-
ir, err := layout.NewReference(as.storePath, name)
109+
110+
// Use the canonical name from the found artifact, which will include the tag
111+
// if one was resolved by GetByNameOrDigest.
112+
ir, err := layout.NewReference(as.storePath, arty.Name)
112113
if err != nil {
113114
return nil, err
114115
}
@@ -189,7 +190,17 @@ func (as ArtifactStore) Push(ctx context.Context, src, dest string, opts libimag
189190
as.lock.Lock()
190191
defer as.lock.Unlock()
191192

192-
srcRef, err := layout.NewReference(as.storePath, src)
193+
artifacts, err := as.getArtifacts(ctx, nil)
194+
if err != nil {
195+
return "", err
196+
}
197+
198+
arty, _, err := artifacts.GetByNameOrDigest(src)
199+
if err != nil {
200+
return "", err
201+
}
202+
203+
srcRef, err := layout.NewReference(as.storePath, arty.Name)
193204
if err != nil {
194205
return "", err
195206
}
@@ -217,6 +228,18 @@ func (as ArtifactStore) Add(ctx context.Context, dest string, artifactBlobs []en
217228
return nil, ErrEmptyArtifactName
218229
}
219230

231+
named, err := reference.Parse(dest)
232+
if err != nil {
233+
return nil, fmt.Errorf("parsing reference %q: %w", dest, err)
234+
}
235+
236+
namedRef, ok := named.(reference.Named)
237+
if !ok {
238+
return nil, fmt.Errorf("reference %q is not a Named reference", dest)
239+
}
240+
241+
dest = reference.TagNameOnly(namedRef).String()
242+
220243
if options.Append && len(options.ArtifactMIMEType) > 0 {
221244
return nil, errors.New("append option is not compatible with type option")
222245
}
@@ -416,20 +439,16 @@ func getArtifactAndImageSource(ctx context.Context, as ArtifactStore, nameOrDige
416439
return nil, nil, err
417440
}
418441

419-
arty, nameIsDigest, err := artifacts.GetByNameOrDigest(nameOrDigest)
442+
arty, _, err := artifacts.GetByNameOrDigest(nameOrDigest)
420443
if err != nil {
421444
return nil, nil, err
422445
}
423-
name := nameOrDigest
424-
if nameIsDigest {
425-
name = arty.Name
426-
}
427446

428447
if len(arty.Manifest.Layers) == 0 {
429448
return nil, nil, fmt.Errorf("the artifact has no blobs, nothing to extract")
430449
}
431450

432-
ir, err := layout.NewReference(as.storePath, name)
451+
ir, err := layout.NewReference(as.storePath, arty.Name)
433452
if err != nil {
434453
return nil, nil, err
435454
}

test/apiv2/python/rest_api/test_v2_0_0_artifact.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
class ArtifactTestCase(APITestCase):
1313
def test_add(self):
14-
ARTIFACT_NAME = "quay.io/myimage/myartifact:latest"
14+
ARTIFACT_NAME = "quay.io/myimage/myartifact"
1515
file = ArtifactFile()
1616
parameters: dict[str, str | list[str]] = {
1717
"name": ARTIFACT_NAME,
@@ -43,6 +43,10 @@ def test_add(self):
4343
# Assert blob media type fallback detection is working
4444
self.assertEqual(artifact_layer["mediaType"], "application/octet-stream")
4545

46+
# Assert latest tag was added by default
47+
self.assertEqual(inspect_response_json["Name"], "quay.io/myimage/myartifact:latest")
48+
49+
4650
def test_add_with_replace(self):
4751
ARTIFACT_NAME = "quay.io/myimage/newartifact:latest"
4852

@@ -128,7 +132,7 @@ def test_add_with_append(self):
128132
self.assertEqual(len(artifact_layers), 2)
129133

130134
def test_add_with_artifactMIMEType_override(self):
131-
ARTIFACT_NAME = "quay.io/myimage/myartifact_artifactType:latest"
135+
ARTIFACT_NAME = "quay.io/myimage/myartifact_artifact_type:latest"
132136
file = ArtifactFile()
133137
parameters: dict[str, str | list[str]] = {
134138
"name": ARTIFACT_NAME,
@@ -672,7 +676,7 @@ def test_push_missing_artifact(self):
672676

673677
# Assert return error response is json and contains correct message
674678
self.assertIn(
675-
"no descriptor found for reference",
679+
"artifact does not exist",
676680
rjson["cause"],
677681
)
678682

test/e2e/artifact_created_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ var _ = Describe("Podman artifact created timestamp", func() {
4747

4848
// Inspect artifact
4949
a := podmanTest.InspectArtifact(artifactName)
50-
Expect(a.Name).To(Equal(artifactName))
50+
Expect(a.Name).To(Equal(artifactName + ":latest"))
5151

5252
// Check that created annotation exists and is in valid RFC3339 format
5353
createdStr, exists := a.Manifest.Annotations["org.opencontainers.image.created"]

test/e2e/artifact_test.go

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,12 +88,12 @@ var _ = Describe("Podman artifact", func() {
8888

8989
a := podmanTest.InspectArtifact(artifact1Name)
9090

91-
Expect(a.Name).To(Equal(artifact1Name))
91+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
9292

9393
// Adding an artifact with an existing name should fail
9494
addAgain := podmanTest.Podman([]string{"artifact", "add", artifact1Name, artifact1File})
9595
addAgain.WaitWithDefaultTimeout()
96-
Expect(addAgain).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact already exists", artifact1Name)))
96+
Expect(addAgain).Should(ExitWithError(125, fmt.Sprintf("Error: %s: artifact already exists", artifact1Name+":latest")))
9797
})
9898

9999
It("podman artifact add with options", func() {
@@ -109,7 +109,7 @@ var _ = Describe("Podman artifact", func() {
109109
podmanTest.PodmanExitCleanly("artifact", "add", "--file-type", yamlType, "--type", artifactType, "--annotation", annotation1, "--annotation", annotation2, artifact1Name, artifact1File)
110110

111111
a := podmanTest.InspectArtifact(artifact1Name)
112-
Expect(a.Name).To(Equal(artifact1Name))
112+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
113113
Expect(a.Manifest.ArtifactType).To(Equal(artifactType))
114114
Expect(a.Manifest.Layers[0].Annotations["color"]).To(Equal("blue"))
115115
Expect(a.Manifest.Layers[0].Annotations["flavor"]).To(Equal("lemon"))
@@ -131,7 +131,7 @@ var _ = Describe("Podman artifact", func() {
131131
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File1, artifact1File2)
132132

133133
a := podmanTest.InspectArtifact(artifact1Name)
134-
Expect(a.Name).To(Equal(artifact1Name))
134+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
135135

136136
Expect(a.Manifest.Layers).To(HaveLen(2))
137137
})
@@ -168,7 +168,7 @@ var _ = Describe("Podman artifact", func() {
168168

169169
a := podmanTest.InspectArtifact(artifact1Name)
170170

171-
Expect(a.Name).To(Equal(artifact1Name))
171+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
172172
})
173173

174174
It("podman artifact push with authorization", func() {
@@ -476,7 +476,7 @@ var _ = Describe("Podman artifact", func() {
476476
podmanTest.PodmanExitCleanly("artifact", "add", "--append", "--annotation", annotation1, artifact1Name, artifact3File)
477477

478478
a = podmanTest.InspectArtifact(artifact1Name)
479-
Expect(a.Name).To(Equal(artifact1Name))
479+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
480480
Expect(a.Manifest.Layers).To(HaveLen(3))
481481

482482
for _, l := range a.Manifest.Layers {
@@ -522,7 +522,6 @@ var _ = Describe("Podman artifact", func() {
522522

523523
artifact1Name := "localhost/test/artifact1"
524524
podmanTest.PodmanExitCleanly("artifact", "add", artifact1Name, artifact1File)
525-
526525
f, err := os.OpenFile(artifact1File, os.O_APPEND|os.O_WRONLY, 0644)
527526
Expect(err).ToNot(HaveOccurred())
528527
_, err = f.WriteString("This is modification.")
@@ -587,16 +586,13 @@ var _ = Describe("Podman artifact", func() {
587586
podmanTest.PodmanExitCleanly("artifact", "add", "--type", artifactType, artifact1Name, artifact1File)
588587

589588
a := podmanTest.InspectArtifact(artifact1Name)
590-
Expect(a.Name).To(Equal(artifact1Name))
589+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
591590
Expect(a.Manifest.ArtifactType).To(Equal(artifactType))
592591

593592
podmanTest.PodmanExitCleanly("artifact", "add", "--append", artifact1Name, artifact2File)
594593

595594
a = podmanTest.InspectArtifact(artifact1Name)
596-
Expect(a.Name).To(Equal(artifact1Name))
597-
Expect(a.Manifest.ArtifactType).To(Equal(artifactType))
598-
Expect(a.Manifest.Layers).To(HaveLen(2))
599-
595+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
600596
failSession := podmanTest.Podman([]string{"artifact", "add", "--type", artifactType, "--append", artifact1Name, artifact3File})
601597
failSession.WaitWithDefaultTimeout()
602598
Expect(failSession).Should(ExitWithError(125, "Error: append option is not compatible with type option"))
@@ -615,7 +611,7 @@ var _ = Describe("Podman artifact", func() {
615611

616612
// Inspect artifact
617613
a := podmanTest.InspectArtifact(artifact1Name)
618-
Expect(a.Name).To(Equal(artifact1Name))
614+
Expect(a.Name).To(Equal(artifact1Name + ":latest"))
619615

620616
// Check that created annotation exists and is in valid Unix nanosecond format
621617
createdStr, exists := a.Manifest.Annotations["org.opencontainers.image.created"]

test/e2e/inspect_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ var _ = Describe("Podman inspect", func() {
626626
inspect = podmanTest.Podman([]string{"inspect", "--format", "{{.Name}}", artifactName})
627627
inspect.WaitWithDefaultTimeout()
628628
Expect(inspect).Should(ExitCleanly())
629-
Expect(inspect.OutputToString()).To(Equal(artifactName))
629+
Expect(inspect.OutputToString()).To(Equal(artifactName + ":latest"))
630630

631631
inspect2 := podmanTest.Podman([]string{"inspect", "--format", "{{.Digest}}", artifactName})
632632
inspect2.WaitWithDefaultTimeout()

0 commit comments

Comments
 (0)