-
Notifications
You must be signed in to change notification settings - Fork 288
/
extractors.go
153 lines (132 loc) · 4.86 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
144
145
146
147
148
149
150
151
152
153
package engine
import (
"fmt"
"github.com/pkg/errors"
"github.com/windmilleng/tilt/internal/container"
"github.com/windmilleng/tilt/internal/engine/buildcontrol"
"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, buildcontrol.SilentRedirectToNextBuilderf("In-place build does not support initial deploy")
}
if state.NeedsForceUpdate {
return nil, buildcontrol.SilentRedirectToNextBuilderf("Force update (triggered manually, not automatically, with no dirty files)")
}
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
}
luInfo := iTarget.LiveUpdateInfo()
if luInfo.Empty() {
return nil, buildcontrol.SilentRedirectToNextBuilderf("LiveUpdate requires that LiveUpdate details be specified")
}
if state.RunningContainerError != nil {
return nil, buildcontrol.RedirectToNextBuilderInfof("Error retrieving container info: %v", state.RunningContainerError)
}
// Now that we have live update 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, buildcontrol.RedirectToNextBuilderInfof("Don't have info for running container of image %q "+
"(often a result of the deployment not yet being ready)", container.FamiliarString(iTarget.Refs.ClusterRef()))
}
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
}