-
Notifications
You must be signed in to change notification settings - Fork 290
/
image_and_cache_builder.go
188 lines (157 loc) · 5.67 KB
/
image_and_cache_builder.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
package engine
import (
"context"
"fmt"
"github.com/docker/distribution/reference"
"github.com/windmilleng/tilt/internal/build"
"github.com/windmilleng/tilt/internal/dockerfile"
"github.com/windmilleng/tilt/internal/ignore"
"github.com/windmilleng/tilt/internal/logger"
"github.com/windmilleng/tilt/internal/model"
"github.com/windmilleng/tilt/internal/store"
)
type imageAndCacheBuilder struct {
ib build.ImageBuilder
cb build.CacheBuilder
custb build.CustomBuilder
updateMode UpdateMode
}
func NewImageAndCacheBuilder(ib build.ImageBuilder, cb build.CacheBuilder, custb build.CustomBuilder, updateMode UpdateMode) *imageAndCacheBuilder {
return &imageAndCacheBuilder{
ib: ib,
cb: cb,
custb: custb,
updateMode: updateMode,
}
}
func (icb *imageAndCacheBuilder) Build(ctx context.Context, iTarget model.ImageTarget, state store.BuildState, ps *build.PipelineState) (reference.NamedTagged, error) {
var n reference.NamedTagged
userFacingRefName := iTarget.ConfigurationRef.String()
refToBuild := iTarget.DeploymentRef
cacheInputs := icb.createCacheInputs(iTarget)
cacheRef, err := icb.cb.FetchCache(ctx, cacheInputs)
if err != nil {
return nil, err
}
switch bd := iTarget.BuildDetails.(type) {
case model.DockerBuild:
ps.StartPipelineStep(ctx, "Building Dockerfile: [%s]", userFacingRefName)
defer ps.EndPipelineStep(ctx)
df := icb.dockerfile(iTarget, cacheRef)
ref, err := icb.ib.BuildDockerfile(ctx, ps, refToBuild, df, bd.BuildPath, ignore.CreateBuildContextFilter(iTarget), bd.BuildArgs)
if err != nil {
return nil, err
}
n = ref
go icb.maybeCreateCacheFrom(ctx, cacheInputs, ref, state, iTarget, cacheRef)
case model.FastBuild:
if !state.HasImage() || icb.updateMode == UpdateModeNaive {
// No existing image to build off of, need to build from scratch
ps.StartPipelineStep(ctx, "Building from scratch: [%s]", userFacingRefName)
defer ps.EndPipelineStep(ctx)
df := icb.baseDockerfile(bd, cacheRef, iTarget.CachePaths())
runs := bd.Runs
ref, err := icb.ib.BuildImageFromScratch(ctx, ps, refToBuild, df, bd.Syncs, ignore.CreateBuildContextFilter(iTarget), runs, bd.Entrypoint)
if err != nil {
return nil, err
}
n = ref
go icb.maybeCreateCacheFrom(ctx, cacheInputs, ref, state, iTarget, cacheRef)
} else {
// We have an existing image, can do an iterative build
changed, err := state.FilesChangedSinceLastResultImage()
if err != nil {
return nil, err
}
cf, err := build.FilesToPathMappings(changed, bd.Syncs)
if err != nil {
return nil, err
}
ps.StartPipelineStep(ctx, "Building from existing: [%s]", userFacingRefName)
defer ps.EndPipelineStep(ctx)
runs := bd.Runs
ref, err := icb.ib.BuildImageFromExisting(ctx, ps, state.LastResult.Image, cf, ignore.CreateBuildContextFilter(iTarget), runs)
if err != nil {
return nil, err
}
n = ref
}
case model.CustomBuild:
ps.StartPipelineStep(ctx, "Building Dockerfile: [%s]", userFacingRefName)
defer ps.EndPipelineStep(ctx)
ref, err := icb.custb.Build(ctx, refToBuild, bd.Command, bd.Tag)
if err != nil {
return nil, err
}
n = ref
default:
// Theoretically this should never trip b/c we `validate` the manifest beforehand...?
// If we get here, something is very wrong.
return nil, fmt.Errorf("image %q has no valid buildDetails (neither DockerBuildInfo nor FastBuildInfo)", iTarget.ConfigurationRef)
}
return n, nil
}
func (icb *imageAndCacheBuilder) dockerfile(image model.ImageTarget, cacheRef reference.NamedTagged) dockerfile.Dockerfile {
df := dockerfile.Dockerfile(image.DockerBuildInfo().Dockerfile)
if cacheRef == nil {
return df
}
if len(image.CachePaths()) == 0 {
return df
}
_, restDf, ok := df.SplitIntoBaseDockerfile()
if !ok {
return df
}
// Replace all the lines before the ADD with a load from the Tilt cache.
return dockerfile.FromExisting(cacheRef).
WithLabel(build.CacheImage, "0"). // sadly there's no way to unset a label :sob:
Append(restDf)
}
func (icb *imageAndCacheBuilder) baseDockerfile(fbInfo model.FastBuild,
cacheRef build.CacheRef, cachePaths []string) dockerfile.Dockerfile {
df := dockerfile.Dockerfile(fbInfo.BaseDockerfile)
if cacheRef == nil {
return df
}
if len(cachePaths) == 0 {
return df
}
// Use the cache as the new base dockerfile.
return dockerfile.FromExisting(cacheRef).
WithLabel(build.CacheImage, "0") // sadly there's no way to unset a label :sob:
}
func (icb *imageAndCacheBuilder) createCacheInputs(iTarget model.ImageTarget) build.CacheInputs {
baseDockerfile := dockerfile.Dockerfile(iTarget.TopFastBuildInfo().BaseDockerfile)
if dbInfo, ok := iTarget.BuildDetails.(model.DockerBuild); ok {
df := dockerfile.Dockerfile(dbInfo.Dockerfile)
var ok bool
baseDockerfile, _, ok = df.SplitIntoBaseDockerfile()
if !ok {
return build.CacheInputs{}
}
}
return build.CacheInputs{
Ref: iTarget.ConfigurationRef.AsNamedOnly(),
CachePaths: iTarget.CachePaths(),
BaseDockerfile: baseDockerfile,
}
}
func (icb *imageAndCacheBuilder) maybeCreateCacheFrom(ctx context.Context, cacheInputs build.CacheInputs, sourceRef reference.NamedTagged, state store.BuildState, image model.ImageTarget, oldCacheRef reference.NamedTagged) {
// Only create the cache the first time we build the image.
if !state.LastResult.IsEmpty() {
return
}
// Only create the cache if there is no existing cache
if oldCacheRef != nil {
return
}
var buildArgs model.DockerBuildArgs
if dbInfo, ok := image.BuildDetails.(model.DockerBuild); ok {
buildArgs = dbInfo.BuildArgs
}
err := icb.cb.CreateCacheFrom(ctx, cacheInputs, sourceRef, buildArgs)
if err != nil {
logger.Get(ctx).Debugf("Could not create cache: %v", err)
}
}