@@ -23,27 +23,15 @@ import (
23
23
"github.com/coder/coder/provisionersdk/proto"
24
24
)
25
25
26
- // initMut is a global mutex that protects the Terraform cache directory from
27
- // concurrent usage by path. Only `terraform init` commands are guarded by this
28
- // mutex.
29
- //
30
- // When cache path is set, we must protect against multiple calls to
31
- // `terraform init`.
32
- //
33
- // From the Terraform documentation:
34
- //
35
- //Note: The plugin cache directory is not guaranteed to be concurrency
36
- //safe. The provider installer's behavior in environments with multiple
37
- //terraform init calls is undefined.
38
- var initMut = & sync.Mutex {}
39
-
40
26
type executor struct {
27
+ mut * sync.Mutex
41
28
binaryPath string
42
- cachePath string
43
- workdir string
29
+ // cachePath and workdir must not be used by multiple processes at once.
30
+ cachePath string
31
+ workdir string
44
32
}
45
33
46
- func (e executor )basicEnv () []string {
34
+ func (e * executor )basicEnv () []string {
47
35
// Required for "terraform init" to find "git" to
48
36
// clone Terraform modules.
49
37
env := safeEnviron ()
@@ -55,7 +43,8 @@ func (e executor) basicEnv() []string {
55
43
return env
56
44
}
57
45
58
- func (e executor )execWriteOutput (ctx ,killCtx context.Context ,args ,env []string ,stdOutWriter ,stdErrWriter io.WriteCloser ) (err error ) {
46
+ // execWriteOutput must only be called while the lock is held.
47
+ func (e * executor )execWriteOutput (ctx ,killCtx context.Context ,args ,env []string ,stdOutWriter ,stdErrWriter io.WriteCloser ) (err error ) {
59
48
defer func () {
60
49
closeErr := stdOutWriter .Close ()
61
50
if err == nil && closeErr != nil {
@@ -98,7 +87,8 @@ func (e executor) execWriteOutput(ctx, killCtx context.Context, args, env []stri
98
87
return cmd .Wait ()
99
88
}
100
89
101
- func (e executor )execParseJSON (ctx ,killCtx context.Context ,args ,env []string ,v interface {})error {
90
+ // execParseJSON must only be called while the lock is held.
91
+ func (e * executor )execParseJSON (ctx ,killCtx context.Context ,args ,env []string ,v interface {})error {
102
92
if ctx .Err ()!= nil {
103
93
return ctx .Err ()
104
94
}
@@ -133,7 +123,7 @@ func (e executor) execParseJSON(ctx, killCtx context.Context, args, env []string
133
123
return nil
134
124
}
135
125
136
- func (e executor )checkMinVersion (ctx context.Context )error {
126
+ func (e * executor )checkMinVersion (ctx context.Context )error {
137
127
v ,err := e .version (ctx )
138
128
if err != nil {
139
129
return err
@@ -147,7 +137,8 @@ func (e executor) checkMinVersion(ctx context.Context) error {
147
137
return nil
148
138
}
149
139
150
- func (e executor )version (ctx context.Context ) (* version.Version ,error ) {
140
+ // version doesn't need the lock because it doesn't read or write to any state.
141
+ func (e * executor )version (ctx context.Context ) (* version.Version ,error ) {
151
142
return versionFromBinaryPath (ctx ,e .binaryPath )
152
143
}
153
144
@@ -177,7 +168,10 @@ func versionFromBinaryPath(ctx context.Context, binaryPath string) (*version.Ver
177
168
return version .NewVersion (vj .Version )
178
169
}
179
170
180
- func (e executor )init (ctx ,killCtx context.Context ,logr logSink )error {
171
+ func (e * executor )init (ctx ,killCtx context.Context ,logr logSink )error {
172
+ e .mut .Lock ()
173
+ defer e .mut .Unlock ()
174
+
181
175
outWriter ,doneOut := logWriter (logr ,proto .LogLevel_DEBUG )
182
176
errWriter ,doneErr := logWriter (logr ,proto .LogLevel_ERROR )
183
177
defer func () {
@@ -193,23 +187,14 @@ func (e executor) init(ctx, killCtx context.Context, logr logSink) error {
193
187
"-input=false" ,
194
188
}
195
189
196
- // When cache path is set, we must protect against multiple calls
197
- // to `terraform init`.
198
- //
199
- // From the Terraform documentation:
200
- // Note: The plugin cache directory is not guaranteed to be
201
- // concurrency safe. The provider installer's behavior in
202
- // environments with multiple terraform init calls is undefined.
203
- if e .cachePath != "" {
204
- initMut .Lock ()
205
- defer initMut .Unlock ()
206
- }
207
-
208
190
return e .execWriteOutput (ctx ,killCtx ,args ,e .basicEnv (),outWriter ,errWriter )
209
191
}
210
192
211
193
// revive:disable-next-line:flag-parameter
212
- func (e executor )plan (ctx ,killCtx context.Context ,env ,vars []string ,logr logSink ,destroy bool ) (* proto.Provision_Response ,error ) {
194
+ func (e * executor )plan (ctx ,killCtx context.Context ,env ,vars []string ,logr logSink ,destroy bool ) (* proto.Provision_Response ,error ) {
195
+ e .mut .Lock ()
196
+ defer e .mut .Unlock ()
197
+
213
198
planfilePath := filepath .Join (e .workdir ,"terraform.tfplan" )
214
199
args := []string {
215
200
"plan" ,
@@ -257,7 +242,8 @@ func (e executor) plan(ctx, killCtx context.Context, env, vars []string, logr lo
257
242
},nil
258
243
}
259
244
260
- func (e executor )planResources (ctx ,killCtx context.Context ,planfilePath string ) ([]* proto.Resource ,error ) {
245
+ // planResources must only be called while the lock is held.
246
+ func (e * executor )planResources (ctx ,killCtx context.Context ,planfilePath string ) ([]* proto.Resource ,error ) {
261
247
plan ,err := e .showPlan (ctx ,killCtx ,planfilePath )
262
248
if err != nil {
263
249
return nil ,xerrors .Errorf ("show terraform plan file: %w" ,err )
@@ -270,14 +256,16 @@ func (e executor) planResources(ctx, killCtx context.Context, planfilePath strin
270
256
return ConvertResources (plan .PlannedValues .RootModule ,rawGraph )
271
257
}
272
258
273
- func (e executor )showPlan (ctx ,killCtx context.Context ,planfilePath string ) (* tfjson.Plan ,error ) {
259
+ // showPlan must only be called while the lock is held.
260
+ func (e * executor )showPlan (ctx ,killCtx context.Context ,planfilePath string ) (* tfjson.Plan ,error ) {
274
261
args := []string {"show" ,"-json" ,"-no-color" ,planfilePath }
275
262
p := new (tfjson.Plan )
276
263
err := e .execParseJSON (ctx ,killCtx ,args ,e .basicEnv (),p )
277
264
return p ,err
278
265
}
279
266
280
- func (e executor )graph (ctx ,killCtx context.Context ) (string ,error ) {
267
+ // graph must only be called while the lock is held.
268
+ func (e * executor )graph (ctx ,killCtx context.Context ) (string ,error ) {
281
269
if ctx .Err ()!= nil {
282
270
return "" ,ctx .Err ()
283
271
}
@@ -302,9 +290,12 @@ func (e executor) graph(ctx, killCtx context.Context) (string, error) {
302
290
}
303
291
304
292
// revive:disable-next-line:flag-parameter
305
- func (e executor )apply (
293
+ func (e * executor )apply (
306
294
ctx ,killCtx context.Context ,plan []byte ,env []string ,logr logSink ,
307
295
) (* proto.Provision_Response ,error ) {
296
+ e .mut .Lock ()
297
+ defer e .mut .Unlock ()
298
+
308
299
planFile ,err := ioutil .TempFile ("" ,"coder-terrafrom-plan" )
309
300
if err != nil {
310
301
return nil ,xerrors .Errorf ("create plan file: %w" ,err )
@@ -356,7 +347,8 @@ func (e executor) apply(
356
347
},nil
357
348
}
358
349
359
- func (e executor )stateResources (ctx ,killCtx context.Context ) ([]* proto.Resource ,error ) {
350
+ // stateResources must only be called while the lock is held.
351
+ func (e * executor )stateResources (ctx ,killCtx context.Context ) ([]* proto.Resource ,error ) {
360
352
state ,err := e .state (ctx ,killCtx )
361
353
if err != nil {
362
354
return nil ,err
@@ -375,7 +367,8 @@ func (e executor) stateResources(ctx, killCtx context.Context) ([]*proto.Resourc
375
367
return resources ,nil
376
368
}
377
369
378
- func (e executor )state (ctx ,killCtx context.Context ) (* tfjson.State ,error ) {
370
+ // state must only be called while the lock is held.
371
+ func (e * executor )state (ctx ,killCtx context.Context ) (* tfjson.State ,error ) {
379
372
args := []string {"show" ,"-json" ,"-no-color" }
380
373
state := & tfjson.State {}
381
374
err := e .execParseJSON (ctx ,killCtx ,args ,e .basicEnv (),state )