|
17 | 17 | package referenceutil
|
18 | 18 |
|
19 | 19 | import (
|
20 |
| - "fmt" |
21 | 20 | "path"
|
22 | 21 | "strings"
|
23 | 22 |
|
24 |
| - distributionref "github.com/distribution/reference" |
| 23 | + "github.com/distribution/reference" |
25 | 24 | "github.com/ipfs/go-cid"
|
| 25 | + "github.com/opencontainers/go-digest" |
26 | 26 | )
|
27 | 27 |
|
28 |
| -// Reference is a reference to an image. |
29 |
| -type Reference interface { |
| 28 | +type Protocol string |
30 | 29 |
|
31 |
| - // String returns the full reference which can be understood by containerd. |
32 |
| - String() string |
| 30 | +const IPFSProtocol Protocol = "ipfs" |
| 31 | +const IPNSProtocol Protocol = "ipns" |
| 32 | +const shortIDLength = 5 |
| 33 | + |
| 34 | +type ImageReference struct { |
| 35 | + Protocol Protocol |
| 36 | + Digest digest.Digest |
| 37 | + Tag string |
| 38 | + ExplicitTag string |
| 39 | + Path string |
| 40 | + Domain string |
| 41 | + |
| 42 | + nn reference.Reference |
33 | 43 | }
|
34 | 44 |
|
35 |
| -// ParseAnyReference parses the passed reference as IPFS, CID, or a classic reference. |
36 |
| -// Unlike ParseAny, it is not limited to the DockerRef limitations (being either tagged or digested) |
37 |
| -// and should be used instead. |
38 |
| -func ParseAnyReference(rawRef string) (Reference, error) { |
39 |
| - if scheme, ref, err := ParseIPFSRefWithScheme(rawRef); err == nil { |
40 |
| - return Reference(stringRef{scheme: scheme, s: ref}), nil |
| 45 | +func (ir *ImageReference) Name() string { |
| 46 | + ret := ir.Domain |
| 47 | + if ret != "" { |
| 48 | + ret += "/" |
41 | 49 | }
|
42 |
| - if c, err := cid.Decode(rawRef); err == nil { |
43 |
| - return c, nil |
44 |
| - } |
45 |
| - return distributionref.ParseAnyReference(rawRef) |
| 50 | + ret += ir.Path |
| 51 | + return ret |
46 | 52 | }
|
47 | 53 |
|
48 |
| -// ParseAny parses the passed reference with allowing it to be non-docker reference. |
49 |
| -// If the ref has IPFS scheme or can be parsed as CID, it's parsed as an IPFS reference. |
50 |
| -// Otherwise it's parsed as a docker reference. |
51 |
| -func ParseAny(rawRef string) (Reference, error) { |
52 |
| - if scheme, ref, err := ParseIPFSRefWithScheme(rawRef); err == nil { |
53 |
| - return stringRef{scheme: scheme, s: ref}, nil |
| 54 | +func (ir *ImageReference) FamiliarName() string { |
| 55 | + if ir.Protocol != "" && ir.Domain == "" { |
| 56 | + return ir.Path |
54 | 57 | }
|
55 |
| - if c, err := cid.Decode(rawRef); err == nil { |
56 |
| - return c, nil |
| 58 | + if ir.nn != nil { |
| 59 | + return reference.FamiliarName(ir.nn.(reference.Named)) |
57 | 60 | }
|
58 |
| - return ParseDockerRef(rawRef) |
| 61 | + return "" |
59 | 62 | }
|
60 | 63 |
|
61 |
| -// ParseDockerRef parses the passed reference with assuming it's a docker reference. |
62 |
| -func ParseDockerRef(rawRef string) (distributionref.Named, error) { |
63 |
| - return distributionref.ParseDockerRef(rawRef) |
| 64 | +func (ir *ImageReference) FamiliarMatch(pattern string) (bool, error) { |
| 65 | + return reference.FamiliarMatch(pattern, ir.nn) |
64 | 66 | }
|
65 | 67 |
|
66 |
| -// ParseIPFSRefWithScheme parses the passed reference with assuming it's an IPFS reference with scheme prefix. |
67 |
| -func ParseIPFSRefWithScheme(name string) (scheme, ref string, err error) { |
68 |
| - if strings.HasPrefix(name, "ipfs://") || strings.HasPrefix(name, "ipns://") { |
69 |
| - return name[:4], name[7:], nil |
| 68 | +func (ir *ImageReference) String() string { |
| 69 | + if ir.Protocol != "" && ir.Domain == "" { |
| 70 | + return ir.Path |
| 71 | + } |
| 72 | + if ir.Path == "" && ir.Digest != "" { |
| 73 | + return ir.Digest.String() |
70 | 74 | }
|
71 |
| - return "", "", fmt.Errorf("reference is not an IPFS reference") |
| 75 | + if ir.nn != nil { |
| 76 | + return ir.nn.String() |
| 77 | + } |
| 78 | + return "" |
72 | 79 | }
|
73 | 80 |
|
74 |
| -type stringRef struct { |
75 |
| - scheme string |
76 |
| - s string |
| 81 | +func (ir *ImageReference) SuggestContainerName(suffix string) string { |
| 82 | + name := "untitled" |
| 83 | + if ir.Protocol != "" && ir.Domain == "" { |
| 84 | + name = string(ir.Protocol) + "-" + ir.String()[:shortIDLength] |
| 85 | + } else if ir.Path != "" { |
| 86 | + name = path.Base(ir.Path) |
| 87 | + } |
| 88 | + return name + "-" + suffix[:5] |
77 | 89 | }
|
78 | 90 |
|
79 |
| -func (s stringRef) String() string { |
80 |
| - return s.s |
81 |
| -} |
| 91 | +func Parse(rawRef string) (*ImageReference, error) { |
| 92 | + ir := &ImageReference{} |
| 93 | + |
| 94 | + if strings.HasPrefix(rawRef, "ipfs://") { |
| 95 | + ir.Protocol = IPFSProtocol |
| 96 | + rawRef = rawRef[7:] |
| 97 | + } else if strings.HasPrefix(rawRef, "ipns://") { |
| 98 | + ir.Protocol = IPNSProtocol |
| 99 | + rawRef = rawRef[7:] |
| 100 | + } |
| 101 | + if decodedCID, err := cid.Decode(rawRef); err == nil { |
| 102 | + ir.Protocol = IPFSProtocol |
| 103 | + rawRef = decodedCID.String() |
| 104 | + ir.Path = rawRef |
| 105 | + return ir, nil |
| 106 | + } |
| 107 | + |
| 108 | + if dgst, err := digest.Parse(rawRef); err == nil { |
| 109 | + ir.Digest = dgst |
| 110 | + return ir, nil |
| 111 | + } else if dgst, err := digest.Parse("sha256:" + rawRef); err == nil { |
| 112 | + ir.Digest = dgst |
| 113 | + return ir, nil |
| 114 | + } |
82 | 115 |
|
83 |
| -// SuggestContainerName generates a container name from name. |
84 |
| -// The result MUST NOT be parsed. |
85 |
| -func SuggestContainerName(rawRef, containerID string) string { |
86 |
| - const shortIDLength = 5 |
87 |
| - if len(containerID) < shortIDLength { |
88 |
| - panic(fmt.Errorf("got too short (< %d) container ID: %q", shortIDLength, containerID)) |
| 116 | + var err error |
| 117 | + ir.nn, err = reference.ParseNormalizedNamed(rawRef) |
| 118 | + if err != nil { |
| 119 | + return ir, err |
89 | 120 | }
|
90 |
| - name := "untitled-" + containerID[:shortIDLength] |
91 |
| - if rawRef != "" { |
92 |
| - r, err := ParseAny(rawRef) |
93 |
| - if err == nil { |
94 |
| - switch rr := r.(type) { |
95 |
| - case distributionref.Named: |
96 |
| - if rrName := rr.Name(); rrName != "" { |
97 |
| - imageNameBased := path.Base(rrName) |
98 |
| - if imageNameBased != "" { |
99 |
| - name = imageNameBased + "-" + containerID[:shortIDLength] |
100 |
| - } |
101 |
| - } |
102 |
| - case cid.Cid: |
103 |
| - name = "ipfs" + "-" + rr.String()[:shortIDLength] + "-" + containerID[:shortIDLength] |
104 |
| - case stringRef: |
105 |
| - name = rr.scheme + "-" + rr.s[:shortIDLength] + "-" + containerID[:shortIDLength] |
106 |
| - } |
107 |
| - } |
| 121 | + if tg, ok := ir.nn.(reference.Tagged); ok { |
| 122 | + ir.ExplicitTag = tg.Tag() |
108 | 123 | }
|
109 |
| - return name |
| 124 | + if tg, ok := ir.nn.(reference.Named); ok { |
| 125 | + ir.nn = reference.TagNameOnly(tg) |
| 126 | + ir.Domain = reference.Domain(tg) |
| 127 | + ir.Path = reference.Path(tg) |
| 128 | + } |
| 129 | + if tg, ok := ir.nn.(reference.Tagged); ok { |
| 130 | + ir.Tag = tg.Tag() |
| 131 | + } |
| 132 | + if tg, ok := ir.nn.(reference.Digested); ok { |
| 133 | + ir.Digest = tg.Digest() |
| 134 | + } |
| 135 | + |
| 136 | + return ir, nil |
110 | 137 | }
|
0 commit comments