forked from d2iq-archive/cake-builder
-
Notifications
You must be signed in to change notification settings - Fork 0
/
image_tree.go
166 lines (138 loc) · 4.57 KB
/
image_tree.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
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package main
import (
"errors"
"fmt"
"sort"
)
// Tree Node represents Docker Image
type Image struct {
ImageConfig ImageConfig
Dockerfile string
Checksum string
Parent *Image
Children []*Image
}
func (image Image) String() string {
parent := ""
if image.Parent != nil {
parentConfig := image.Parent.ImageConfig
parent = fmt.Sprintf("{Id: %s, Repository: %s, Name: %s}", parentConfig.Id, parentConfig.Repository, parentConfig.Name)
}
return fmt.Sprintf("Image{Dockerfile: %s, Checksum: %s, Parent: %s, %s}",
image.Dockerfile, image.Checksum, parent, image.ImageConfig)
}
func (image Image) getFullName() string {
return fmt.Sprintf("%s/%s", image.ImageConfig.Repository, image.ImageConfig.Name)
}
func (image Image) getDockerTags(config BuildConfig) []string {
tags := []string{fmt.Sprintf("%s:%s%s", image.getFullName(), "latest", getTagSuffixStr(image))}
if len(image.Checksum) > 0 {
tags = append(tags, fmt.Sprintf("%s:%s%s", image.getFullName(), image.Checksum, getTagSuffixStr(image)))
}
if len(config.ReleaseTag) > 0 && "latest" != config.ReleaseTag {
tags = append(tags, fmt.Sprintf("%s:%s%s", image.getFullName(), config.ReleaseTag, getTagSuffixStr(image)))
}
return tags
}
func (image Image) getStableTag(config BuildConfig) string {
if len(config.ReleaseTag) > 0 && config.ReleaseTag != "latest" {
return fmt.Sprintf("%s%s", config.ReleaseTag, getTagSuffixStr(image))
} else if len(image.Checksum) > 0 {
return fmt.Sprintf("%s%s", image.Checksum, getTagSuffixStr(image))
} else {
return fmt.Sprintf("%s%s", "latest", getTagSuffixStr(image))
}
}
func (image Image) getChecksumTag(config BuildConfig) string {
return fmt.Sprintf("%s%s", image.Checksum, getTagSuffixStr(image))
}
func getTagSuffixStr(image Image) string {
if len(image.ImageConfig.TagSuffix) > 0 {
return fmt.Sprintf("-%s", image.ImageConfig.TagSuffix)
} else {
return ""
}
}
// Transforms list of config items into independent Tree nodes.
// Checks for duplicate IDs and multiple parents (images with no parent defined)
func transformConfigToImages(config BuildConfig) (images map[string]*Image, err error) {
imageMap := make(map[string]*Image)
var baseImage *Image
for _, imageConfig := range config.Images {
if _, exists := imageMap[imageConfig.Id]; exists {
return nil, errors.New("Duplicate Image ID in config: " + imageConfig.Id)
}
image := Image{
ImageConfig: imageConfig,
}
if len(imageConfig.Parent) == 0 {
if baseImage != nil {
return nil, errors.New(fmt.Sprintf("Multiple base images without declared parents: %s and %s", baseImage.ImageConfig.Id, image.ImageConfig.Id))
}
baseImage = &image
}
imageMap[imageConfig.Id] = &image
}
return imageMap, nil
}
// Constructs a tree/DAG of images and performs cycle detection check and orphaned images check
func createImageBuildGraph(images map[string]*Image) (image *Image, err error) {
// using sorted slice of image ids to maintain consistent building of the target graph
// which can not be achieved by iterating over the map due to random iteration order
var ids []string
for key := range images {
ids = append(ids, key)
}
sort.Strings(ids)
var root *Image
for _, key := range ids {
image := images[key]
if len(image.ImageConfig.Parent) == 0 {
root = image
continue
}
imageParent, found := images[image.ImageConfig.Parent]
if !found || imageParent == nil {
return nil, errors.New(fmt.Sprintf("Unable to find parent with ID: %s", image.ImageConfig.Parent))
}
image.Parent = imageParent
imageParent.Children = append(imageParent.Children, image)
}
if root == nil {
return nil, errors.New("unable to find base image, check config for cycles")
}
//checking for cycles
visited := make(map[*Image]bool)
queue := []*Image{root}
for {
if len(queue) == 0 {
break
}
image := queue[len(queue)-1]
if visited[image] {
return nil, errors.New(fmt.Sprintf("Build hierarchy defined in the config has a cycle, aborting. Image ID: %s", image.ImageConfig.Id))
} else {
visited[image] = true
}
queue = append(image.Children, queue[:len(queue)-1]...)
}
//checking for orphaned images
for _, key := range ids {
image := images[key]
if !visited[image] {
return nil, errors.New(fmt.Sprintf("Detected orphaned image defined in the config but not found in the build graph. Image ID: %s", image.ImageConfig.Id))
}
}
return root, nil
}
func walkBuildGraph(graph *Image, apply func(image *Image)) {
queue := []*Image{graph}
for {
if len(queue) == 0 {
break
}
image := queue[len(queue)-1]
apply(image)
queue = append(image.Children, queue[:len(queue)-1]...)
}
}