Skip to content

Commit

Permalink
fix artifact set tagging (#1033)
Browse files Browse the repository at this point in the history
<!-- markdownlint-disable MD041 -->
#### What this PR does / why we need it
Because the new OCI references now always contain the digest in addition
to the tag, the artifactset creation
is not able anymore to provide appropriate meta data.

This is fixed by a new ArtVersion type able to handle the combination of
digest and tag.

#### Which issue(s) this PR fixes
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
-->

---------

Co-authored-by: Jakob Möller <[email protected]>
  • Loading branch information
mandelsoft and jakobmoellerdev authored Nov 6, 2024
1 parent 9e27f16 commit d6a5994
Show file tree
Hide file tree
Showing 13 changed files with 214 additions and 77 deletions.
8 changes: 4 additions & 4 deletions api/oci/art_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func CheckArt(ref string, exp *oci.ArtSpec) {
Expect(err).To(HaveOccurred())
} else {
Expect(err).To(Succeed())
Expect(spec).To(Equal(*exp))
Expect(spec).To(Equal(exp))
}
}

Expand All @@ -26,9 +26,9 @@ var _ = Describe("art parsing", func() {
It("succeeds", func() {
CheckArt("ubuntu", &oci.ArtSpec{Repository: "ubuntu"})
CheckArt("ubuntu/test", &oci.ArtSpec{Repository: "ubuntu/test"})
CheckArt("ubuntu/test@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", Digest: &digest})
CheckArt("ubuntu/test:"+tag, &oci.ArtSpec{Repository: "ubuntu/test", Tag: &tag})
CheckArt("ubuntu/test:"+tag+"@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", Digest: &digest, Tag: &tag})
CheckArt("ubuntu/test@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", ArtVersion: oci.ArtVersion{Digest: &digest}})
CheckArt("ubuntu/test:"+tag, &oci.ArtSpec{Repository: "ubuntu/test", ArtVersion: oci.ArtVersion{Tag: &tag}})
CheckArt("ubuntu/test:"+tag+"@"+digest.String(), &oci.ArtSpec{Repository: "ubuntu/test", ArtVersion: oci.ArtVersion{Digest: &digest, Tag: &tag}})
})

