-
Notifications
You must be signed in to change notification settings - Fork 289
/
build_result.go
261 lines (217 loc) · 6.71 KB
/
build_result.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
package store
import (
"fmt"
"sort"
"github.com/docker/distribution/reference"
"github.com/windmilleng/tilt/internal/container"
"github.com/windmilleng/tilt/internal/dockercompose"
"github.com/windmilleng/tilt/internal/k8s"
"github.com/windmilleng/tilt/internal/model"
)
// The results of a successful build.
type BuildResult struct {
// The image target that this built.
TargetID model.TargetID
// The name+tag of the image that the pod is running.
//
// The tag is derived from a content-addressable digest.
Image reference.NamedTagged
// If this build was a container build, containerID we built on top of
ContainerID container.ID
// Some of our build engines replace the files in-place, rather
// than building a new image. This captures how much the code
// running on-pod has diverged from the original image.
FilesReplacedSet map[string]bool
}
// For docker-compose deploys that don't have any built images.
func NewContainerBuildResult(id model.TargetID, containerID container.ID) BuildResult {
return BuildResult{
TargetID: id,
ContainerID: containerID,
}
}
// For image targets. The container id will be added later.
func NewImageBuildResult(id model.TargetID, image reference.NamedTagged) BuildResult {
return BuildResult{
TargetID: id,
Image: image,
}
}
func (b BuildResult) IsEmpty() bool {
return b.TargetID.Empty()
}
func (b BuildResult) HasImage() bool {
return b.Image != nil
}
func (b BuildResult) IsInPlaceUpdate() bool {
return !b.ContainerID.Empty()
}
// Clone the build result and add new replaced files.
// Does not do a deep clone of the underlying entities.
func (b BuildResult) ShallowCloneForContainerUpdate(filesReplacedSet map[string]bool) BuildResult {
result := BuildResult{}
result.TargetID = b.TargetID
result.Image = b.Image
newSet := make(map[string]bool, len(b.FilesReplacedSet)+len(filesReplacedSet))
for k, v := range b.FilesReplacedSet {
newSet[k] = v
}
for k, v := range filesReplacedSet {
newSet[k] = v
}
result.FilesReplacedSet = newSet
return result
}
type BuildResultSet map[model.TargetID]BuildResult
// Returns a container ID iff it's the only container ID in the result set.
// If there are multiple container IDs, we have to give up.
func (set BuildResultSet) OneAndOnlyContainerID() container.ID {
var id container.ID
for _, result := range set {
if result.ContainerID == "" {
continue
}
if id != "" && result.ContainerID != id {
return ""
}
id = result.ContainerID
}
return id
}
// The state of the system since the last successful build.
// This data structure should be considered immutable.
// All methods that return a new BuildState should first clone the existing build state.
type BuildState struct {
// The last successful build.
LastResult BuildResult
// Files changed since the last result was build.
// This must be liberal: it's ok if this has too many files, but not ok if it has too few.
FilesChangedSet map[string]bool
DeployInfo DeployInfo
}
func NewBuildState(result BuildResult, files []string) BuildState {
set := make(map[string]bool, len(files))
for _, f := range files {
set[f] = true
}
return BuildState{
LastResult: result,
FilesChangedSet: set,
}
}
func (b BuildState) WithDeployTarget(d DeployInfo) BuildState {
b.DeployInfo = d
return b
}
func (b BuildState) LastImageAsString() string {
img := b.LastResult.Image
if img == nil {
return ""
}
return img.String()
}
// Return the files changed since the last result in sorted order.
// The sorting helps ensure that this is deterministic, both for testing
// and for deterministic builds.
func (b BuildState) FilesChanged() []string {
result := make([]string, 0, len(b.FilesChangedSet))
for file, _ := range b.FilesChangedSet {
result = append(result, file)
}
sort.Strings(result)
return result
}
// Return the files changed since the last result's image in sorted order.
// The sorting helps ensure that this is deterministic, both for testing
// and for deterministic builds.
// Errors if there was no last result image.
func (b BuildState) FilesChangedSinceLastResultImage() ([]string, error) {
if !b.LastResult.HasImage() {
return nil, fmt.Errorf("No image in last result")
}
cSet := b.FilesChangedSet
rSet := b.LastResult.FilesReplacedSet
sum := make(map[string]bool, len(cSet)+len(rSet))
for k, v := range cSet {
sum[k] = v
}
for k, v := range rSet {
sum[k] = v
}
result := make([]string, 0, len(sum))
for file, _ := range sum {
result = append(result, file)
}
sort.Strings(result)
return result, nil
}
// A build state is empty if there are no previous results.
func (b BuildState) IsEmpty() bool {
return b.LastResult.IsEmpty()
}
func (b BuildState) HasImage() bool {
return b.LastResult.HasImage()
}
// Whether the image represented by this state needs to be built.
// If the image has already been built, and no files have been
// changed since then, then we can re-use the previous result.
func (b BuildState) NeedsImageBuild() bool {
lastBuildWasImgBuild := b.LastResult.HasImage() && !b.LastResult.IsInPlaceUpdate()
return !lastBuildWasImgBuild || len(b.FilesChangedSet) > 0
}
type BuildStateSet map[model.TargetID]BuildState
func (set BuildStateSet) Empty() bool {
return len(set) == 0
}
func (set BuildStateSet) FilesChanged() []string {
resultMap := map[string]bool{}
for _, state := range set {
for k := range state.FilesChangedSet {
resultMap[k] = true
}
}
result := make([]string, 0, len(resultMap))
for k := range resultMap {
result = append(result, k)
}
sort.Strings(result)
return result
}
// The information we need to find a ready container.
type DeployInfo struct {
PodID k8s.PodID
ContainerID container.ID
ContainerName container.Name
Namespace k8s.Namespace
}
func (d DeployInfo) Empty() bool {
return d == DeployInfo{}
}
// Check to see if there's a single, unambiguous Ready container
// in the given PodSet. If so, create a DeployInfo for that container.
func NewDeployInfo(iTarget model.ImageTarget, deployID model.DeployID, podSet PodSet) DeployInfo {
if podSet.Len() != 1 {
return DeployInfo{}
}
pod := podSet.MostRecentPod()
if pod.PodID == "" || pod.ContainerID == "" || pod.ContainerName == "" || !pod.ContainerReady {
return DeployInfo{}
}
if podSet.DeployID != deployID {
return DeployInfo{}
}
// Only return the pod if it matches our image.
if pod.ContainerImageRef == nil || iTarget.DeploymentRef.Name() != pod.ContainerImageRef.Name() {
return DeployInfo{}
}
return DeployInfo{
PodID: pod.PodID,
ContainerID: pod.ContainerID,
ContainerName: pod.ContainerName,
Namespace: pod.Namespace,
}
}
func NewDeployInfoFromDC(state dockercompose.State) DeployInfo {
return DeployInfo{ContainerID: state.ContainerID}
}
var BuildStateClean = BuildState{}