Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions common/pkg/libartifact/artifact.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package libartifact

import (
"encoding/json"
"fmt"
"strings"

"github.com/opencontainers/go-digest"
"go.podman.io/common/pkg/libartifact/types"
Expand Down Expand Up @@ -54,26 +52,3 @@ func (a *Artifact) GetDigest() (*digest.Digest, error) {
}

type ArtifactList []*Artifact

// GetByNameOrDigest returns an artifact, if present, by a given name
// Returns an error if not found.
func (al ArtifactList) GetByNameOrDigest(nameOrDigest string) (*Artifact, bool, error) {
// This is the hot route through
for _, artifact := range al {
if artifact.Name == nameOrDigest {
return artifact, false, nil
}
}
// Before giving up, check by digest
for _, artifact := range al {
artifactDigest, err := artifact.GetDigest()
if err != nil {
return nil, false, err
}
// If the artifact's digest matches or is a prefix of ...
if artifactDigest.Encoded() == nameOrDigest || strings.HasPrefix(artifactDigest.Encoded(), nameOrDigest) {
return artifact, true, nil
}
}
return nil, false, fmt.Errorf("%s: %w", nameOrDigest, types.ErrArtifactNotExist)
}
63 changes: 63 additions & 0 deletions common/pkg/libartifact/store/reference.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package store

import (
"go.podman.io/common/pkg/libartifact/types"
"go.podman.io/image/v5/docker/reference"
)

// ArtifactReference is a fully qualified oci reference except for tag, where we add
// "latest" as the tag if tag is empty. Valid references:
//
// quay.io/podman/machine-os:latest
// quay.io/podman/machine-os
// quay.io/podman/machine-os@sha256:916ede4b2b9012f91f63100f8ba82d07ed81bf8a55d23c1503285a22a9759a1e
//
// Note: Partial sha references and digests (IDs) are not allowed.
// Note: A zero value of ArtifactReference{} is not valid because it violates
// the format promise.
// Note: repo:tag@digest are invalid.
type ArtifactReference struct {
ref reference.Named
}

// Name returns the full reference without the tag.
func (ar ArtifactReference) RepoName() string {
return ar.ref.Name()
}

// String returns the full reference.
func (ar ArtifactReference) String() string {
return ar.ref.String()
}

func (ar ArtifactReference) ToArtifactStoreReference() ArtifactStoreReference {
afr := ArtifactStoreReference{
ref: &ar.ref,
}
return afr
}

// NewArtifactReference is a theoretical reference to an artifact.
func NewArtifactReference(input string) (ArtifactReference, error) {
named, err := stringToNamed(input)
if err != nil {
return ArtifactReference{}, err
}
return ArtifactReference{ref: named}, nil
}

// stringToNamed converts a string to a reference.Named.
func stringToNamed(s string) (reference.Named, error) {
named, err := reference.ParseNamed(s)
if err != nil {
return nil, err
}
_, isTagged := named.(reference.NamedTagged)
_, isDigested := named.(reference.Digested)

if isTagged && isDigested {
return nil, types.ErrTaggedAndDigested
}

return reference.TagNameOnly(named), nil
}
101 changes: 101 additions & 0 deletions common/pkg/libartifact/store/reference_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package store

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.podman.io/common/pkg/libartifact/types"
)

func TestNewArtifactReference(t *testing.T) {
tests := []struct {
name string
input string
expectError bool
errorContains string
errorIs error
expectedRef string
}{
{
name: "valid reference with tag",
input: "quay.io/podman/machine-os:5.1",
expectError: false,
expectedRef: "quay.io/podman/machine-os:5.1",
},
{
name: "valid reference docker.io",
input: "docker.io/library/nginx:latest",
expectError: false,
},
{
name: "empty string",
input: "",
expectError: true,
errorContains: "invalid reference format",
},
{
name: "malformed reference",
input: "invalid::reference",
expectError: true,
errorContains: "invalid reference format",
},
{
name: "no tag adds latest",
input: "quay.io/machine-os/podman",
expectError: false,
expectedRef: "quay.io/machine-os/podman:latest",
},
{
name: "valid digest",
input: "quay.io/machine-os/podman@sha256:8b96f36deaf1d2713858eebd9ef2fee9610df8452fbd083bbfa7dca66d6fcd0b",
expectError: false,
},
{
name: "partial digest",
input: "quay.io/machine-os/podman@sha256:8b96f36deaf1d2",
expectError: true,
errorContains: "invalid reference format",
},
{
name: "64-byte hex ID",
input: "84ddb405470e733d0202d6946e48fc75a7ee231337bdeb31a8579407a7052d9e",
expectError: true,
errorContains: "cannot specify 64-byte hexadecimal strings",
},
{
name: "shortname rejected",
input: "machine-os:latest",
expectError: true,
errorContains: "repository name must be canonical",
},
{
name: "tagged and digested",
input: "quay.io/machine-os/podman:latest@sha256:8b96f36deaf1d2713858eebd9ef2fee9610df8452fbd083bbfa7dca66d6fcd0b",
expectError: true,
errorIs: types.ErrTaggedAndDigested,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ar, err := NewArtifactReference(tt.input)

if tt.expectError {
assert.Error(t, err)
if len(tt.errorContains) > 0 {
assert.ErrorContains(t, err, tt.errorContains)
}
if tt.errorIs != nil {
assert.ErrorIs(t, err, tt.errorIs)
}
} else {
require.NoError(t, err)
assert.NotNil(t, ar.ref)
if len(tt.expectedRef) > 0 {
assert.Equal(t, tt.expectedRef, ar.ref.String())
}
}
})
}
}
Loading
Loading