It("fails", func() {
Expand Down
10 changes: 8 additions & 2 deletions api/oci/extensions/repositories/artifactset/utils_synthesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (

"ocm.software/ocm/api/oci/artdesc"
"ocm.software/ocm/api/oci/cpi"
"ocm.software/ocm/api/oci/ociutils"
"ocm.software/ocm/api/oci/tools/transfer"
"ocm.software/ocm/api/oci/tools/transfer/filters"
"ocm.software/ocm/api/utils/accessio"
Expand Down Expand Up @@ -92,14 +93,19 @@ func SynthesizeArtifactBlobForArtifact(art cpi.ArtifactAccess, ref string, filte
return nil, err
}

vers, err := ociutils.ParseVersion(ref)
if err != nil {
return nil, err
}

return SythesizeArtifactSet(func(set *ArtifactSet) (string, error) {
dig, err := transfer.TransferArtifactWithFilter(art, set, filters.And(filter...))
if err != nil {
return "", fmt.Errorf("failed to transfer artifact: %w", err)
}

if ok, _ := artdesc.IsDigest(ref); !ok {
err = set.AddTags(*dig, ref)
if ok := vers.IsTagged(); ok {
err = set.AddTags(*dig, vers.GetTag())
if err != nil {
return "", fmt.Errorf("failed to add tag: %w", err)
}
Expand Down
97 changes: 97 additions & 0 deletions api/oci/ociutils/ref.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package ociutils

import (
"strings"

"github.com/mandelsoft/goutils/generics"
"github.com/opencontainers/go-digest"
)

// ParseVersion parses the version part of an OCI reference consisting
// of an optional tag and/or digest.
func ParseVersion(vers string) (*ArtVersion, error) {
if strings.HasPrefix(vers, "@") {
dig, err := digest.Parse(vers[1:])
if err != nil {
return nil, err
}
return &ArtVersion{
Digest: &dig,
}, nil
}

i := strings.Index(vers, "@")
if i > 0 {
dig, err := digest.Parse(vers[i+1:])
if err != nil {
return nil, err
}
return &ArtVersion{
Tag: generics.Pointer(vers[:i]),
Digest: &dig,
}, nil
}
return &ArtVersion{
Tag: &vers,
}, nil
}

// ArtVersion is the version part of an OCI reference consisting of an
// optional tag and/or digest. Both parts may be nil, if a reference
// does not include a version part.
// Such objects are sub objects of (oci.)ArtSpec, which has be moved
// to separate package to avoid package cycles. The methods are
// derived from ArtSpec.
type ArtVersion struct {
// +optional
Tag *string `json:"tag,omitempty"`
// +optional
Digest *digest.Digest `json:"digest,omitempty"`
}

func (v *ArtVersion) VersionSpec() string {
if v != nil {
return ""
}

vers := ""
if v.Tag != nil {
vers = *v.Tag
}

if v.Digest != nil {
vers += "@" + string(*v.Digest)
}
if vers == "" {
return "latest"
}
return vers
}

// IsVersion returns true, if the object ref is given
// and describes a dedicated version, either by tag or digest.
// As part of the ArtSpec type in oci, it might describe
// no version part. THis method indicates, whether a version part
// is present.
func (v *ArtVersion) IsVersion() bool {
if v == nil {
return false
}
return v.Tag != nil || v.Digest != nil
}

func (v *ArtVersion) IsTagged() bool {
return v != nil && v.Tag != nil
}

func (v *ArtVersion) IsDigested() bool {
return v != nil && v.Digest != nil
}

func (v *ArtVersion) GetTag() string {
if v != nil &&
v.Tag != nil {
return *v.Tag
}
return ""
}
48 changes: 23 additions & 25 deletions api/oci/ref.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/opencontainers/go-digest"

"ocm.software/ocm/api/oci/grammar"
"ocm.software/ocm/api/oci/ociutils"
)

// to find a suitable secret for images on Docker Hub, we need its two domains to do matching.
Expand Down Expand Up @@ -224,11 +225,18 @@ func (r RefSpec) DeepCopy() RefSpec {

////////////////////////////////////////////////////////////////////////////////

func ParseArt(art string) (ArtSpec, error) {
// ParseVersion parses an OCI version part of an OCI reference.
// It has to be placed in a utils package to avoid package cycles
// for particular users.
func ParseVersion(vers string) (*ArtVersion, error) {
return ociutils.ParseVersion(vers)
}

func ParseArt(art string) (*ArtSpec, error) {
match := grammar.AnchoredArtifactVersionRegexp.FindSubmatch([]byte(art))

if match == nil {
return ArtSpec{}, errors.ErrInvalid(KIND_ARETEFACT_REFERENCE, art)
return nil, errors.ErrInvalid(KIND_ARETEFACT_REFERENCE, art)
}
var tag *string
var dig *digest.Digest
Expand All @@ -241,25 +249,27 @@ func ParseArt(art string) (ArtSpec, error) {
t := string(match[3])
d, err := digest.Parse(t)
if err != nil {
return ArtSpec{}, errors.ErrInvalidWrap(err, KIND_ARETEFACT_REFERENCE, art)
return nil, errors.ErrInvalidWrap(err, KIND_ARETEFACT_REFERENCE, art)
}
dig = &d
}
return ArtSpec{
return &ArtSpec{
Repository: string(match[1]),
Tag: tag,
Digest: dig,
ArtVersion: ArtVersion{
Tag: tag,
Digest: dig,
},
}, nil
}

type ArtVersion = ociutils.ArtVersion

// ArtSpec is a go internal representation of a oci reference.
type ArtSpec struct {
// Repository is the part of a reference without its hostname
Repository string `json:"repository"`
// +optional
Tag *string `json:"tag,omitempty"`
// +optional
Digest *digest.Digest `json:"digest,omitempty"`
// artifact version
ArtVersion `json:",inline"`
}

func (r *ArtSpec) Version() string {
Expand All @@ -276,22 +286,10 @@ func (r *ArtSpec) IsRegistry() bool {
return r.Repository == ""
}

func (r *ArtSpec) IsVersion() bool {
return r.Tag != nil || r.Digest != nil
}

func (r *ArtSpec) IsTagged() bool {
return r.Tag != nil
}

func (r *ArtSpec) GetTag() string {
if r.Tag != nil {
return *r.Tag
}
return ""
}

func (r *ArtSpec) String() string {
if r == nil {
return ""
}
s := r.Repository
if r.Tag != nil {
s += fmt.Sprintf(":%s", *r.Tag)
Expand Down
Loading

0 comments on commit d6a5994

Please sign in to comment.