@@ -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 )
0 commit comments