-
Notifications
You must be signed in to change notification settings - Fork 290
/
extractors.go
143 lines (124 loc) · 4.41 KB
/
extractors.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
package engine
import (
"fmt"
"github.com/pkg/errors"
"github.com/windmilleng/tilt/internal/sliceutils"
"github.com/windmilleng/tilt/internal/store"
"github.com/windmilleng/tilt/pkg/model"
)
// Extract the targets that we can apply, or nil if we can't apply these targets.
func extractImageAndK8sTargets(specs []model.TargetSpec) (iTargets []model.ImageTarget, kTargets []model.K8sTarget) {
for _, s := range specs {
switch s := s.(type) {
case model.ImageTarget:
iTargets = append(iTargets, s)
case model.K8sTarget:
kTargets = append(kTargets, s)
default:
return nil, nil
}
}
return iTargets, kTargets
}
// If there are images that can be updated in-place in a container, return
// a state tree of what needs to be updated.
func extractImageTargetsForLiveUpdates(specs []model.TargetSpec, stateSet store.BuildStateSet) ([]liveUpdateStateTree, error) {
g, err := model.NewTargetGraph(specs)
if err != nil {
return nil, errors.Wrap(err, "extractImageTargetsForLiveUpdates")
}
if !g.IsSingleSourceDAG() {
return nil, fmt.Errorf("Cannot extract live updates on this build graph structure")
}
result := make([]liveUpdateStateTree, 0)
deployedImages := g.DeployedImages()
for _, iTarget := range deployedImages {
state := stateSet[iTarget.ID()]
if state.IsEmpty() {
return nil, SilentRedirectToNextBuilderf("In-place build does not support initial deploy")
}
hasFileChangesIDs, err := hasFileChangesTree(g, iTarget, stateSet)
if err != nil {
return nil, errors.Wrap(err, "extractImageTargetsForLiveUpdates")
}
// If this image and none of its dependencies need a rebuild,
// we can skip it.
if len(hasFileChangesIDs) == 0 {
continue
}
fbInfo := iTarget.AnyFastBuildInfo()
luInfo := iTarget.AnyLiveUpdateInfo()
if fbInfo.Empty() && luInfo.Empty() {
return nil, SilentRedirectToNextBuilderf("In-place build requires either FastBuild or LiveUpdate")
}
// Now that we have fast build information, we know this CAN be updated in
// a container(s). Check to see if we have enough information about the
// container(s) that would need to be updated.
if len(state.RunningContainers) == 0 {
return nil, RedirectToNextBuilderInfof("don't have info for running container of image %q (often a result of the deployment not yet being ready)", iTarget.DeploymentRef.String())
}
filesChanged, err := filesChangedTree(g, iTarget, stateSet)
if err != nil {
return nil, errors.Wrap(err, "extractImageTargetsForLiveUpdates")
}
result = append(result, liveUpdateStateTree{
iTarget: iTarget,
filesChanged: filesChanged,
iTargetState: state,
hasFileChangesIDs: hasFileChangesIDs,
})
}
return result, nil
}
// Returns true if the given image is deployed to one of the given k8s targets.
// Note that some images are injected into other images, so may never be deployed.
func isImageDeployedToK8s(iTarget model.ImageTarget, kTarget model.K8sTarget) bool {
id := iTarget.ID()
for _, depID := range kTarget.DependencyIDs() {
if depID == id {
return true
}
}
return false
}
// Returns true if the given image is deployed to one of the given docker-compose targets.
// Note that some images are injected into other images, so may never be deployed.
func isImageDeployedToDC(iTarget model.ImageTarget, dcTarget model.DockerComposeTarget) bool {
id := iTarget.ID()
for _, depID := range dcTarget.DependencyIDs() {
if depID == id {
return true
}
}
return false
}
// Given a target, return all the target IDs in its tree of dependencies that
// have changed files.
func hasFileChangesTree(g model.TargetGraph, target model.TargetSpec, stateSet store.BuildStateSet) ([]model.TargetID, error) {
result := []model.TargetID{}
err := g.VisitTree(target, func(current model.TargetSpec) error {
state := stateSet[current.ID()]
if len(state.FilesChangedSet) > 0 {
result = append(result, current.ID())
}
return nil
})
if err != nil {
return nil, err
}
return result, nil
}
// Given a target, return all the files in its tree of dependencies that have
// changed.
func filesChangedTree(g model.TargetGraph, target model.TargetSpec, stateSet store.BuildStateSet) ([]string, error) {
result := []string{}
err := g.VisitTree(target, func(current model.TargetSpec) error {
state := stateSet[current.ID()]
result = append(result, state.FilesChanged()...)
return nil
})
if err != nil {
return nil, err
}
return sliceutils.DedupedAndSorted(result), nil
}