forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
image_mapper.go
116 lines (106 loc) · 4.15 KB
/
image_mapper.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
package release
import (
"fmt"
"io/ioutil"
"regexp"
"strings"
"github.com/golang/glog"
imageapi "github.com/openshift/api/image/v1"
imagescheme "github.com/openshift/client-go/image/clientset/versioned/scheme"
)
type ManifestMapper func(data []byte) ([]byte, error)
func NewImageMapperFromImageStreamFile(path string, input *imageapi.ImageStream, allowMissingImages bool) (ManifestMapper, error) {
data, err := ioutil.ReadFile(path)
if err != nil {
return nil, err
}
is := &imageapi.ImageStream{}
if _, _, err := imagescheme.Codecs.UniversalDeserializer().Decode(data, nil, is); err != nil {
return nil, err
}
if is.TypeMeta.Kind != "ImageStream" || is.TypeMeta.APIVersion != "image.openshift.io/v1" {
return nil, fmt.Errorf("%q was not a valid image stream - kind and apiVersion must be ImageStream and image.openshift.io/v1", path)
}
references := make(map[string]ImageReference)
for _, tag := range is.Spec.Tags {
if tag.From == nil || tag.From.Kind != "DockerImage" {
continue
}
if len(tag.From.Name) == 0 {
return nil, fmt.Errorf("Image file %q did not specify a valid target location for tag %q - no from.name for the tag", path, tag.Name)
}
ref := ImageReference{SourceRepository: tag.From.Name}
for _, inputTag := range input.Spec.Tags {
if inputTag.Name == tag.Name {
ref.TargetPullSpec = inputTag.From.Name
break
}
}
if len(ref.TargetPullSpec) == 0 {
if allowMissingImages {
glog.V(2).Infof("Image file %q referenced an image %q that is not part of the input images, skipping", path, tag.From.Name)
continue
}
return nil, fmt.Errorf("requested mapping for %q, but no input image could be located", tag.From.Name)
}
references[tag.Name] = ref
}
return NewImageMapper(references)
}
type ImageReference struct {
SourceRepository string
TargetPullSpec string
}
func NopManifestMapper(data []byte) ([]byte, error) {
return data, nil
}
// patternImageFormat attempts to match a docker pull spec by prefix (%s) and capture the
// prefix and either a tag or digest. It requires leading and trailing whitespace, quotes, or
// end of file.
const patternImageFormat = `([\s\"\']|^)(%s)(:[\w][\w.-]{0,127}|@[A-Za-z][A-Za-z0-9]*(?:[-_+.][A-Za-z][A-Za-z0-9]*)*[:][[:xdigit:]]{2,})?([\s"']|$)`
func NewImageMapper(images map[string]ImageReference) (ManifestMapper, error) {
repositories := make([]string, 0, len(images))
bySource := make(map[string]string)
for name, ref := range images {
if len(ref.SourceRepository) == 0 {
return nil, fmt.Errorf("an empty source repository is not allowed for name %q", name)
}
if existing, ok := bySource[ref.SourceRepository]; ok {
return nil, fmt.Errorf("the source repository %q was defined more than once (for %q and %q)", ref.SourceRepository, existing, name)
}
bySource[ref.SourceRepository] = name
repositories = append(repositories, regexp.QuoteMeta(ref.SourceRepository))
}
if len(repositories) == 0 {
glog.V(5).Infof("No images are mapped, will not replace any contents")
return NopManifestMapper, nil
}
pattern := fmt.Sprintf(patternImageFormat, strings.Join(repositories, "|"))
re := regexp.MustCompile(pattern)
return func(data []byte) ([]byte, error) {
out := re.ReplaceAllFunc(data, func(in []byte) []byte {
parts := re.FindSubmatch(in)
repository := string(parts[2])
name, ok := bySource[repository]
if !ok {
glog.V(4).Infof("found potential image %q, but no matching definition", repository)
return in
}
ref := images[name]
suffix := parts[3]
glog.V(2).Infof("found repository %q with locator %q in the input, switching to %q (from pattern %s)", string(repository), string(suffix), ref.TargetPullSpec, pattern)
switch {
case len(suffix) == 0:
// we found a repository, but no tag or digest (implied latest), or we got an exact match
return []byte(string(parts[1]) + ref.TargetPullSpec + string(parts[4]))
case suffix[0] == '@':
// we got a digest
return []byte(string(parts[1]) + ref.TargetPullSpec + string(parts[4]))
default:
// TODO: we didn't get a digest, so we have to decide what to replace
return []byte(string(parts[1]) + ref.TargetPullSpec + string(parts[4]))
}
})
return out, nil
}, nil
}