This repository has been archived by the owner on Mar 16, 2024. It is now read-only.
/
find.go
152 lines (136 loc) · 4.69 KB
/
find.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
package images
import (
"fmt"
"strings"
"github.com/google/go-containerregistry/pkg/name"
apiv1 "github.com/acorn-io/runtime/pkg/apis/api.acorn.io/v1"
tags2 "github.com/acorn-io/runtime/pkg/tags"
)
type ErrImageNotFound struct {
ImageSearch string
}
func (e ErrImageNotFound) Error() string {
return fmt.Sprintf("image not found: %s", e.ImageSearch)
}
type ErrImageIdentifierNotUnique struct {
ImageSearch string
}
func (e ErrImageIdentifierNotUnique) Error() string {
return fmt.Sprintf("image identifier not unique: %s", e.ImageSearch)
}
// findImageMatch matches images by digest, digest prefix, or tag name:
//
// - digest (raw): sha256:<digest> or <digest> (exactly 64 chars)
// - digest (image): <registry>/<repo>@sha256:<digest> or <repo>@sha256:<digest>
// - digest prefix: sha256:<digest prefix> (min. 3 chars)
// - tag name: <registry>/<repo>:<tag> or <repo>:<tag>
// - tag name (with default): <registry>/<repo> or <repo> -> Will be matched against the default tag (:latest)
// - Note: if we get some string here, that matches the SHAPermissivePrefixPattern, it could be both a digest or a name without a tag
// so we will try to match it against the default tag (:latest) first and if that fails, we treat it as a digest(-prefix)
func FindImageMatch(images apiv1.ImageList, search string) (*apiv1.Image, string, error) {
var (
repoDigest name.Digest
digest string
digestPrefix string
tagName string
tagNameDefault string
canBeMultiple bool // if true, we will not return on first match
)
if strings.HasPrefix(search, "sha256:") {
digest = search
} else if tags2.SHAPattern.MatchString(search) {
digest = "sha256:" + search
tagNameDefault = search // this could as well be some name without registry/repo path and tag
} else if tags2.SHAPermissivePrefixPattern.MatchString(search) {
digestPrefix = "sha256:" + search
tagNameDefault = search // this could as well be some name without registry/repo path and tag
} else {
ref, err := name.ParseReference(search, name.WithDefaultRegistry(""), name.WithDefaultTag(""))
if err != nil {
return nil, "", err
}
if ref.Identifier() == "" {
tagNameDefault = ref.Name() // some name without a tag, so we will try to match it against the default tag (:latest)
canBeMultiple = true
} else if dig, ok := ref.(name.Digest); ok {
repoDigest = dig
} else {
tagName = ref.Name()
}
}
if tagNameDefault != "" {
// add default tag (:latest)
t, err := name.ParseReference(tagNameDefault, name.WithDefaultRegistry(""))
if err != nil {
return nil, "", err
}
tagNameDefault = t.Name()
}
var matchedImage apiv1.Image
var matchedTag string
for _, image := range images.Items {
// >>> match by tag name with default tag (:latest)
if tagNameDefault != "" {
for _, tag := range image.Tags {
if tag == tagNameDefault {
return &image, tag, nil
}
}
}
// >>> match by digest or digest prefix
if image.Digest == digest {
return &image, "", nil
} else if digestPrefix != "" && strings.HasPrefix(image.Digest, digestPrefix) {
if matchedImage.Digest != "" && matchedImage.Digest != image.Digest {
return nil, "", ErrImageIdentifierNotUnique{ImageSearch: search}
}
matchedImage = image
}
// >>> match by repo digest
// this returns an image which matches the digest and has at least one tag
// which matches the repo part of the repo digest.
if repoDigest.Name() != "" && image.Digest == repoDigest.DigestStr() {
for _, tag := range image.Tags {
imageParsedTag, err := name.NewTag(tag, name.WithDefaultRegistry(""))
if err != nil {
continue
}
if imageParsedTag.Context().Name() == repoDigest.Context().Name() {
return &image, tag, nil
}
}
}
// >>> match by tag name
for _, tag := range image.Tags {
if tag == search {
if !canBeMultiple {
return &image, tag, nil
}
if matchedImage.Digest != "" && matchedImage.Digest != image.Digest {
return nil, "", ErrImageIdentifierNotUnique{ImageSearch: search}
}
matchedImage = image
matchedTag = tag
} else if tag != "" {
imageParsedTag, err := name.NewTag(tag, name.WithDefaultRegistry(""), name.WithDefaultTag("")) // no default here, as we also have repo-only tag items
if err != nil {
continue
}
if imageParsedTag.Name() == tagName {
if !canBeMultiple {
return &image, tag, nil
}
if matchedImage.Digest != "" && matchedImage.Digest != image.Digest {
return nil, "", ErrImageIdentifierNotUnique{ImageSearch: search}
}
matchedImage = image
matchedTag = tag
}
}
}
}
if matchedImage.Digest != "" {
return &matchedImage, matchedTag, nil
}
return nil, "", ErrImageNotFound{ImageSearch: search}
}