File: image.go

package info (click to toggle)
singularity-container 4.1.5%2Bds4-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 43,876 kB
  • sloc: asm: 14,840; sh: 3,190; ansic: 1,751; awk: 414; makefile: 413; python: 99
file content (132 lines) | stat: -rw-r--r-- 3,771 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
package purl

import (
	"strings"

	"github.com/containerd/platforms"
	"github.com/distribution/reference"
	digest "github.com/opencontainers/go-digest"
	ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
	packageurl "github.com/package-url/packageurl-go"
	"github.com/pkg/errors"
)

// RefToPURL converts an image reference with optional platform constraint to a package URL.
// Image references are defined in https://github.com/distribution/distribution/blob/v2.8.1/reference/reference.go#L1
// Package URLs are defined in https://github.com/package-url/purl-spec
func RefToPURL(purlType string, ref string, platform *ocispecs.Platform) (string, error) {
	named, err := reference.ParseNormalizedNamed(ref)
	if err != nil {
		return "", errors.Wrapf(err, "failed to parse ref %q", ref)
	}
	var qualifiers []packageurl.Qualifier

	if canonical, ok := named.(reference.Canonical); ok {
		qualifiers = append(qualifiers, packageurl.Qualifier{
			Key:   "digest",
			Value: canonical.Digest().String(),
		})
	} else {
		named = reference.TagNameOnly(named)
	}

	version := ""
	if tagged, ok := named.(reference.Tagged); ok {
		version = tagged.Tag()
	}

	name := reference.FamiliarName(named)

	ns := ""
	parts := strings.Split(name, "/")
	if len(parts) > 1 {
		ns = strings.Join(parts[:len(parts)-1], "/")
	}
	name = parts[len(parts)-1]

	if platform != nil {
		p := platforms.Normalize(*platform)
		qualifiers = append(qualifiers, packageurl.Qualifier{
			Key:   "platform",
			Value: platforms.Format(p),
		})
	}

	p := packageurl.NewPackageURL(purlType, ns, name, version, qualifiers, "")
	return p.ToString(), nil
}

// PURLToRef converts a package URL to an image reference and platform.
func PURLToRef(purl string) (string, *ocispecs.Platform, error) {
	p, err := packageurl.FromString(purl)
	if err != nil {
		return "", nil, err
	}
	if p.Type != "docker" {
		return "", nil, errors.Errorf("invalid package type %q, expecting docker", p.Type)
	}
	ref := p.Name
	if p.Namespace != "" {
		ref = p.Namespace + "/" + ref
	}
	dgstVersion := ""
	if p.Version != "" {
		dgst, err := digest.Parse(p.Version)
		if err == nil {
			ref = ref + "@" + dgst.String()
			dgstVersion = dgst.String()
		} else {
			ref += ":" + p.Version
		}
	}
	var platform *ocispecs.Platform
	for _, q := range p.Qualifiers {
		if q.Key == "platform" {
			p, err := platforms.Parse(q.Value)
			if err != nil {
				return "", nil, err
			}

			// OS-version and OS-features are not included when serializing a
			// platform as a string, however, containerd platforms.Parse appends
			// missing information (including os-version) based on the host's
			// platform.
			//
			// Given that this information is not obtained from the package-URL,
			// we're resetting this information. Ideally, we'd do the same for
			// "OS" and "architecture" (when not included in the URL).
			//
			// See:
			// - https://github.com/containerd/containerd/commit/cfb30a31a8507e4417d42d38c9a99b04fc8af8a9 (https://github.com/containerd/containerd/pull/8778)
			// - https://github.com/moby/buildkit/pull/4315#discussion_r1355141241
			p.OSVersion = ""
			p.OSFeatures = nil
			platform = &p
		}
		if q.Key == "digest" {
			if dgstVersion != "" {
				if dgstVersion != q.Value {
					return "", nil, errors.Errorf("digest %q does not match version %q", q.Value, dgstVersion)
				}
				continue
			}
			dgst, err := digest.Parse(q.Value)
			if err != nil {
				return "", nil, err
			}
			ref = ref + "@" + dgst.String()
			dgstVersion = dgst.String()
		}
	}

	if dgstVersion == "" && p.Version == "" {
		ref += ":latest"
	}

	named, err := reference.ParseNormalizedNamed(ref)
	if err != nil {
		return "", nil, errors.Wrapf(err, "invalid image url %q", purl)
	}

	return named.String(), platform, nil
}