-
Notifications
You must be signed in to change notification settings - Fork 208
/
reference.go
235 lines (203 loc) · 6.05 KB
/
reference.go
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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
package cnab
import (
"encoding/json"
"errors"
"fmt"
"strings"
"github.com/Masterminds/semver/v3"
"github.com/docker/distribution/reference"
"github.com/docker/docker/registry"
"github.com/opencontainers/go-digest"
)
// ParseOCIReference parses the specified value as an OCIReference.
// If the reference includes a digest, the digest is validated.
func ParseOCIReference(value string) (OCIReference, error) {
named, err := reference.ParseNormalizedNamed(value)
if err != nil {
return OCIReference{}, fmt.Errorf("failed to parse named reference %s: %w", value, err)
}
ref := OCIReference{Named: named}
if ref.HasDigest() {
err := ref.Digest().Validate()
if err != nil {
return OCIReference{}, err
}
}
return ref, nil
}
// MustParseOCIReference parses the specified value as an OCIReference,
// panicking on any errors.
// Only use this for unit tests where you know the value is a reference.
func MustParseOCIReference(value string) OCIReference {
ref, err := ParseOCIReference(value)
if err != nil {
panic(err)
}
return ref
}
var (
_ json.Marshaler = OCIReference{}
_ json.Unmarshaler = &OCIReference{}
)
// OCIReference is a wrapper around a docker reference with convenience methods
// for decomposing and manipulating bundle references.
//
// It is designed to be safe to call even when uninitialized, returning empty
// strings when parts are requested that do not exist, such as calling Digest()
// when no digest is set on the reference.
type OCIReference struct {
// Name is the wrapped reference that we are providing helper methods on top of
Named reference.Named
}
func (r *OCIReference) UnmarshalJSON(bytes []byte) error {
value := strings.TrimPrefix(strings.TrimSuffix(string(bytes), `"`), `"`)
ref, err := ParseOCIReference(value)
if err != nil {
return err
}
r.Named = ref.Named
return nil
}
func (r OCIReference) MarshalJSON() ([]byte, error) {
return []byte(fmt.Sprintf(`"%s"`, r.String())), nil
}
// Always print the original name provided, not
// the one with docker.io prefixed.
func (r OCIReference) String() string {
if r.Named == nil {
return ""
}
return reference.FamiliarString(r.Named)
}
// Repository portion of the reference.
// Example: docker.io/getporter/mybuns:v0.1.1 returns getporter/mybuns
func (r OCIReference) Repository() string {
if r.Named == nil {
return ""
}
return reference.FamiliarName(r.Named)
}
// Registry portion of the reference.
// Example: ghcr.io/getporter/mybuns:v0.1.1 returns ghcr.io
func (r OCIReference) Registry() string {
if r.Named == nil {
return ""
}
return reference.Domain(r.Named)
}
// IsRepositoryOnly determines if the reference is fully qualified
// with a tag/digest or if it only contains a repository.
// Example: ghcr.io/getporter/mybuns returns true
func (r OCIReference) IsRepositoryOnly() bool {
return !r.HasTag() && !r.HasDigest()
}
// HasDigest determines if the reference has a digest portion.
// Example: ghcr.io/getporter/mybuns@sha256:abc123 returns true
func (r OCIReference) HasDigest() bool {
if r.Named == nil {
return false
}
_, ok := r.Named.(reference.Digested)
return ok
}
// Digest portion of the reference.
// Example: ghcr.io/getporter/mybuns@sha256:abc123 returns sha256:abc123
func (r OCIReference) Digest() digest.Digest {
if r.Named == nil {
return ""
}
t, ok := r.Named.(reference.Digested)
if ok {
return t.Digest()
}
return ""
}
// HasTag determines if the reference has a tag portion.
// Example: ghcr.io/getporter/mybuns:latest returns true
func (r OCIReference) HasTag() bool {
if r.Named == nil {
return false
}
_, ok := r.Named.(reference.Tagged)
return ok
}
// Tag portion of the reference.
// Example: ghcr.io/getporter/mybuns:latest returns latest
func (r OCIReference) Tag() string {
if r.Named == nil {
return ""
}
t, ok := r.Named.(reference.Tagged)
if ok {
return t.Tag()
}
return ""
}
// HasVersion detects if the reference tag is a bundle version (semver).
func (r OCIReference) HasVersion() bool {
if r.Named == nil {
return false
}
if tagged, ok := r.Named.(reference.Tagged); ok {
_, err := semver.NewVersion(tagged.Tag())
return err == nil
}
return false
}
// Version parses the reference tag as a bundle version (semver).
func (r OCIReference) Version() string {
if r.Named == nil {
return ""
}
if tagged, ok := r.Named.(reference.Tagged); ok {
v, err := semver.NewVersion(tagged.Tag())
if err == nil {
return v.String()
}
}
return ""
}
// WithVersion creates a new reference using the repository and the specified bundle version.
func (r OCIReference) WithVersion(version string) (OCIReference, error) {
if r.Named == nil {
return OCIReference{}, errors.New("OCIReference has not been initialized")
}
v, err := semver.NewVersion(version)
if err != nil {
return OCIReference{}, fmt.Errorf("invalid bundle version specified %s: %w", version, err)
}
newRef, err := reference.WithTag(r.Named, "v"+v.String())
if err != nil {
return OCIReference{}, err
}
return OCIReference{Named: newRef}, nil
}
// WithTag creates a new reference using the repository and the specified tag.
func (r OCIReference) WithTag(tag string) (OCIReference, error) {
if r.Named == nil {
return OCIReference{}, errors.New("OCIReference has not been initialized")
}
newRef, err := reference.WithTag(r.Named, tag)
if err != nil {
return OCIReference{}, err
}
return OCIReference{Named: newRef}, nil
}
// WithDigest creates a new reference using the repository and the specified digest.
func (r OCIReference) WithDigest(digest digest.Digest) (OCIReference, error) {
if r.Named == nil {
return OCIReference{}, errors.New("OCIReference has not been initialized")
}
newRef, err := reference.WithDigest(r.Named, digest)
if err != nil {
return OCIReference{}, err
}
return OCIReference{Named: newRef}, nil
}
// ParseRepositoryInfo returns additional metadata about the repository portion of the reference.
func (r OCIReference) ParseRepositoryInfo() (*registry.RepositoryInfo, error) {
if r.Named == nil {
return nil, errors.New("OCIReference has not been initialized")
}
return registry.ParseRepositoryInfo(r.Named)
}