...
1
15 package registry
16
17 import (
18 "fmt"
19 "net/url"
20 "regexp"
21 "strings"
22
23 "github.com/opencontainers/go-digest"
24 errdef "oras.land/oras-go/pkg/content"
25 )
26
27
28 var (
29
30
31
32
33
34
35
36 repositoryRegexp = regexp.MustCompile(`^[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*(?:/[a-z0-9]+(?:(?:[._]|__|[-]*)[a-z0-9]+)*)*$`)
37
38
39
40
41 tagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`)
42 )
43
44
45 type Reference struct {
46
47
48 Registry string
49
50
51 Repository string
52
53
54
55 Reference string
56 }
57
58
59
60
61
62 func ParseReference(raw string) (Reference, error) {
63 parts := strings.SplitN(raw, "/", 2)
64 if len(parts) == 1 {
65 return Reference{}, fmt.Errorf("%w: missing repository", errdef.ErrInvalidReference)
66 }
67 registry, path := parts[0], parts[1]
68 var repository string
69 var reference string
70 if index := strings.Index(path, "@"); index != -1 {
71
72 repository = path[:index]
73 reference = path[index+1:]
74
75
76 if index := strings.Index(repository, ":"); index != -1 {
77 repository = repository[:index]
78 }
79 } else if index := strings.Index(path, ":"); index != -1 {
80
81 repository = path[:index]
82 reference = path[index+1:]
83 } else {
84
85 repository = path
86 }
87 res := Reference{
88 Registry: registry,
89 Repository: repository,
90 Reference: reference,
91 }
92 if err := res.Validate(); err != nil {
93 return Reference{}, err
94 }
95 return res, nil
96 }
97
98
99 func (r Reference) Validate() error {
100 err := r.ValidateRegistry()
101 if err != nil {
102 return err
103 }
104 err = r.ValidateRepository()
105 if err != nil {
106 return err
107 }
108 return r.ValidateReference()
109 }
110
111
112 func (r Reference) ValidateRegistry() error {
113 uri, err := url.ParseRequestURI("dummy://" + r.Registry)
114 if err != nil || uri.Host != r.Registry {
115 return fmt.Errorf("%w: invalid registry", errdef.ErrInvalidReference)
116 }
117 return nil
118 }
119
120
121 func (r Reference) ValidateRepository() error {
122 if !repositoryRegexp.MatchString(r.Repository) {
123 return fmt.Errorf("%w: invalid repository", errdef.ErrInvalidReference)
124 }
125 return nil
126 }
127
128
129 func (r Reference) ValidateReference() error {
130 if r.Reference == "" {
131 return nil
132 }
133 if _, err := r.Digest(); err == nil {
134 return nil
135 }
136 if !tagRegexp.MatchString(r.Reference) {
137 return fmt.Errorf("%w: invalid tag", errdef.ErrInvalidReference)
138 }
139 return nil
140 }
141
142
143 func (r Reference) Host() string {
144 if r.Registry == "docker.io" {
145 return "registry-1.docker.io"
146 }
147 return r.Registry
148 }
149
150
151 func (r Reference) ReferenceOrDefault() string {
152 if r.Reference == "" {
153 return "latest"
154 }
155 return r.Reference
156 }
157
158
159 func (r Reference) Digest() (digest.Digest, error) {
160 return digest.Parse(r.Reference)
161 }
162
163
164
165 func (r Reference) String() string {
166 if r.Repository == "" {
167 return r.Registry
168 }
169 ref := r.Registry + "/" + r.Repository
170 if r.Reference == "" {
171 return ref
172 }
173 if d, err := r.Digest(); err == nil {
174 return ref + "@" + d.String()
175 }
176 return ref + ":" + r.Reference
177 }
178
View as plain text