Distribution reference update
Aaron Lehmann authored on 2017/01/25 13:38:20... | ... |
@@ -5,6 +5,7 @@ import ( |
5 | 5 |
"io" |
6 | 6 |
"os" |
7 | 7 |
|
8 |
+ "github.com/docker/distribution/reference" |
|
8 | 9 |
"github.com/docker/docker/api/types" |
9 | 10 |
"github.com/docker/docker/api/types/container" |
10 | 11 |
networktypes "github.com/docker/docker/api/types/network" |
... | ... |
@@ -13,8 +14,6 @@ import ( |
13 | 13 |
"github.com/docker/docker/cli/command/image" |
14 | 14 |
apiclient "github.com/docker/docker/client" |
15 | 15 |
"github.com/docker/docker/pkg/jsonmessage" |
16 |
- // FIXME migrate to docker/distribution/reference |
|
17 |
- "github.com/docker/docker/reference" |
|
18 | 16 |
"github.com/docker/docker/registry" |
19 | 17 |
"github.com/spf13/cobra" |
20 | 18 |
"github.com/spf13/pflag" |
... | ... |
@@ -72,7 +71,7 @@ func runCreate(dockerCli *command.DockerCli, flags *pflag.FlagSet, opts *createO |
72 | 72 |
} |
73 | 73 |
|
74 | 74 |
func pullImage(ctx context.Context, dockerCli *command.DockerCli, image string, out io.Writer) error { |
75 |
- ref, err := reference.ParseNamed(image) |
|
75 |
+ ref, err := reference.ParseNormalizedNamed(image) |
|
76 | 76 |
if err != nil { |
77 | 77 |
return err |
78 | 78 |
} |
... | ... |
@@ -150,7 +149,12 @@ func newCIDFile(path string) (*cidFile, error) { |
150 | 150 |
func createContainer(ctx context.Context, dockerCli *command.DockerCli, config *container.Config, hostConfig *container.HostConfig, networkingConfig *networktypes.NetworkingConfig, cidfile, name string) (*container.ContainerCreateCreatedBody, error) { |
151 | 151 |
stderr := dockerCli.Err() |
152 | 152 |
|
153 |
- var containerIDFile *cidFile |
|
153 |
+ var ( |
|
154 |
+ containerIDFile *cidFile |
|
155 |
+ trustedRef reference.Canonical |
|
156 |
+ namedRef reference.Named |
|
157 |
+ ) |
|
158 |
+ |
|
154 | 159 |
if cidfile != "" { |
155 | 160 |
var err error |
156 | 161 |
if containerIDFile, err = newCIDFile(cidfile); err != nil { |
... | ... |
@@ -159,21 +163,24 @@ func createContainer(ctx context.Context, dockerCli *command.DockerCli, config * |
159 | 159 |
defer containerIDFile.Close() |
160 | 160 |
} |
161 | 161 |
|
162 |
- var trustedRef reference.Canonical |
|
163 |
- _, ref, err := reference.ParseIDOrReference(config.Image) |
|
162 |
+ ref, err := reference.ParseAnyReference(config.Image) |
|
164 | 163 |
if err != nil { |
165 | 164 |
return nil, err |
166 | 165 |
} |
167 |
- if ref != nil { |
|
168 |
- ref = reference.WithDefaultTag(ref) |
|
166 |
+ if named, ok := ref.(reference.Named); ok { |
|
167 |
+ if reference.IsNameOnly(named) { |
|
168 |
+ namedRef = reference.EnsureTagged(named) |
|
169 |
+ } else { |
|
170 |
+ namedRef = named |
|
171 |
+ } |
|
169 | 172 |
|
170 |
- if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() { |
|
173 |
+ if taggedRef, ok := namedRef.(reference.NamedTagged); ok && command.IsTrusted() { |
|
171 | 174 |
var err error |
172 |
- trustedRef, err = image.TrustedReference(ctx, dockerCli, ref, nil) |
|
175 |
+ trustedRef, err = image.TrustedReference(ctx, dockerCli, taggedRef, nil) |
|
173 | 176 |
if err != nil { |
174 | 177 |
return nil, err |
175 | 178 |
} |
176 |
- config.Image = trustedRef.String() |
|
179 |
+ config.Image = reference.FamiliarString(trustedRef) |
|
177 | 180 |
} |
178 | 181 |
} |
179 | 182 |
|
... | ... |
@@ -182,15 +189,15 @@ func createContainer(ctx context.Context, dockerCli *command.DockerCli, config * |
182 | 182 |
|
183 | 183 |
//if image not found try to pull it |
184 | 184 |
if err != nil { |
185 |
- if apiclient.IsErrImageNotFound(err) && ref != nil { |
|
186 |
- fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", ref.String()) |
|
185 |
+ if apiclient.IsErrImageNotFound(err) && namedRef != nil { |
|
186 |
+ fmt.Fprintf(stderr, "Unable to find image '%s' locally\n", reference.FamiliarString(namedRef)) |
|
187 | 187 |
|
188 | 188 |
// we don't want to write to stdout anything apart from container.ID |
189 | 189 |
if err = pullImage(ctx, dockerCli, config.Image, stderr); err != nil { |
190 | 190 |
return nil, err |
191 | 191 |
} |
192 |
- if ref, ok := ref.(reference.NamedTagged); ok && trustedRef != nil { |
|
193 |
- if err := image.TagTrusted(ctx, dockerCli, trustedRef, ref); err != nil { |
|
192 |
+ if taggedRef, ok := namedRef.(reference.NamedTagged); ok && trustedRef != nil { |
|
193 |
+ if err := image.TagTrusted(ctx, dockerCli, trustedRef, taggedRef); err != nil { |
|
194 | 194 |
return nil, err |
195 | 195 |
} |
196 | 196 |
} |
... | ... |
@@ -4,9 +4,9 @@ import ( |
4 | 4 |
"fmt" |
5 | 5 |
"time" |
6 | 6 |
|
7 |
+ "github.com/docker/distribution/reference" |
|
7 | 8 |
"github.com/docker/docker/api/types" |
8 | 9 |
"github.com/docker/docker/pkg/stringid" |
9 |
- "github.com/docker/docker/reference" |
|
10 | 10 |
units "github.com/docker/go-units" |
11 | 11 |
) |
12 | 12 |
|
... | ... |
@@ -95,21 +95,23 @@ func imageFormat(ctx ImageContext, images []types.ImageSummary, format func(subC |
95 | 95 |
repoDigests := map[string][]string{} |
96 | 96 |
|
97 | 97 |
for _, refString := range append(image.RepoTags) { |
98 |
- ref, err := reference.ParseNamed(refString) |
|
98 |
+ ref, err := reference.ParseNormalizedNamed(refString) |
|
99 | 99 |
if err != nil { |
100 | 100 |
continue |
101 | 101 |
} |
102 | 102 |
if nt, ok := ref.(reference.NamedTagged); ok { |
103 |
- repoTags[ref.Name()] = append(repoTags[ref.Name()], nt.Tag()) |
|
103 |
+ familiarRef := reference.FamiliarName(ref) |
|
104 |
+ repoTags[familiarRef] = append(repoTags[familiarRef], nt.Tag()) |
|
104 | 105 |
} |
105 | 106 |
} |
106 | 107 |
for _, refString := range append(image.RepoDigests) { |
107 |
- ref, err := reference.ParseNamed(refString) |
|
108 |
+ ref, err := reference.ParseNormalizedNamed(refString) |
|
108 | 109 |
if err != nil { |
109 | 110 |
continue |
110 | 111 |
} |
111 | 112 |
if c, ok := ref.(reference.Canonical); ok { |
112 |
- repoDigests[ref.Name()] = append(repoDigests[ref.Name()], c.Digest().String()) |
|
113 |
+ familiarRef := reference.FamiliarName(ref) |
|
114 |
+ repoDigests[familiarRef] = append(repoDigests[familiarRef], c.Digest().String()) |
|
113 | 115 |
} |
114 | 116 |
} |
115 | 117 |
|
... | ... |
@@ -11,6 +11,7 @@ import ( |
11 | 11 |
"regexp" |
12 | 12 |
"runtime" |
13 | 13 |
|
14 |
+ "github.com/docker/distribution/reference" |
|
14 | 15 |
"github.com/docker/docker/api" |
15 | 16 |
"github.com/docker/docker/api/types" |
16 | 17 |
"github.com/docker/docker/api/types/container" |
... | ... |
@@ -25,7 +26,6 @@ import ( |
25 | 25 |
"github.com/docker/docker/pkg/progress" |
26 | 26 |
"github.com/docker/docker/pkg/streamformatter" |
27 | 27 |
"github.com/docker/docker/pkg/urlutil" |
28 |
- "github.com/docker/docker/reference" |
|
29 | 28 |
runconfigopts "github.com/docker/docker/runconfig/opts" |
30 | 29 |
units "github.com/docker/go-units" |
31 | 30 |
"github.com/spf13/cobra" |
... | ... |
@@ -360,7 +360,7 @@ type translatorFunc func(context.Context, reference.NamedTagged) (reference.Cano |
360 | 360 |
|
361 | 361 |
// validateTag checks if the given image name can be resolved. |
362 | 362 |
func validateTag(rawRepo string) (string, error) { |
363 |
- _, err := reference.ParseNamed(rawRepo) |
|
363 |
+ _, err := reference.ParseNormalizedNamed(rawRepo) |
|
364 | 364 |
if err != nil { |
365 | 365 |
return "", err |
366 | 366 |
} |
... | ... |
@@ -392,18 +392,21 @@ func rewriteDockerfileFrom(ctx context.Context, dockerfile io.Reader, translator |
392 | 392 |
matches := dockerfileFromLinePattern.FindStringSubmatch(line) |
393 | 393 |
if matches != nil && matches[1] != api.NoBaseImageSpecifier { |
394 | 394 |
// Replace the line with a resolved "FROM repo@digest" |
395 |
- ref, err := reference.ParseNamed(matches[1]) |
|
395 |
+ var ref reference.Named |
|
396 |
+ ref, err = reference.ParseNormalizedNamed(matches[1]) |
|
396 | 397 |
if err != nil { |
397 | 398 |
return nil, nil, err |
398 | 399 |
} |
399 |
- ref = reference.WithDefaultTag(ref) |
|
400 |
+ if reference.IsNameOnly(ref) { |
|
401 |
+ ref = reference.EnsureTagged(ref) |
|
402 |
+ } |
|
400 | 403 |
if ref, ok := ref.(reference.NamedTagged); ok && command.IsTrusted() { |
401 | 404 |
trustedRef, err := translator(ctx, ref) |
402 | 405 |
if err != nil { |
403 | 406 |
return nil, nil, err |
404 | 407 |
} |
405 | 408 |
|
406 |
- line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", trustedRef.String())) |
|
409 |
+ line = dockerfileFromLinePattern.ReplaceAllLiteralString(line, fmt.Sprintf("FROM %s", reference.FamiliarString(trustedRef))) |
|
407 | 410 |
resolvedTags = append(resolvedTags, &resolvedTag{ |
408 | 411 |
digestRef: trustedRef, |
409 | 412 |
tagRef: ref, |
... | ... |
@@ -7,9 +7,9 @@ import ( |
7 | 7 |
|
8 | 8 |
"golang.org/x/net/context" |
9 | 9 |
|
10 |
+ "github.com/docker/distribution/reference" |
|
10 | 11 |
"github.com/docker/docker/cli" |
11 | 12 |
"github.com/docker/docker/cli/command" |
12 |
- "github.com/docker/docker/reference" |
|
13 | 13 |
"github.com/docker/docker/registry" |
14 | 14 |
"github.com/spf13/cobra" |
15 | 15 |
) |
... | ... |
@@ -42,7 +42,8 @@ func NewPullCommand(dockerCli *command.DockerCli) *cobra.Command { |
42 | 42 |
} |
43 | 43 |
|
44 | 44 |
func runPull(dockerCli *command.DockerCli, opts pullOptions) error { |
45 |
- distributionRef, err := reference.ParseNamed(opts.remote) |
|
45 |
+ var distributionRef reference.Named |
|
46 |
+ distributionRef, err := reference.ParseNormalizedNamed(opts.remote) |
|
46 | 47 |
if err != nil { |
47 | 48 |
return err |
48 | 49 |
} |
... | ... |
@@ -51,8 +52,9 @@ func runPull(dockerCli *command.DockerCli, opts pullOptions) error { |
51 | 51 |
} |
52 | 52 |
|
53 | 53 |
if !opts.all && reference.IsNameOnly(distributionRef) { |
54 |
- distributionRef = reference.WithDefaultTag(distributionRef) |
|
55 |
- fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", reference.DefaultTag) |
|
54 |
+ taggedRef := reference.EnsureTagged(distributionRef) |
|
55 |
+ fmt.Fprintf(dockerCli.Out(), "Using default tag: %s\n", taggedRef.Tag()) |
|
56 |
+ distributionRef = taggedRef |
|
56 | 57 |
} |
57 | 58 |
|
58 | 59 |
// Resolve the Repository name from fqn to RepositoryInfo |
... | ... |
@@ -71,7 +73,7 @@ func runPull(dockerCli *command.DockerCli, opts pullOptions) error { |
71 | 71 |
if command.IsTrusted() && !isCanonical { |
72 | 72 |
err = trustedPull(ctx, dockerCli, repoInfo, distributionRef, authConfig, requestPrivilege) |
73 | 73 |
} else { |
74 |
- err = imagePullPrivileged(ctx, dockerCli, authConfig, distributionRef.String(), requestPrivilege, opts.all) |
|
74 |
+ err = imagePullPrivileged(ctx, dockerCli, authConfig, reference.FamiliarString(distributionRef), requestPrivilege, opts.all) |
|
75 | 75 |
} |
76 | 76 |
if err != nil { |
77 | 77 |
if strings.Contains(err.Error(), "target is plugin") { |
... | ... |
@@ -3,10 +3,10 @@ package image |
3 | 3 |
import ( |
4 | 4 |
"golang.org/x/net/context" |
5 | 5 |
|
6 |
+ "github.com/docker/distribution/reference" |
|
6 | 7 |
"github.com/docker/docker/cli" |
7 | 8 |
"github.com/docker/docker/cli/command" |
8 | 9 |
"github.com/docker/docker/pkg/jsonmessage" |
9 |
- "github.com/docker/docker/reference" |
|
10 | 10 |
"github.com/docker/docker/registry" |
11 | 11 |
"github.com/spf13/cobra" |
12 | 12 |
) |
... | ... |
@@ -30,7 +30,7 @@ func NewPushCommand(dockerCli *command.DockerCli) *cobra.Command { |
30 | 30 |
} |
31 | 31 |
|
32 | 32 |
func runPush(dockerCli *command.DockerCli, remote string) error { |
33 |
- ref, err := reference.ParseNamed(remote) |
|
33 |
+ ref, err := reference.ParseNormalizedNamed(remote) |
|
34 | 34 |
if err != nil { |
35 | 35 |
return err |
36 | 36 |
} |
... | ... |
@@ -51,7 +51,7 @@ func runPush(dockerCli *command.DockerCli, remote string) error { |
51 | 51 |
return trustedPush(ctx, dockerCli, repoInfo, ref, authConfig, requestPrivilege) |
52 | 52 |
} |
53 | 53 |
|
54 |
- responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref.String(), requestPrivilege) |
|
54 |
+ responseBody, err := imagePushPrivileged(ctx, dockerCli, authConfig, ref, requestPrivilege) |
|
55 | 55 |
if err != nil { |
56 | 56 |
return err |
57 | 57 |
} |
... | ... |
@@ -10,11 +10,11 @@ import ( |
10 | 10 |
"sort" |
11 | 11 |
|
12 | 12 |
"github.com/Sirupsen/logrus" |
13 |
+ "github.com/docker/distribution/reference" |
|
13 | 14 |
"github.com/docker/docker/api/types" |
14 | 15 |
"github.com/docker/docker/cli/command" |
15 | 16 |
"github.com/docker/docker/cli/trust" |
16 | 17 |
"github.com/docker/docker/pkg/jsonmessage" |
17 |
- "github.com/docker/docker/reference" |
|
18 | 18 |
"github.com/docker/docker/registry" |
19 | 19 |
"github.com/docker/notary/client" |
20 | 20 |
"github.com/docker/notary/tuf/data" |
... | ... |
@@ -30,7 +30,7 @@ type target struct { |
30 | 30 |
|
31 | 31 |
// trustedPush handles content trust pushing of an image |
32 | 32 |
func trustedPush(ctx context.Context, cli *command.DockerCli, repoInfo *registry.RepositoryInfo, ref reference.Named, authConfig types.AuthConfig, requestPrivilege types.RequestPrivilegeFunc) error { |
33 |
- responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref.String(), requestPrivilege) |
|
33 |
+ responseBody, err := imagePushPrivileged(ctx, cli, authConfig, ref, requestPrivilege) |
|
34 | 34 |
if err != nil { |
35 | 35 |
return err |
36 | 36 |
} |
... | ... |
@@ -202,7 +202,7 @@ func addTargetToAllSignableRoles(repo *client.NotaryRepository, target *client.T |
202 | 202 |
} |
203 | 203 |
|
204 | 204 |
// imagePushPrivileged push the image |
205 |
-func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref string, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) { |
|
205 |
+func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig types.AuthConfig, ref reference.Named, requestPrivilege types.RequestPrivilegeFunc) (io.ReadCloser, error) { |
|
206 | 206 |
encodedAuth, err := command.EncodeAuthToBase64(authConfig) |
207 | 207 |
if err != nil { |
208 | 208 |
return nil, err |
... | ... |
@@ -212,7 +212,7 @@ func imagePushPrivileged(ctx context.Context, cli *command.DockerCli, authConfig |
212 | 212 |
PrivilegeFunc: requestPrivilege, |
213 | 213 |
} |
214 | 214 |
|
215 |
- return cli.Client().ImagePush(ctx, ref, options) |
|
215 |
+ return cli.Client().ImagePush(ctx, reference.FamiliarString(ref), options) |
|
216 | 216 |
} |
217 | 217 |
|
218 | 218 |
// trustedPull handles content trust pulling of an image |
... | ... |
@@ -229,12 +229,12 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry |
229 | 229 |
// List all targets |
230 | 230 |
targets, err := notaryRepo.ListTargets(trust.ReleasesRole, data.CanonicalTargetsRole) |
231 | 231 |
if err != nil { |
232 |
- return trust.NotaryError(repoInfo.FullName(), err) |
|
232 |
+ return trust.NotaryError(ref.Name(), err) |
|
233 | 233 |
} |
234 | 234 |
for _, tgt := range targets { |
235 | 235 |
t, err := convertTarget(tgt.Target) |
236 | 236 |
if err != nil { |
237 |
- fmt.Fprintf(cli.Out(), "Skipping target for %q\n", repoInfo.Name()) |
|
237 |
+ fmt.Fprintf(cli.Out(), "Skipping target for %q\n", reference.FamiliarName(ref)) |
|
238 | 238 |
continue |
239 | 239 |
} |
240 | 240 |
// Only list tags in the top level targets role or the releases delegation role - ignore |
... | ... |
@@ -245,17 +245,17 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry |
245 | 245 |
refs = append(refs, t) |
246 | 246 |
} |
247 | 247 |
if len(refs) == 0 { |
248 |
- return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trusted tags for %s", repoInfo.FullName())) |
|
248 |
+ return trust.NotaryError(ref.Name(), fmt.Errorf("No trusted tags for %s", ref.Name())) |
|
249 | 249 |
} |
250 | 250 |
} else { |
251 | 251 |
t, err := notaryRepo.GetTargetByName(tagged.Tag(), trust.ReleasesRole, data.CanonicalTargetsRole) |
252 | 252 |
if err != nil { |
253 |
- return trust.NotaryError(repoInfo.FullName(), err) |
|
253 |
+ return trust.NotaryError(ref.Name(), err) |
|
254 | 254 |
} |
255 | 255 |
// Only get the tag if it's in the top level targets role or the releases delegation role |
256 | 256 |
// ignore it if it's in any other delegation roles |
257 | 257 |
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole { |
258 |
- return trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", tagged.Tag())) |
|
258 |
+ return trust.NotaryError(ref.Name(), fmt.Errorf("No trust data for %s", tagged.Tag())) |
|
259 | 259 |
} |
260 | 260 |
|
261 | 261 |
logrus.Debugf("retrieving target for %s role\n", t.Role) |
... | ... |
@@ -272,24 +272,21 @@ func trustedPull(ctx context.Context, cli *command.DockerCli, repoInfo *registry |
272 | 272 |
if displayTag != "" { |
273 | 273 |
displayTag = ":" + displayTag |
274 | 274 |
} |
275 |
- fmt.Fprintf(cli.Out(), "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), repoInfo.Name(), displayTag, r.digest) |
|
275 |
+ fmt.Fprintf(cli.Out(), "Pull (%d of %d): %s%s@%s\n", i+1, len(refs), reference.FamiliarName(ref), displayTag, r.digest) |
|
276 | 276 |
|
277 |
- ref, err := reference.WithDigest(reference.TrimNamed(repoInfo), r.digest) |
|
277 |
+ trustedRef, err := reference.WithDigest(reference.TrimNamed(ref), r.digest) |
|
278 | 278 |
if err != nil { |
279 | 279 |
return err |
280 | 280 |
} |
281 |
- if err := imagePullPrivileged(ctx, cli, authConfig, ref.String(), requestPrivilege, false); err != nil { |
|
281 |
+ if err := imagePullPrivileged(ctx, cli, authConfig, reference.FamiliarString(trustedRef), requestPrivilege, false); err != nil { |
|
282 | 282 |
return err |
283 | 283 |
} |
284 | 284 |
|
285 |
- tagged, err := reference.WithTag(repoInfo, r.name) |
|
286 |
- if err != nil { |
|
287 |
- return err |
|
288 |
- } |
|
289 |
- trustedRef, err := reference.WithDigest(reference.TrimNamed(repoInfo), r.digest) |
|
285 |
+ tagged, err := reference.WithTag(reference.TrimNamed(ref), r.name) |
|
290 | 286 |
if err != nil { |
291 | 287 |
return err |
292 | 288 |
} |
289 |
+ |
|
293 | 290 |
if err := TagTrusted(ctx, cli, trustedRef, tagged); err != nil { |
294 | 291 |
return err |
295 | 292 |
} |
... | ... |
@@ -375,7 +372,11 @@ func convertTarget(t client.Target) (target, error) { |
375 | 375 |
|
376 | 376 |
// TagTrusted tags a trusted ref |
377 | 377 |
func TagTrusted(ctx context.Context, cli *command.DockerCli, trustedRef reference.Canonical, ref reference.NamedTagged) error { |
378 |
- fmt.Fprintf(cli.Out(), "Tagging %s as %s\n", trustedRef.String(), ref.String()) |
|
378 |
+ // Use familiar references when interacting with client and output |
|
379 |
+ familiarRef := reference.FamiliarString(ref) |
|
380 |
+ trustedFamiliarRef := reference.FamiliarString(trustedRef) |
|
381 |
+ |
|
382 |
+ fmt.Fprintf(cli.Out(), "Tagging %s as %s\n", trustedFamiliarRef, familiarRef) |
|
379 | 383 |
|
380 |
- return cli.Client().ImageTag(ctx, trustedRef.String(), ref.String()) |
|
384 |
+ return cli.Client().ImageTag(ctx, trustedFamiliarRef, familiarRef) |
|
381 | 385 |
} |
... | ... |
@@ -8,18 +8,18 @@ import ( |
8 | 8 |
"path/filepath" |
9 | 9 |
|
10 | 10 |
"github.com/Sirupsen/logrus" |
11 |
+ "github.com/docker/distribution/reference" |
|
11 | 12 |
"github.com/docker/docker/api/types" |
12 | 13 |
"github.com/docker/docker/cli" |
13 | 14 |
"github.com/docker/docker/cli/command" |
14 | 15 |
"github.com/docker/docker/pkg/archive" |
15 |
- "github.com/docker/docker/reference" |
|
16 | 16 |
"github.com/spf13/cobra" |
17 | 17 |
"golang.org/x/net/context" |
18 | 18 |
) |
19 | 19 |
|
20 | 20 |
// validateTag checks if the given repoName can be resolved. |
21 | 21 |
func validateTag(rawRepo string) error { |
22 |
- _, err := reference.ParseNamed(rawRepo) |
|
22 |
+ _, err := reference.ParseNormalizedNamed(rawRepo) |
|
23 | 23 |
|
24 | 24 |
return err |
25 | 25 |
} |
... | ... |
@@ -6,14 +6,13 @@ import ( |
6 | 6 |
"fmt" |
7 | 7 |
"strings" |
8 | 8 |
|
9 |
- distreference "github.com/docker/distribution/reference" |
|
9 |
+ "github.com/docker/distribution/reference" |
|
10 | 10 |
"github.com/docker/docker/api/types" |
11 | 11 |
registrytypes "github.com/docker/docker/api/types/registry" |
12 | 12 |
"github.com/docker/docker/cli" |
13 | 13 |
"github.com/docker/docker/cli/command" |
14 | 14 |
"github.com/docker/docker/cli/command/image" |
15 | 15 |
"github.com/docker/docker/pkg/jsonmessage" |
16 |
- "github.com/docker/docker/reference" |
|
17 | 16 |
"github.com/docker/docker/registry" |
18 | 17 |
"github.com/spf13/cobra" |
19 | 18 |
"golang.org/x/net/context" |
... | ... |
@@ -52,8 +51,8 @@ func newInstallCommand(dockerCli *command.DockerCli) *cobra.Command { |
52 | 52 |
return cmd |
53 | 53 |
} |
54 | 54 |
|
55 |
-func getRepoIndexFromUnnormalizedRef(ref distreference.Named) (*registrytypes.IndexInfo, error) { |
|
56 |
- named, err := reference.ParseNamed(ref.Name()) |
|
55 |
+func getRepoIndexFromUnnormalizedRef(ref reference.Named) (*registrytypes.IndexInfo, error) { |
|
56 |
+ named, err := reference.ParseNormalizedNamed(ref.Name()) |
|
57 | 57 |
if err != nil { |
58 | 58 |
return nil, err |
59 | 59 |
} |
... | ... |
@@ -85,71 +84,60 @@ func newRegistryService() registry.Service { |
85 | 85 |
} |
86 | 86 |
|
87 | 87 |
func runInstall(dockerCli *command.DockerCli, opts pluginOptions) error { |
88 |
- // Parse name using distribution reference package to support name |
|
89 |
- // containing both tag and digest. Names with both tag and digest |
|
90 |
- // will be treated by the daemon as a pull by digest with |
|
91 |
- // an alias for the tag (if no alias is provided). |
|
92 |
- ref, err := distreference.ParseNamed(opts.name) |
|
88 |
+ // Names with both tag and digest will be treated by the daemon |
|
89 |
+ // as a pull by digest with an alias for the tag |
|
90 |
+ // (if no alias is provided). |
|
91 |
+ ref, err := reference.ParseNormalizedNamed(opts.name) |
|
93 | 92 |
if err != nil { |
94 | 93 |
return err |
95 | 94 |
} |
96 | 95 |
|
97 | 96 |
alias := "" |
98 | 97 |
if opts.alias != "" { |
99 |
- aref, err := reference.ParseNamed(opts.alias) |
|
98 |
+ aref, err := reference.ParseNormalizedNamed(opts.alias) |
|
100 | 99 |
if err != nil { |
101 | 100 |
return err |
102 | 101 |
} |
103 |
- aref = reference.WithDefaultTag(aref) |
|
104 |
- if _, ok := aref.(reference.NamedTagged); !ok { |
|
102 |
+ if _, ok := aref.(reference.Canonical); ok { |
|
105 | 103 |
return fmt.Errorf("invalid name: %s", opts.alias) |
106 | 104 |
} |
107 |
- alias = aref.String() |
|
105 |
+ alias = reference.FamiliarString(reference.EnsureTagged(aref)) |
|
108 | 106 |
} |
109 | 107 |
ctx := context.Background() |
110 | 108 |
|
111 |
- index, err := getRepoIndexFromUnnormalizedRef(ref) |
|
109 |
+ repoInfo, err := registry.ParseRepositoryInfo(ref) |
|
112 | 110 |
if err != nil { |
113 | 111 |
return err |
114 | 112 |
} |
115 | 113 |
|
116 | 114 |
remote := ref.String() |
117 | 115 |
|
118 |
- _, isCanonical := ref.(distreference.Canonical) |
|
116 |
+ _, isCanonical := ref.(reference.Canonical) |
|
119 | 117 |
if command.IsTrusted() && !isCanonical { |
120 | 118 |
if alias == "" { |
121 |
- alias = ref.String() |
|
119 |
+ alias = reference.FamiliarString(ref) |
|
122 | 120 |
} |
123 |
- var nt reference.NamedTagged |
|
124 |
- named, err := reference.ParseNamed(ref.Name()) |
|
125 |
- if err != nil { |
|
126 |
- return err |
|
127 |
- } |
|
128 |
- if tagged, ok := ref.(distreference.Tagged); ok { |
|
129 |
- nt, err = reference.WithTag(named, tagged.Tag()) |
|
130 |
- if err != nil { |
|
131 |
- return err |
|
132 |
- } |
|
133 |
- } else { |
|
134 |
- named = reference.WithDefaultTag(named) |
|
135 |
- nt = named.(reference.NamedTagged) |
|
121 |
+ |
|
122 |
+ nt, ok := ref.(reference.NamedTagged) |
|
123 |
+ if !ok { |
|
124 |
+ nt = reference.EnsureTagged(ref) |
|
136 | 125 |
} |
137 | 126 |
|
138 | 127 |
trusted, err := image.TrustedReference(ctx, dockerCli, nt, newRegistryService()) |
139 | 128 |
if err != nil { |
140 | 129 |
return err |
141 | 130 |
} |
142 |
- remote = trusted.String() |
|
131 |
+ remote = reference.FamiliarString(trusted) |
|
143 | 132 |
} |
144 | 133 |
|
145 |
- authConfig := command.ResolveAuthConfig(ctx, dockerCli, index) |
|
134 |
+ authConfig := command.ResolveAuthConfig(ctx, dockerCli, repoInfo.Index) |
|
146 | 135 |
|
147 | 136 |
encodedAuth, err := command.EncodeAuthToBase64(authConfig) |
148 | 137 |
if err != nil { |
149 | 138 |
return err |
150 | 139 |
} |
151 | 140 |
|
152 |
- registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, index, "plugin install") |
|
141 |
+ registryAuthFunc := command.RegistryAuthenticationPrivilegedFunc(dockerCli, repoInfo.Index, "plugin install") |
|
153 | 142 |
|
154 | 143 |
options := types.PluginInstallOptions{ |
155 | 144 |
RegistryAuth: encodedAuth, |
... | ... |
@@ -5,11 +5,11 @@ import ( |
5 | 5 |
|
6 | 6 |
"golang.org/x/net/context" |
7 | 7 |
|
8 |
+ "github.com/docker/distribution/reference" |
|
8 | 9 |
"github.com/docker/docker/cli" |
9 | 10 |
"github.com/docker/docker/cli/command" |
10 | 11 |
"github.com/docker/docker/cli/command/image" |
11 | 12 |
"github.com/docker/docker/pkg/jsonmessage" |
12 |
- "github.com/docker/docker/reference" |
|
13 | 13 |
"github.com/docker/docker/registry" |
14 | 14 |
"github.com/spf13/cobra" |
15 | 15 |
) |
... | ... |
@@ -32,16 +32,17 @@ func newPushCommand(dockerCli *command.DockerCli) *cobra.Command { |
32 | 32 |
} |
33 | 33 |
|
34 | 34 |
func runPush(dockerCli *command.DockerCli, name string) error { |
35 |
- named, err := reference.ParseNamed(name) // FIXME: validate |
|
35 |
+ named, err := reference.ParseNormalizedNamed(name) |
|
36 | 36 |
if err != nil { |
37 | 37 |
return err |
38 | 38 |
} |
39 |
- if reference.IsNameOnly(named) { |
|
40 |
- named = reference.WithDefaultTag(named) |
|
39 |
+ if _, ok := named.(reference.Canonical); ok { |
|
40 |
+ return fmt.Errorf("invalid name: %s", name) |
|
41 | 41 |
} |
42 |
- ref, ok := named.(reference.NamedTagged) |
|
42 |
+ |
|
43 |
+ taggedRef, ok := named.(reference.NamedTagged) |
|
43 | 44 |
if !ok { |
44 |
- return fmt.Errorf("invalid name: %s", named.String()) |
|
45 |
+ taggedRef = reference.EnsureTagged(named) |
|
45 | 46 |
} |
46 | 47 |
|
47 | 48 |
ctx := context.Background() |
... | ... |
@@ -56,7 +57,8 @@ func runPush(dockerCli *command.DockerCli, name string) error { |
56 | 56 |
if err != nil { |
57 | 57 |
return err |
58 | 58 |
} |
59 |
- responseBody, err := dockerCli.Client().PluginPush(ctx, ref.String(), encodedAuth) |
|
59 |
+ |
|
60 |
+ responseBody, err := dockerCli.Client().PluginPush(ctx, reference.FamiliarString(taggedRef), encodedAuth) |
|
60 | 61 |
if err != nil { |
61 | 62 |
return err |
62 | 63 |
} |
... | ... |
@@ -12,10 +12,10 @@ import ( |
12 | 12 |
|
13 | 13 |
"golang.org/x/net/context" |
14 | 14 |
|
15 |
+ "github.com/docker/distribution/reference" |
|
15 | 16 |
"github.com/docker/docker/api/types" |
16 | 17 |
registrytypes "github.com/docker/docker/api/types/registry" |
17 | 18 |
"github.com/docker/docker/pkg/term" |
18 |
- "github.com/docker/docker/reference" |
|
19 | 19 |
"github.com/docker/docker/registry" |
20 | 20 |
) |
21 | 21 |
|
... | ... |
@@ -174,7 +174,7 @@ func RetrieveAuthTokenFromImage(ctx context.Context, cli *DockerCli, image strin |
174 | 174 |
|
175 | 175 |
// resolveAuthConfigFromImage retrieves that AuthConfig using the image string |
176 | 176 |
func resolveAuthConfigFromImage(ctx context.Context, cli *DockerCli, image string) (types.AuthConfig, error) { |
177 |
- registryRef, err := reference.ParseNamed(image) |
|
177 |
+ registryRef, err := reference.ParseNormalizedNamed(image) |
|
178 | 178 |
if err != nil { |
179 | 179 |
return types.AuthConfig{}, err |
180 | 180 |
} |
... | ... |
@@ -5,11 +5,10 @@ import ( |
5 | 5 |
"fmt" |
6 | 6 |
|
7 | 7 |
"github.com/Sirupsen/logrus" |
8 |
- distreference "github.com/docker/distribution/reference" |
|
8 |
+ "github.com/docker/distribution/reference" |
|
9 | 9 |
"github.com/docker/docker/api/types/swarm" |
10 | 10 |
"github.com/docker/docker/cli/command" |
11 | 11 |
"github.com/docker/docker/cli/trust" |
12 |
- "github.com/docker/docker/reference" |
|
13 | 12 |
"github.com/docker/docker/registry" |
14 | 13 |
"github.com/docker/notary/tuf/data" |
15 | 14 |
"github.com/opencontainers/go-digest" |
... | ... |
@@ -24,41 +23,34 @@ func resolveServiceImageDigest(dockerCli *command.DockerCli, service *swarm.Serv |
24 | 24 |
return nil |
25 | 25 |
} |
26 | 26 |
|
27 |
- image := service.TaskTemplate.ContainerSpec.Image |
|
28 |
- |
|
29 |
- // We only attempt to resolve the digest if the reference |
|
30 |
- // could be parsed as a digest reference. Specifying an image ID |
|
31 |
- // is valid but not resolvable. There is no warning message for |
|
32 |
- // an image ID because it's valid to use one. |
|
33 |
- if _, err := digest.Parse(image); err == nil { |
|
34 |
- return nil |
|
35 |
- } |
|
36 |
- |
|
37 |
- ref, err := reference.ParseNamed(image) |
|
27 |
+ ref, err := reference.ParseAnyReference(service.TaskTemplate.ContainerSpec.Image) |
|
38 | 28 |
if err != nil { |
39 |
- return fmt.Errorf("Could not parse image reference %s", service.TaskTemplate.ContainerSpec.Image) |
|
29 |
+ return errors.Wrapf(err, "invalid reference %s", service.TaskTemplate.ContainerSpec.Image) |
|
40 | 30 |
} |
41 |
- if _, ok := ref.(reference.Canonical); !ok { |
|
42 |
- ref = reference.WithDefaultTag(ref) |
|
43 | 31 |
|
44 |
- taggedRef, ok := ref.(reference.NamedTagged) |
|
32 |
+ // If reference does not have digest (is not canonical nor image id) |
|
33 |
+ if _, ok := ref.(reference.Digested); !ok { |
|
34 |
+ namedRef, ok := ref.(reference.Named) |
|
45 | 35 |
if !ok { |
46 |
- // This should never happen because a reference either |
|
47 |
- // has a digest, or WithDefaultTag would give it a tag. |
|
48 |
- return errors.New("Failed to resolve image digest using content trust: reference is missing a tag") |
|
36 |
+ return errors.New("failed to resolve image digest using content trust: reference is not named") |
|
37 |
+ |
|
49 | 38 |
} |
50 | 39 |
|
40 |
+ taggedRef := reference.EnsureTagged(namedRef) |
|
41 |
+ |
|
51 | 42 |
resolvedImage, err := trustedResolveDigest(context.Background(), dockerCli, taggedRef) |
52 | 43 |
if err != nil { |
53 |
- return fmt.Errorf("Failed to resolve image digest using content trust: %v", err) |
|
44 |
+ return errors.Wrap(err, "failed to resolve image digest using content trust") |
|
54 | 45 |
} |
55 |
- logrus.Debugf("resolved image tag to %s using content trust", resolvedImage.String()) |
|
56 |
- service.TaskTemplate.ContainerSpec.Image = resolvedImage.String() |
|
46 |
+ resolvedFamiliar := reference.FamiliarString(resolvedImage) |
|
47 |
+ logrus.Debugf("resolved image tag to %s using content trust", resolvedFamiliar) |
|
48 |
+ service.TaskTemplate.ContainerSpec.Image = resolvedFamiliar |
|
57 | 49 |
} |
50 |
+ |
|
58 | 51 |
return nil |
59 | 52 |
} |
60 | 53 |
|
61 |
-func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (distreference.Canonical, error) { |
|
54 |
+func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref reference.NamedTagged) (reference.Canonical, error) { |
|
62 | 55 |
repoInfo, err := registry.ParseRepositoryInfo(ref) |
63 | 56 |
if err != nil { |
64 | 57 |
return nil, err |
... | ... |
@@ -78,7 +70,7 @@ func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref refer |
78 | 78 |
// Only get the tag if it's in the top level targets role or the releases delegation role |
79 | 79 |
// ignore it if it's in any other delegation roles |
80 | 80 |
if t.Role != trust.ReleasesRole && t.Role != data.CanonicalTargetsRole { |
81 |
- return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", ref.String())) |
|
81 |
+ return nil, trust.NotaryError(repoInfo.FullName(), fmt.Errorf("No trust data for %s", reference.FamiliarString(ref))) |
|
82 | 82 |
} |
83 | 83 |
|
84 | 84 |
logrus.Debugf("retrieving target for %s role\n", t.Role) |
... | ... |
@@ -89,8 +81,6 @@ func trustedResolveDigest(ctx context.Context, cli *command.DockerCli, ref refer |
89 | 89 |
|
90 | 90 |
dgst := digest.NewDigestFromHex("sha256", hex.EncodeToString(h)) |
91 | 91 |
|
92 |
- // Using distribution reference package to make sure that adding a |
|
93 |
- // digest does not erase the tag. When the two reference packages |
|
94 |
- // are unified, this will no longer be an issue. |
|
95 |
- return distreference.WithDigest(ref, dgst) |
|
92 |
+ // Allow returning canonical reference with tag |
|
93 |
+ return reference.WithDigest(ref, dgst) |
|
96 | 94 |
} |
... | ... |
@@ -202,7 +202,7 @@ func TestLayerAlreadyExists(t *testing.T) { |
202 | 202 |
checkOtherRepositories: true, |
203 | 203 |
metadata: []metadata.V2Metadata{ |
204 | 204 |
{Digest: digest.Digest("apple"), SourceRepository: "docker.io/library/hello-world"}, |
205 |
- {Digest: digest.Digest("orange"), SourceRepository: "docker.io/library/busybox/subapp"}, |
|
205 |
+ {Digest: digest.Digest("orange"), SourceRepository: "docker.io/busybox/subapp"}, |
|
206 | 206 |
{Digest: digest.Digest("pear"), SourceRepository: "docker.io/busybox"}, |
207 | 207 |
{Digest: digest.Digest("plum"), SourceRepository: "busybox"}, |
208 | 208 |
{Digest: digest.Digest("banana"), SourceRepository: "127.0.0.1/busybox"}, |
... | ... |
@@ -3082,7 +3082,7 @@ func (s *DockerSuite) TestBuildInvalidTag(c *check.C) { |
3082 | 3082 |
name := "abcd:" + stringutils.GenerateRandomAlphaOnlyString(200) |
3083 | 3083 |
buildImage(name, withDockerfile("FROM "+minimalBaseImage()+"\nMAINTAINER quux\n")).Assert(c, icmd.Expected{ |
3084 | 3084 |
ExitCode: 125, |
3085 |
- Err: "Error parsing reference", |
|
3085 |
+ Err: "invalid reference format", |
|
3086 | 3086 |
}) |
3087 | 3087 |
} |
3088 | 3088 |
|
... | ... |
@@ -274,7 +274,7 @@ func (s *DockerSuite) TestCreateByImageID(c *check.C) { |
274 | 274 |
c.Fatalf("expected non-zero exit code; received %d", exit) |
275 | 275 |
} |
276 | 276 |
|
277 |
- if expected := "Error parsing reference"; !strings.Contains(out, expected) { |
|
277 |
+ if expected := "invalid reference format"; !strings.Contains(out, expected) { |
|
278 | 278 |
c.Fatalf(`Expected %q in output; got: %s`, expected, out) |
279 | 279 |
} |
280 | 280 |
|
... | ... |
@@ -3862,8 +3862,8 @@ func (s *DockerSuite) TestRunInvalidReference(c *check.C) { |
3862 | 3862 |
c.Fatalf("expected non-zero exist code; received %d", exit) |
3863 | 3863 |
} |
3864 | 3864 |
|
3865 |
- if !strings.Contains(out, "Error parsing reference") { |
|
3866 |
- c.Fatalf(`Expected "Error parsing reference" in output; got: %s`, out) |
|
3865 |
+ if !strings.Contains(out, "invalid reference format") { |
|
3866 |
+ c.Fatalf(`Expected "invalid reference format" in output; got: %s`, out) |
|
3867 | 3867 |
} |
3868 | 3868 |
} |
3869 | 3869 |
|
... | ... |
@@ -12,6 +12,7 @@ import ( |
12 | 12 |
"sync" |
13 | 13 |
|
14 | 14 |
"github.com/Sirupsen/logrus" |
15 |
+ "github.com/docker/distribution/reference" |
|
15 | 16 |
"github.com/docker/docker/api/types" |
16 | 17 |
"github.com/docker/docker/image" |
17 | 18 |
"github.com/docker/docker/layer" |
... | ... |
@@ -19,7 +20,6 @@ import ( |
19 | 19 |
"github.com/docker/docker/pkg/ioutils" |
20 | 20 |
"github.com/docker/docker/pkg/mount" |
21 | 21 |
"github.com/docker/docker/plugin/v2" |
22 |
- "github.com/docker/docker/reference" |
|
23 | 22 |
"github.com/docker/docker/registry" |
24 | 23 |
"github.com/opencontainers/go-digest" |
25 | 24 |
"github.com/pkg/errors" |
... | ... |
@@ -1,13 +1,12 @@ |
1 | 1 |
package reference |
2 | 2 |
|
3 | 3 |
import ( |
4 |
- "errors" |
|
5 | 4 |
"fmt" |
6 |
- "strings" |
|
7 | 5 |
|
8 | 6 |
distreference "github.com/docker/distribution/reference" |
9 | 7 |
"github.com/docker/docker/pkg/stringid" |
10 | 8 |
"github.com/opencontainers/go-digest" |
9 |
+ "github.com/pkg/errors" |
|
11 | 10 |
) |
12 | 11 |
|
13 | 12 |
const ( |
... | ... |
@@ -53,21 +52,31 @@ type Canonical interface { |
53 | 53 |
// returned. |
54 | 54 |
// If an error was encountered it is returned, along with a nil Reference. |
55 | 55 |
func ParseNamed(s string) (Named, error) { |
56 |
- named, err := distreference.ParseNamed(s) |
|
56 |
+ named, err := distreference.ParseNormalizedNamed(s) |
|
57 | 57 |
if err != nil { |
58 |
- return nil, fmt.Errorf("Error parsing reference: %q is not a valid repository/tag: %s", s, err) |
|
58 |
+ return nil, errors.Wrapf(err, "failed to parse reference %q", s) |
|
59 | 59 |
} |
60 |
- r, err := WithName(named.Name()) |
|
61 |
- if err != nil { |
|
60 |
+ if err := validateName(distreference.FamiliarName(named)); err != nil { |
|
62 | 61 |
return nil, err |
63 | 62 |
} |
63 |
+ |
|
64 |
+ // Ensure returned reference cannot have tag and digest |
|
64 | 65 |
if canonical, isCanonical := named.(distreference.Canonical); isCanonical { |
65 |
- return WithDigest(r, canonical.Digest()) |
|
66 |
+ r, err := distreference.WithDigest(distreference.TrimNamed(named), canonical.Digest()) |
|
67 |
+ if err != nil { |
|
68 |
+ return nil, err |
|
69 |
+ } |
|
70 |
+ return &canonicalRef{namedRef{r}}, nil |
|
66 | 71 |
} |
67 | 72 |
if tagged, isTagged := named.(distreference.NamedTagged); isTagged { |
68 |
- return WithTag(r, tagged.Tag()) |
|
73 |
+ r, err := distreference.WithTag(distreference.TrimNamed(named), tagged.Tag()) |
|
74 |
+ if err != nil { |
|
75 |
+ return nil, err |
|
76 |
+ } |
|
77 |
+ return &taggedRef{namedRef{r}}, nil |
|
69 | 78 |
} |
70 |
- return r, nil |
|
79 |
+ |
|
80 |
+ return &namedRef{named}, nil |
|
71 | 81 |
} |
72 | 82 |
|
73 | 83 |
// TrimNamed removes any tag or digest from the named reference |
... | ... |
@@ -78,16 +87,15 @@ func TrimNamed(ref Named) Named { |
78 | 78 |
// WithName returns a named object representing the given string. If the input |
79 | 79 |
// is invalid ErrReferenceInvalidFormat will be returned. |
80 | 80 |
func WithName(name string) (Named, error) { |
81 |
- name, err := normalize(name) |
|
81 |
+ r, err := distreference.ParseNormalizedNamed(name) |
|
82 | 82 |
if err != nil { |
83 | 83 |
return nil, err |
84 | 84 |
} |
85 |
- if err := validateName(name); err != nil { |
|
85 |
+ if err := validateName(distreference.FamiliarName(r)); err != nil { |
|
86 | 86 |
return nil, err |
87 | 87 |
} |
88 |
- r, err := distreference.WithName(name) |
|
89 |
- if err != nil { |
|
90 |
- return nil, err |
|
88 |
+ if !distreference.IsNameOnly(r) { |
|
89 |
+ return nil, distreference.ErrReferenceInvalidFormat |
|
91 | 90 |
} |
92 | 91 |
return &namedRef{r}, nil |
93 | 92 |
} |
... | ... |
@@ -122,17 +130,22 @@ type canonicalRef struct { |
122 | 122 |
namedRef |
123 | 123 |
} |
124 | 124 |
|
125 |
+func (r *namedRef) Name() string { |
|
126 |
+ return distreference.FamiliarName(r.Named) |
|
127 |
+} |
|
128 |
+ |
|
129 |
+func (r *namedRef) String() string { |
|
130 |
+ return distreference.FamiliarString(r.Named) |
|
131 |
+} |
|
132 |
+ |
|
125 | 133 |
func (r *namedRef) FullName() string { |
126 |
- hostname, remoteName := splitHostname(r.Name()) |
|
127 |
- return hostname + "/" + remoteName |
|
134 |
+ return r.Named.Name() |
|
128 | 135 |
} |
129 | 136 |
func (r *namedRef) Hostname() string { |
130 |
- hostname, _ := splitHostname(r.Name()) |
|
131 |
- return hostname |
|
137 |
+ return distreference.Domain(r.Named) |
|
132 | 138 |
} |
133 | 139 |
func (r *namedRef) RemoteName() string { |
134 |
- _, remoteName := splitHostname(r.Name()) |
|
135 |
- return remoteName |
|
140 |
+ return distreference.Path(r.Named) |
|
136 | 141 |
} |
137 | 142 |
func (r *taggedRef) Tag() string { |
138 | 143 |
return r.namedRef.Named.(distreference.NamedTagged).Tag() |
... | ... |
@@ -173,41 +186,6 @@ func ParseIDOrReference(idOrRef string) (digest.Digest, Named, error) { |
173 | 173 |
return "", ref, err |
174 | 174 |
} |
175 | 175 |
|
176 |
-// splitHostname splits a repository name to hostname and remotename string. |
|
177 |
-// If no valid hostname is found, the default hostname is used. Repository name |
|
178 |
-// needs to be already validated before. |
|
179 |
-func splitHostname(name string) (hostname, remoteName string) { |
|
180 |
- i := strings.IndexRune(name, '/') |
|
181 |
- if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { |
|
182 |
- hostname, remoteName = DefaultHostname, name |
|
183 |
- } else { |
|
184 |
- hostname, remoteName = name[:i], name[i+1:] |
|
185 |
- } |
|
186 |
- if hostname == LegacyDefaultHostname { |
|
187 |
- hostname = DefaultHostname |
|
188 |
- } |
|
189 |
- if hostname == DefaultHostname && !strings.ContainsRune(remoteName, '/') { |
|
190 |
- remoteName = DefaultRepoPrefix + remoteName |
|
191 |
- } |
|
192 |
- return |
|
193 |
-} |
|
194 |
- |
|
195 |
-// normalize returns a repository name in its normalized form, meaning it |
|
196 |
-// will not contain default hostname nor library/ prefix for official images. |
|
197 |
-func normalize(name string) (string, error) { |
|
198 |
- host, remoteName := splitHostname(name) |
|
199 |
- if strings.ToLower(remoteName) != remoteName { |
|
200 |
- return "", errors.New("invalid reference format: repository name must be lowercase") |
|
201 |
- } |
|
202 |
- if host == DefaultHostname { |
|
203 |
- if strings.HasPrefix(remoteName, DefaultRepoPrefix) { |
|
204 |
- return strings.TrimPrefix(remoteName, DefaultRepoPrefix), nil |
|
205 |
- } |
|
206 |
- return remoteName, nil |
|
207 |
- } |
|
208 |
- return name, nil |
|
209 |
-} |
|
210 |
- |
|
211 | 176 |
func validateName(name string) error { |
212 | 177 |
if err := stringid.ValidateID(name); err == nil { |
213 | 178 |
return fmt.Errorf("Invalid repository name (%s), cannot specify 64-byte hexadecimal strings", name) |
... | ... |
@@ -1,16 +1,17 @@ |
1 | 1 |
package registry |
2 | 2 |
|
3 | 3 |
import ( |
4 |
- "errors" |
|
5 | 4 |
"fmt" |
6 | 5 |
"net" |
7 | 6 |
"net/url" |
8 | 7 |
"strings" |
9 | 8 |
|
10 | 9 |
"github.com/Sirupsen/logrus" |
10 |
+ "github.com/docker/distribution/reference" |
|
11 | 11 |
registrytypes "github.com/docker/docker/api/types/registry" |
12 | 12 |
"github.com/docker/docker/opts" |
13 |
- "github.com/docker/docker/reference" |
|
13 |
+ forkedref "github.com/docker/docker/reference" |
|
14 |
+ "github.com/pkg/errors" |
|
14 | 15 |
"github.com/spf13/pflag" |
15 | 16 |
) |
16 | 17 |
|
... | ... |
@@ -270,8 +271,8 @@ func ValidateMirror(val string) (string, error) { |
270 | 270 |
|
271 | 271 |
// ValidateIndexName validates an index name. |
272 | 272 |
func ValidateIndexName(val string) (string, error) { |
273 |
- if val == reference.LegacyDefaultHostname { |
|
274 |
- val = reference.DefaultHostname |
|
273 |
+ if val == forkedref.LegacyDefaultHostname { |
|
274 |
+ val = forkedref.DefaultHostname |
|
275 | 275 |
} |
276 | 276 |
if strings.HasPrefix(val, "-") || strings.HasSuffix(val, "-") { |
277 | 277 |
return "", fmt.Errorf("Invalid index name (%s). Cannot begin or end with a hyphen.", val) |
... | ... |
@@ -321,13 +322,19 @@ func GetAuthConfigKey(index *registrytypes.IndexInfo) string { |
321 | 321 |
|
322 | 322 |
// newRepositoryInfo validates and breaks down a repository name into a RepositoryInfo |
323 | 323 |
func newRepositoryInfo(config *serviceConfig, name reference.Named) (*RepositoryInfo, error) { |
324 |
- index, err := newIndexInfo(config, name.Hostname()) |
|
324 |
+ index, err := newIndexInfo(config, reference.Domain(name)) |
|
325 |
+ if err != nil { |
|
326 |
+ return nil, err |
|
327 |
+ } |
|
328 |
+ official := !strings.ContainsRune(reference.FamiliarName(name), '/') |
|
329 |
+ |
|
330 |
+ // TODO: remove used of forked reference package |
|
331 |
+ nameref, err := forkedref.ParseNamed(name.String()) |
|
325 | 332 |
if err != nil { |
326 | 333 |
return nil, err |
327 | 334 |
} |
328 |
- official := !strings.ContainsRune(name.Name(), '/') |
|
329 | 335 |
return &RepositoryInfo{ |
330 |
- Named: name, |
|
336 |
+ Named: nameref, |
|
331 | 337 |
Index: index, |
332 | 338 |
Official: official, |
333 | 339 |
}, nil |
... | ... |
@@ -10,10 +10,11 @@ import ( |
10 | 10 |
"strings" |
11 | 11 |
"testing" |
12 | 12 |
|
13 |
+ "github.com/docker/distribution/reference" |
|
13 | 14 |
"github.com/docker/distribution/registry/client/transport" |
14 | 15 |
"github.com/docker/docker/api/types" |
15 | 16 |
registrytypes "github.com/docker/docker/api/types/registry" |
16 |
- "github.com/docker/docker/reference" |
|
17 |
+ forkedref "github.com/docker/docker/reference" |
|
17 | 18 |
) |
18 | 19 |
|
19 | 20 |
var ( |
... | ... |
@@ -201,7 +202,7 @@ func TestGetRemoteImageLayer(t *testing.T) { |
201 | 201 |
|
202 | 202 |
func TestGetRemoteTag(t *testing.T) { |
203 | 203 |
r := spawnTestRegistrySession(t) |
204 |
- repoRef, err := reference.ParseNamed(REPO) |
|
204 |
+ repoRef, err := forkedref.ParseNamed(REPO) |
|
205 | 205 |
if err != nil { |
206 | 206 |
t.Fatal(err) |
207 | 207 |
} |
... | ... |
@@ -211,7 +212,7 @@ func TestGetRemoteTag(t *testing.T) { |
211 | 211 |
} |
212 | 212 |
assertEqual(t, tag, imageID, "Expected tag test to map to "+imageID) |
213 | 213 |
|
214 |
- bazRef, err := reference.ParseNamed("foo42/baz") |
|
214 |
+ bazRef, err := forkedref.ParseNamed("foo42/baz") |
|
215 | 215 |
if err != nil { |
216 | 216 |
t.Fatal(err) |
217 | 217 |
} |
... | ... |
@@ -223,7 +224,7 @@ func TestGetRemoteTag(t *testing.T) { |
223 | 223 |
|
224 | 224 |
func TestGetRemoteTags(t *testing.T) { |
225 | 225 |
r := spawnTestRegistrySession(t) |
226 |
- repoRef, err := reference.ParseNamed(REPO) |
|
226 |
+ repoRef, err := forkedref.ParseNamed(REPO) |
|
227 | 227 |
if err != nil { |
228 | 228 |
t.Fatal(err) |
229 | 229 |
} |
... | ... |
@@ -235,7 +236,7 @@ func TestGetRemoteTags(t *testing.T) { |
235 | 235 |
assertEqual(t, tags["latest"], imageID, "Expected tag latest to map to "+imageID) |
236 | 236 |
assertEqual(t, tags["test"], imageID, "Expected tag test to map to "+imageID) |
237 | 237 |
|
238 |
- bazRef, err := reference.ParseNamed("foo42/baz") |
|
238 |
+ bazRef, err := forkedref.ParseNamed("foo42/baz") |
|
239 | 239 |
if err != nil { |
240 | 240 |
t.Fatal(err) |
241 | 241 |
} |
... | ... |
@@ -252,7 +253,7 @@ func TestGetRepositoryData(t *testing.T) { |
252 | 252 |
t.Fatal(err) |
253 | 253 |
} |
254 | 254 |
host := "http://" + parsedURL.Host + "/v1/" |
255 |
- repoRef, err := reference.ParseNamed(REPO) |
|
255 |
+ repoRef, err := forkedref.ParseNamed(REPO) |
|
256 | 256 |
if err != nil { |
257 | 257 |
t.Fatal(err) |
258 | 258 |
} |
... | ... |
@@ -505,7 +506,7 @@ func TestParseRepositoryInfo(t *testing.T) { |
505 | 505 |
} |
506 | 506 |
|
507 | 507 |
for reposName, expectedRepoInfo := range expectedRepoInfos { |
508 |
- named, err := reference.WithName(reposName) |
|
508 |
+ named, err := reference.ParseNormalizedNamed(reposName) |
|
509 | 509 |
if err != nil { |
510 | 510 |
t.Error(err) |
511 | 511 |
} |
... | ... |
@@ -669,7 +670,7 @@ func TestMirrorEndpointLookup(t *testing.T) { |
669 | 669 |
if err != nil { |
670 | 670 |
t.Error(err) |
671 | 671 |
} |
672 |
- pushAPIEndpoints, err := s.LookupPushEndpoints(imageName.Hostname()) |
|
672 |
+ pushAPIEndpoints, err := s.LookupPushEndpoints(reference.Domain(imageName)) |
|
673 | 673 |
if err != nil { |
674 | 674 |
t.Fatal(err) |
675 | 675 |
} |
... | ... |
@@ -677,7 +678,7 @@ func TestMirrorEndpointLookup(t *testing.T) { |
677 | 677 |
t.Fatal("Push endpoint should not contain mirror") |
678 | 678 |
} |
679 | 679 |
|
680 |
- pullAPIEndpoints, err := s.LookupPullEndpoints(imageName.Hostname()) |
|
680 |
+ pullAPIEndpoints, err := s.LookupPullEndpoints(reference.Domain(imageName)) |
|
681 | 681 |
if err != nil { |
682 | 682 |
t.Fatal(err) |
683 | 683 |
} |
... | ... |
@@ -688,7 +689,7 @@ func TestMirrorEndpointLookup(t *testing.T) { |
688 | 688 |
|
689 | 689 |
func TestPushRegistryTag(t *testing.T) { |
690 | 690 |
r := spawnTestRegistrySession(t) |
691 |
- repoRef, err := reference.ParseNamed(REPO) |
|
691 |
+ repoRef, err := forkedref.ParseNamed(REPO) |
|
692 | 692 |
if err != nil { |
693 | 693 |
t.Fatal(err) |
694 | 694 |
} |
... | ... |
@@ -710,7 +711,7 @@ func TestPushImageJSONIndex(t *testing.T) { |
710 | 710 |
Checksum: "sha256:bea7bf2e4bacd479344b737328db47b18880d09096e6674165533aa994f5e9f2", |
711 | 711 |
}, |
712 | 712 |
} |
713 |
- repoRef, err := reference.ParseNamed(REPO) |
|
713 |
+ repoRef, err := forkedref.ParseNamed(REPO) |
|
714 | 714 |
if err != nil { |
715 | 715 |
t.Fatal(err) |
716 | 716 |
} |
... | ... |
@@ -11,10 +11,10 @@ import ( |
11 | 11 |
"golang.org/x/net/context" |
12 | 12 |
|
13 | 13 |
"github.com/Sirupsen/logrus" |
14 |
+ "github.com/docker/distribution/reference" |
|
14 | 15 |
"github.com/docker/distribution/registry/client/auth" |
15 | 16 |
"github.com/docker/docker/api/types" |
16 | 17 |
registrytypes "github.com/docker/docker/api/types/registry" |
17 |
- "github.com/docker/docker/reference" |
|
18 | 18 |
) |
19 | 19 |
|
20 | 20 |
const ( |
... | ... |
@@ -43,7 +43,7 @@ github.com/boltdb/bolt fff57c100f4dea1905678da7e90d92429dff2904 |
43 | 43 |
github.com/miekg/dns 75e6e86cc601825c5dbcd4e0c209eab180997cd7 |
44 | 44 |
|
45 | 45 |
# get graph and distribution packages |
46 |
-github.com/docker/distribution 7dba427612198a11b161a27f9d40bb2dca1ccd20 |
|
46 |
+github.com/docker/distribution 129ad8ea0c3760d878b34cffdb9c3be874a7b2f7 |
|
47 | 47 |
github.com/vbatts/tar-split v0.10.1 |
48 | 48 |
github.com/opencontainers/go-digest a6d0ee40d4207ea02364bd3b9e8e77b9159ba1eb |
49 | 49 |
|
... | ... |
@@ -10,3 +10,21 @@ func IsNameOnly(ref Named) bool { |
10 | 10 |
} |
11 | 11 |
return true |
12 | 12 |
} |
13 |
+ |
|
14 |
+// FamiliarName returns the familiar name string |
|
15 |
+// for the given named, familiarizing if needed. |
|
16 |
+func FamiliarName(ref Named) string { |
|
17 |
+ if nn, ok := ref.(NormalizedNamed); ok { |
|
18 |
+ return nn.Familiar().Name() |
|
19 |
+ } |
|
20 |
+ return ref.Name() |
|
21 |
+} |
|
22 |
+ |
|
23 |
+// FamiliarString returns the familiar string representation |
|
24 |
+// for the given reference, familiarizing if needed. |
|
25 |
+func FamiliarString(ref Reference) string { |
|
26 |
+ if nn, ok := ref.(NormalizedNamed); ok { |
|
27 |
+ return nn.Familiar().String() |
|
28 |
+ } |
|
29 |
+ return ref.String() |
|
30 |
+} |
... | ... |
@@ -1,9 +1,125 @@ |
1 | 1 |
package reference |
2 | 2 |
|
3 |
+import ( |
|
4 |
+ "errors" |
|
5 |
+ "fmt" |
|
6 |
+ "strings" |
|
7 |
+ |
|
8 |
+ "github.com/docker/distribution/digestset" |
|
9 |
+ "github.com/opencontainers/go-digest" |
|
10 |
+) |
|
11 |
+ |
|
3 | 12 |
var ( |
4 |
- defaultTag = "latest" |
|
13 |
+ legacyDefaultDomain = "index.docker.io" |
|
14 |
+ defaultDomain = "docker.io" |
|
15 |
+ defaultRepoPrefix = "library/" |
|
16 |
+ defaultTag = "latest" |
|
5 | 17 |
) |
6 | 18 |
|
19 |
+// NormalizedNamed represents a name which has been |
|
20 |
+// normalized and has a familiar form. A familiar name |
|
21 |
+// is what is used in Docker UI. An example normalized |
|
22 |
+// name is "docker.io/library/ubuntu" and corresponding |
|
23 |
+// familiar name of "ubuntu". |
|
24 |
+type NormalizedNamed interface { |
|
25 |
+ Named |
|
26 |
+ Familiar() Named |
|
27 |
+} |
|
28 |
+ |
|
29 |
+// ParseNormalizedNamed parses a string into a named reference |
|
30 |
+// transforming a familiar name from Docker UI to a fully |
|
31 |
+// qualified reference. If the value may be an identifier |
|
32 |
+// use ParseAnyReference. |
|
33 |
+func ParseNormalizedNamed(s string) (NormalizedNamed, error) { |
|
34 |
+ if ok := anchoredIdentifierRegexp.MatchString(s); ok { |
|
35 |
+ return nil, fmt.Errorf("invalid repository name (%s), cannot specify 64-byte hexadecimal strings", s) |
|
36 |
+ } |
|
37 |
+ domain, remainder := splitDockerDomain(s) |
|
38 |
+ var remoteName string |
|
39 |
+ if tagSep := strings.IndexRune(remainder, ':'); tagSep > -1 { |
|
40 |
+ remoteName = remainder[:tagSep] |
|
41 |
+ } else { |
|
42 |
+ remoteName = remainder |
|
43 |
+ } |
|
44 |
+ if strings.ToLower(remoteName) != remoteName { |
|
45 |
+ return nil, errors.New("invalid reference format: repository name must be lowercase") |
|
46 |
+ } |
|
47 |
+ |
|
48 |
+ ref, err := Parse(domain + "/" + remainder) |
|
49 |
+ if err != nil { |
|
50 |
+ return nil, err |
|
51 |
+ } |
|
52 |
+ named, isNamed := ref.(NormalizedNamed) |
|
53 |
+ if !isNamed { |
|
54 |
+ return nil, fmt.Errorf("reference %s has no name", ref.String()) |
|
55 |
+ } |
|
56 |
+ return named, nil |
|
57 |
+} |
|
58 |
+ |
|
59 |
+// splitDockerDomain splits a repository name to domain and remotename string. |
|
60 |
+// If no valid domain is found, the default domain is used. Repository name |
|
61 |
+// needs to be already validated before. |
|
62 |
+func splitDockerDomain(name string) (domain, remainder string) { |
|
63 |
+ i := strings.IndexRune(name, '/') |
|
64 |
+ if i == -1 || (!strings.ContainsAny(name[:i], ".:") && name[:i] != "localhost") { |
|
65 |
+ domain, remainder = defaultDomain, name |
|
66 |
+ } else { |
|
67 |
+ domain, remainder = name[:i], name[i+1:] |
|
68 |
+ } |
|
69 |
+ if domain == legacyDefaultDomain { |
|
70 |
+ domain = defaultDomain |
|
71 |
+ } |
|
72 |
+ if domain == defaultDomain && !strings.ContainsRune(remainder, '/') { |
|
73 |
+ remainder = defaultRepoPrefix + remainder |
|
74 |
+ } |
|
75 |
+ return |
|
76 |
+} |
|
77 |
+ |
|
78 |
+// familiarizeName returns a shortened version of the name familiar |
|
79 |
+// to to the Docker UI. Familiar names have the default domain |
|
80 |
+// "docker.io" and "library/" repository prefix removed. |
|
81 |
+// For example, "docker.io/library/redis" will have the familiar |
|
82 |
+// name "redis" and "docker.io/dmcgowan/myapp" will be "dmcgowan/myapp". |
|
83 |
+// Returns a familiarized named only reference. |
|
84 |
+func familiarizeName(named namedRepository) repository { |
|
85 |
+ repo := repository{ |
|
86 |
+ domain: named.Domain(), |
|
87 |
+ path: named.Path(), |
|
88 |
+ } |
|
89 |
+ |
|
90 |
+ if repo.domain == defaultDomain { |
|
91 |
+ repo.domain = "" |
|
92 |
+ repo.path = strings.TrimPrefix(repo.path, defaultRepoPrefix) |
|
93 |
+ } |
|
94 |
+ return repo |
|
95 |
+} |
|
96 |
+ |
|
97 |
+func (r reference) Familiar() Named { |
|
98 |
+ return reference{ |
|
99 |
+ namedRepository: familiarizeName(r.namedRepository), |
|
100 |
+ tag: r.tag, |
|
101 |
+ digest: r.digest, |
|
102 |
+ } |
|
103 |
+} |
|
104 |
+ |
|
105 |
+func (r repository) Familiar() Named { |
|
106 |
+ return familiarizeName(r) |
|
107 |
+} |
|
108 |
+ |
|
109 |
+func (t taggedReference) Familiar() Named { |
|
110 |
+ return taggedReference{ |
|
111 |
+ namedRepository: familiarizeName(t.namedRepository), |
|
112 |
+ tag: t.tag, |
|
113 |
+ } |
|
114 |
+} |
|
115 |
+ |
|
116 |
+func (c canonicalReference) Familiar() Named { |
|
117 |
+ return canonicalReference{ |
|
118 |
+ namedRepository: familiarizeName(c.namedRepository), |
|
119 |
+ digest: c.digest, |
|
120 |
+ } |
|
121 |
+} |
|
122 |
+ |
|
7 | 123 |
// EnsureTagged adds the default tag "latest" to a reference if it only has |
8 | 124 |
// a repo name. |
9 | 125 |
func EnsureTagged(ref Named) NamedTagged { |
... | ... |
@@ -20,3 +136,33 @@ func EnsureTagged(ref Named) NamedTagged { |
20 | 20 |
} |
21 | 21 |
return namedTagged |
22 | 22 |
} |
23 |
+ |
|
24 |
+// ParseAnyReference parses a reference string as a possible identifier, |
|
25 |
+// full digest, or familiar name. |
|
26 |
+func ParseAnyReference(ref string) (Reference, error) { |
|
27 |
+ if ok := anchoredIdentifierRegexp.MatchString(ref); ok { |
|
28 |
+ return digestReference("sha256:" + ref), nil |
|
29 |
+ } |
|
30 |
+ if dgst, err := digest.Parse(ref); err == nil { |
|
31 |
+ return digestReference(dgst), nil |
|
32 |
+ } |
|
33 |
+ |
|
34 |
+ return ParseNormalizedNamed(ref) |
|
35 |
+} |
|
36 |
+ |
|
37 |
+// ParseAnyReferenceWithSet parses a reference string as a possible short |
|
38 |
+// identifier to be matched in a digest set, a full digest, or familiar name. |
|
39 |
+func ParseAnyReferenceWithSet(ref string, ds *digestset.Set) (Reference, error) { |
|
40 |
+ if ok := anchoredShortIdentifierRegexp.MatchString(ref); ok { |
|
41 |
+ dgst, err := ds.Lookup(ref) |
|
42 |
+ if err == nil { |
|
43 |
+ return digestReference(dgst), nil |
|
44 |
+ } |
|
45 |
+ } else { |
|
46 |
+ if dgst, err := digest.Parse(ref); err == nil { |
|
47 |
+ return digestReference(dgst), nil |
|
48 |
+ } |
|
49 |
+ } |
|
50 |
+ |
|
51 |
+ return ParseNormalizedNamed(ref) |
|
52 |
+} |
... | ... |
@@ -4,11 +4,11 @@ |
4 | 4 |
// Grammar |
5 | 5 |
// |
6 | 6 |
// reference := name [ ":" tag ] [ "@" digest ] |
7 |
-// name := [hostname '/'] component ['/' component]* |
|
8 |
-// hostname := hostcomponent ['.' hostcomponent]* [':' port-number] |
|
9 |
-// hostcomponent := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ |
|
7 |
+// name := [domain '/'] path-component ['/' path-component]* |
|
8 |
+// domain := domain-component ['.' domain-component]* [':' port-number] |
|
9 |
+// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ |
|
10 | 10 |
// port-number := /[0-9]+/ |
11 |
-// component := alpha-numeric [separator alpha-numeric]* |
|
11 |
+// path-component := alpha-numeric [separator alpha-numeric]* |
|
12 | 12 |
// alpha-numeric := /[a-z0-9]+/ |
13 | 13 |
// separator := /[_.]|__|[-]*/ |
14 | 14 |
// |
... | ... |
@@ -19,6 +19,9 @@ |
19 | 19 |
// digest-algorithm-separator := /[+.-_]/ |
20 | 20 |
// digest-algorithm-component := /[A-Za-z][A-Za-z0-9]*/ |
21 | 21 |
// digest-hex := /[0-9a-fA-F]{32,}/ ; At least 128 bit digest value |
22 |
+// |
|
23 |
+// identifier := /[a-f0-9]{64}/ |
|
24 |
+// short-identifier := /[a-f0-9]{6,64}/ |
|
22 | 25 |
package reference |
23 | 26 |
|
24 | 27 |
import ( |
... | ... |
@@ -126,23 +129,56 @@ type Digested interface { |
126 | 126 |
} |
127 | 127 |
|
128 | 128 |
// Canonical reference is an object with a fully unique |
129 |
-// name including a name with hostname and digest |
|
129 |
+// name including a name with domain and digest |
|
130 | 130 |
type Canonical interface { |
131 | 131 |
Named |
132 | 132 |
Digest() digest.Digest |
133 | 133 |
} |
134 | 134 |
|
135 |
+// namedRepository is a reference to a repository with a name. |
|
136 |
+// A namedRepository has both domain and path components. |
|
137 |
+type namedRepository interface { |
|
138 |
+ Named |
|
139 |
+ Domain() string |
|
140 |
+ Path() string |
|
141 |
+} |
|
142 |
+ |
|
143 |
+// Domain returns the domain part of the Named reference |
|
144 |
+func Domain(named Named) string { |
|
145 |
+ if r, ok := named.(namedRepository); ok { |
|
146 |
+ return r.Domain() |
|
147 |
+ } |
|
148 |
+ domain, _ := splitDomain(named.Name()) |
|
149 |
+ return domain |
|
150 |
+} |
|
151 |
+ |
|
152 |
+// Path returns the name without the domain part of the Named reference |
|
153 |
+func Path(named Named) (name string) { |
|
154 |
+ if r, ok := named.(namedRepository); ok { |
|
155 |
+ return r.Path() |
|
156 |
+ } |
|
157 |
+ _, path := splitDomain(named.Name()) |
|
158 |
+ return path |
|
159 |
+} |
|
160 |
+ |
|
161 |
+func splitDomain(name string) (string, string) { |
|
162 |
+ match := anchoredNameRegexp.FindStringSubmatch(name) |
|
163 |
+ if len(match) != 3 { |
|
164 |
+ return "", name |
|
165 |
+ } |
|
166 |
+ return match[1], match[2] |
|
167 |
+} |
|
168 |
+ |
|
135 | 169 |
// SplitHostname splits a named reference into a |
136 | 170 |
// hostname and name string. If no valid hostname is |
137 | 171 |
// found, the hostname is empty and the full value |
138 | 172 |
// is returned as name |
173 |
+// DEPRECATED: Use Domain or Path |
|
139 | 174 |
func SplitHostname(named Named) (string, string) { |
140 |
- name := named.Name() |
|
141 |
- match := anchoredNameRegexp.FindStringSubmatch(name) |
|
142 |
- if len(match) != 3 { |
|
143 |
- return "", name |
|
175 |
+ if r, ok := named.(namedRepository); ok { |
|
176 |
+ return r.Domain(), r.Path() |
|
144 | 177 |
} |
145 |
- return match[1], match[2] |
|
178 |
+ return splitDomain(named.Name()) |
|
146 | 179 |
} |
147 | 180 |
|
148 | 181 |
// Parse parses s and returns a syntactically valid Reference. |
... | ... |
@@ -164,9 +200,20 @@ func Parse(s string) (Reference, error) { |
164 | 164 |
return nil, ErrNameTooLong |
165 | 165 |
} |
166 | 166 |
|
167 |
+ var repo repository |
|
168 |
+ |
|
169 |
+ nameMatch := anchoredNameRegexp.FindStringSubmatch(matches[1]) |
|
170 |
+ if nameMatch != nil && len(nameMatch) == 3 { |
|
171 |
+ repo.domain = nameMatch[1] |
|
172 |
+ repo.path = nameMatch[2] |
|
173 |
+ } else { |
|
174 |
+ repo.domain = "" |
|
175 |
+ repo.path = matches[1] |
|
176 |
+ } |
|
177 |
+ |
|
167 | 178 |
ref := reference{ |
168 |
- name: matches[1], |
|
169 |
- tag: matches[2], |
|
179 |
+ namedRepository: repo, |
|
180 |
+ tag: matches[2], |
|
170 | 181 |
} |
171 | 182 |
if matches[3] != "" { |
172 | 183 |
var err error |
... | ... |
@@ -207,10 +254,15 @@ func WithName(name string) (Named, error) { |
207 | 207 |
if len(name) > NameTotalLengthMax { |
208 | 208 |
return nil, ErrNameTooLong |
209 | 209 |
} |
210 |
- if !anchoredNameRegexp.MatchString(name) { |
|
210 |
+ |
|
211 |
+ match := anchoredNameRegexp.FindStringSubmatch(name) |
|
212 |
+ if match == nil || len(match) != 3 { |
|
211 | 213 |
return nil, ErrReferenceInvalidFormat |
212 | 214 |
} |
213 |
- return repository(name), nil |
|
215 |
+ return repository{ |
|
216 |
+ domain: match[1], |
|
217 |
+ path: match[2], |
|
218 |
+ }, nil |
|
214 | 219 |
} |
215 | 220 |
|
216 | 221 |
// WithTag combines the name from "name" and the tag from "tag" to form a |
... | ... |
@@ -219,16 +271,23 @@ func WithTag(name Named, tag string) (NamedTagged, error) { |
219 | 219 |
if !anchoredTagRegexp.MatchString(tag) { |
220 | 220 |
return nil, ErrTagInvalidFormat |
221 | 221 |
} |
222 |
+ var repo repository |
|
223 |
+ if r, ok := name.(namedRepository); ok { |
|
224 |
+ repo.domain = r.Domain() |
|
225 |
+ repo.path = r.Path() |
|
226 |
+ } else { |
|
227 |
+ repo.path = name.Name() |
|
228 |
+ } |
|
222 | 229 |
if canonical, ok := name.(Canonical); ok { |
223 | 230 |
return reference{ |
224 |
- name: name.Name(), |
|
225 |
- tag: tag, |
|
226 |
- digest: canonical.Digest(), |
|
231 |
+ namedRepository: repo, |
|
232 |
+ tag: tag, |
|
233 |
+ digest: canonical.Digest(), |
|
227 | 234 |
}, nil |
228 | 235 |
} |
229 | 236 |
return taggedReference{ |
230 |
- name: name.Name(), |
|
231 |
- tag: tag, |
|
237 |
+ namedRepository: repo, |
|
238 |
+ tag: tag, |
|
232 | 239 |
}, nil |
233 | 240 |
} |
234 | 241 |
|
... | ... |
@@ -238,16 +297,23 @@ func WithDigest(name Named, digest digest.Digest) (Canonical, error) { |
238 | 238 |
if !anchoredDigestRegexp.MatchString(digest.String()) { |
239 | 239 |
return nil, ErrDigestInvalidFormat |
240 | 240 |
} |
241 |
+ var repo repository |
|
242 |
+ if r, ok := name.(namedRepository); ok { |
|
243 |
+ repo.domain = r.Domain() |
|
244 |
+ repo.path = r.Path() |
|
245 |
+ } else { |
|
246 |
+ repo.path = name.Name() |
|
247 |
+ } |
|
241 | 248 |
if tagged, ok := name.(Tagged); ok { |
242 | 249 |
return reference{ |
243 |
- name: name.Name(), |
|
244 |
- tag: tagged.Tag(), |
|
245 |
- digest: digest, |
|
250 |
+ namedRepository: repo, |
|
251 |
+ tag: tagged.Tag(), |
|
252 |
+ digest: digest, |
|
246 | 253 |
}, nil |
247 | 254 |
} |
248 | 255 |
return canonicalReference{ |
249 |
- name: name.Name(), |
|
250 |
- digest: digest, |
|
256 |
+ namedRepository: repo, |
|
257 |
+ digest: digest, |
|
251 | 258 |
}, nil |
252 | 259 |
} |
253 | 260 |
|
... | ... |
@@ -263,11 +329,15 @@ func Match(pattern string, ref Reference) (bool, error) { |
263 | 263 |
|
264 | 264 |
// TrimNamed removes any tag or digest from the named reference. |
265 | 265 |
func TrimNamed(ref Named) Named { |
266 |
- return repository(ref.Name()) |
|
266 |
+ domain, path := SplitHostname(ref) |
|
267 |
+ return repository{ |
|
268 |
+ domain: domain, |
|
269 |
+ path: path, |
|
270 |
+ } |
|
267 | 271 |
} |
268 | 272 |
|
269 | 273 |
func getBestReferenceType(ref reference) Reference { |
270 |
- if ref.name == "" { |
|
274 |
+ if ref.Name() == "" { |
|
271 | 275 |
// Allow digest only references |
272 | 276 |
if ref.digest != "" { |
273 | 277 |
return digestReference(ref.digest) |
... | ... |
@@ -277,16 +347,16 @@ func getBestReferenceType(ref reference) Reference { |
277 | 277 |
if ref.tag == "" { |
278 | 278 |
if ref.digest != "" { |
279 | 279 |
return canonicalReference{ |
280 |
- name: ref.name, |
|
281 |
- digest: ref.digest, |
|
280 |
+ namedRepository: ref.namedRepository, |
|
281 |
+ digest: ref.digest, |
|
282 | 282 |
} |
283 | 283 |
} |
284 |
- return repository(ref.name) |
|
284 |
+ return ref.namedRepository |
|
285 | 285 |
} |
286 | 286 |
if ref.digest == "" { |
287 | 287 |
return taggedReference{ |
288 |
- name: ref.name, |
|
289 |
- tag: ref.tag, |
|
288 |
+ namedRepository: ref.namedRepository, |
|
289 |
+ tag: ref.tag, |
|
290 | 290 |
} |
291 | 291 |
} |
292 | 292 |
|
... | ... |
@@ -294,17 +364,13 @@ func getBestReferenceType(ref reference) Reference { |
294 | 294 |
} |
295 | 295 |
|
296 | 296 |
type reference struct { |
297 |
- name string |
|
297 |
+ namedRepository |
|
298 | 298 |
tag string |
299 | 299 |
digest digest.Digest |
300 | 300 |
} |
301 | 301 |
|
302 | 302 |
func (r reference) String() string { |
303 |
- return r.name + ":" + r.tag + "@" + r.digest.String() |
|
304 |
-} |
|
305 |
- |
|
306 |
-func (r reference) Name() string { |
|
307 |
- return r.name |
|
303 |
+ return r.Name() + ":" + r.tag + "@" + r.digest.String() |
|
308 | 304 |
} |
309 | 305 |
|
310 | 306 |
func (r reference) Tag() string { |
... | ... |
@@ -315,14 +381,28 @@ func (r reference) Digest() digest.Digest { |
315 | 315 |
return r.digest |
316 | 316 |
} |
317 | 317 |
|
318 |
-type repository string |
|
318 |
+type repository struct { |
|
319 |
+ domain string |
|
320 |
+ path string |
|
321 |
+} |
|
319 | 322 |
|
320 | 323 |
func (r repository) String() string { |
321 |
- return string(r) |
|
324 |
+ return r.Name() |
|
322 | 325 |
} |
323 | 326 |
|
324 | 327 |
func (r repository) Name() string { |
325 |
- return string(r) |
|
328 |
+ if r.domain == "" { |
|
329 |
+ return r.path |
|
330 |
+ } |
|
331 |
+ return r.domain + "/" + r.path |
|
332 |
+} |
|
333 |
+ |
|
334 |
+func (r repository) Domain() string { |
|
335 |
+ return r.domain |
|
336 |
+} |
|
337 |
+ |
|
338 |
+func (r repository) Path() string { |
|
339 |
+ return r.path |
|
326 | 340 |
} |
327 | 341 |
|
328 | 342 |
type digestReference digest.Digest |
... | ... |
@@ -336,16 +416,12 @@ func (d digestReference) Digest() digest.Digest { |
336 | 336 |
} |
337 | 337 |
|
338 | 338 |
type taggedReference struct { |
339 |
- name string |
|
340 |
- tag string |
|
339 |
+ namedRepository |
|
340 |
+ tag string |
|
341 | 341 |
} |
342 | 342 |
|
343 | 343 |
func (t taggedReference) String() string { |
344 |
- return t.name + ":" + t.tag |
|
345 |
-} |
|
346 |
- |
|
347 |
-func (t taggedReference) Name() string { |
|
348 |
- return t.name |
|
344 |
+ return t.Name() + ":" + t.tag |
|
349 | 345 |
} |
350 | 346 |
|
351 | 347 |
func (t taggedReference) Tag() string { |
... | ... |
@@ -353,16 +429,12 @@ func (t taggedReference) Tag() string { |
353 | 353 |
} |
354 | 354 |
|
355 | 355 |
type canonicalReference struct { |
356 |
- name string |
|
356 |
+ namedRepository |
|
357 | 357 |
digest digest.Digest |
358 | 358 |
} |
359 | 359 |
|
360 | 360 |
func (c canonicalReference) String() string { |
361 |
- return c.name + "@" + c.digest.String() |
|
362 |
-} |
|
363 |
- |
|
364 |
-func (c canonicalReference) Name() string { |
|
365 |
- return c.name |
|
361 |
+ return c.Name() + "@" + c.digest.String() |
|
366 | 362 |
} |
367 | 363 |
|
368 | 364 |
func (c canonicalReference) Digest() digest.Digest { |
... | ... |
@@ -19,18 +19,18 @@ var ( |
19 | 19 |
alphaNumericRegexp, |
20 | 20 |
optional(repeated(separatorRegexp, alphaNumericRegexp))) |
21 | 21 |
|
22 |
- // hostnameComponentRegexp restricts the registry hostname component of a |
|
23 |
- // repository name to start with a component as defined by hostnameRegexp |
|
22 |
+ // domainComponentRegexp restricts the registry domain component of a |
|
23 |
+ // repository name to start with a component as defined by domainRegexp |
|
24 | 24 |
// and followed by an optional port. |
25 |
- hostnameComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) |
|
25 |
+ domainComponentRegexp = match(`(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`) |
|
26 | 26 |
|
27 |
- // hostnameRegexp defines the structure of potential hostname components |
|
27 |
+ // domainRegexp defines the structure of potential domain components |
|
28 | 28 |
// that may be part of image names. This is purposely a subset of what is |
29 | 29 |
// allowed by DNS to ensure backwards compatibility with Docker image |
30 | 30 |
// names. |
31 |
- hostnameRegexp = expression( |
|
32 |
- hostnameComponentRegexp, |
|
33 |
- optional(repeated(literal(`.`), hostnameComponentRegexp)), |
|
31 |
+ domainRegexp = expression( |
|
32 |
+ domainComponentRegexp, |
|
33 |
+ optional(repeated(literal(`.`), domainComponentRegexp)), |
|
34 | 34 |
optional(literal(`:`), match(`[0-9]+`))) |
35 | 35 |
|
36 | 36 |
// TagRegexp matches valid tag names. From docker/docker:graph/tags.go. |
... | ... |
@@ -48,17 +48,17 @@ var ( |
48 | 48 |
anchoredDigestRegexp = anchored(DigestRegexp) |
49 | 49 |
|
50 | 50 |
// NameRegexp is the format for the name component of references. The |
51 |
- // regexp has capturing groups for the hostname and name part omitting |
|
51 |
+ // regexp has capturing groups for the domain and name part omitting |
|
52 | 52 |
// the separating forward slash from either. |
53 | 53 |
NameRegexp = expression( |
54 |
- optional(hostnameRegexp, literal(`/`)), |
|
54 |
+ optional(domainRegexp, literal(`/`)), |
|
55 | 55 |
nameComponentRegexp, |
56 | 56 |
optional(repeated(literal(`/`), nameComponentRegexp))) |
57 | 57 |
|
58 | 58 |
// anchoredNameRegexp is used to parse a name value, capturing the |
59 |
- // hostname and trailing components. |
|
59 |
+ // domain and trailing components. |
|
60 | 60 |
anchoredNameRegexp = anchored( |
61 |
- optional(capture(hostnameRegexp), literal(`/`)), |
|
61 |
+ optional(capture(domainRegexp), literal(`/`)), |
|
62 | 62 |
capture(nameComponentRegexp, |
63 | 63 |
optional(repeated(literal(`/`), nameComponentRegexp)))) |
64 | 64 |
|
... | ... |
@@ -68,6 +68,25 @@ var ( |
68 | 68 |
ReferenceRegexp = anchored(capture(NameRegexp), |
69 | 69 |
optional(literal(":"), capture(TagRegexp)), |
70 | 70 |
optional(literal("@"), capture(DigestRegexp))) |
71 |
+ |
|
72 |
+ // IdentifierRegexp is the format for string identifier used as a |
|
73 |
+ // content addressable identifier using sha256. These identifiers |
|
74 |
+ // are like digests without the algorithm, since sha256 is used. |
|
75 |
+ IdentifierRegexp = match(`([a-f0-9]{64})`) |
|
76 |
+ |
|
77 |
+ // ShortIdentifierRegexp is the format used to represent a prefix |
|
78 |
+ // of an identifier. A prefix may be used to match a sha256 identifier |
|
79 |
+ // within a list of trusted identifiers. |
|
80 |
+ ShortIdentifierRegexp = match(`([a-f0-9]{6,64})`) |
|
81 |
+ |
|
82 |
+ // anchoredIdentifierRegexp is used to check or match an |
|
83 |
+ // identifier value, anchored at start and end of string. |
|
84 |
+ anchoredIdentifierRegexp = anchored(IdentifierRegexp) |
|
85 |
+ |
|
86 |
+ // anchoredShortIdentifierRegexp is used to check if a value |
|
87 |
+ // is a possible identifier prefix, anchored at start and end |
|
88 |
+ // of string. |
|
89 |
+ anchoredShortIdentifierRegexp = anchored(ShortIdentifierRegexp) |
|
71 | 90 |
) |
72 | 91 |
|
73 | 92 |
// match compiles the string to a regular expression. |