@@ -23,27 +23,15 @@ import (
2323"github.com/coder/coder/provisionersdk/proto"
2424)
2525
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-
4026type executor struct {
27+ mut * sync.Mutex
4128binaryPath 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
4432}
4533
46- func (e executor )basicEnv () []string {
34+ func (e * executor )basicEnv () []string {
4735// Required for "terraform init" to find "git" to
4836// clone Terraform modules.
4937env := safeEnviron ()
@@ -55,7 +43,8 @@ func (e executor) basicEnv() []string {
5543return env
5644}
5745
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 ) {
5948defer func () {
6049closeErr := stdOutWriter .Close ()
6150if err == nil && closeErr != nil {
@@ -98,7 +87,8 @@ func (e executor) execWriteOutput(ctx, killCtx context.Context, args, env []stri
9887return cmd .Wait ()
9988}
10089
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 {
10292if ctx .Err ()!= nil {
10393return ctx .Err ()
10494}
@@ -133,7 +123,7 @@ func (e executor) execParseJSON(ctx, killCtx context.Context, args, env []string
133123return nil
134124}
135125
136- func (e executor )checkMinVersion (ctx context.Context )error {
126+ func (e * executor )checkMinVersion (ctx context.Context )error {
137127v ,err := e .version (ctx )
138128if err != nil {
139129return err
@@ -147,7 +137,8 @@ func (e executor) checkMinVersion(ctx context.Context) error {
147137return nil
148138}
149139
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 ) {
151142return versionFromBinaryPath (ctx ,e .binaryPath )
152143}
153144
@@ -177,7 +168,10 @@ func versionFromBinaryPath(ctx context.Context, binaryPath string) (*version.Ver
177168return version .NewVersion (vj .Version )
178169}
179170
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+
181175outWriter ,doneOut := logWriter (logr ,proto .LogLevel_DEBUG )
182176errWriter ,doneErr := logWriter (logr ,proto .LogLevel_ERROR )
183177defer func () {
@@ -193,23 +187,14 @@ func (e executor) init(ctx, killCtx context.Context, logr logSink) error {
193187"-input=false" ,
194188}
195189
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-
208190return e .execWriteOutput (ctx ,killCtx ,args ,e .basicEnv (),outWriter ,errWriter )
209191}
210192
211193// 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+
213198planfilePath := filepath .Join (e .workdir ,"terraform.tfplan" )
214199args := []string {
215200"plan" ,
@@ -257,7 +242,8 @@ func (e executor) plan(ctx, killCtx context.Context, env, vars []string, logr lo
257242},nil
258243}
259244
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 ) {
261247plan ,err := e .showPlan (ctx ,killCtx ,planfilePath )
262248if err != nil {
263249return nil ,xerrors .Errorf ("show terraform plan file: %w" ,err )
@@ -270,14 +256,16 @@ func (e executor) planResources(ctx, killCtx context.Context, planfilePath strin
270256return ConvertResources (plan .PlannedValues .RootModule ,rawGraph )
271257}
272258
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 ) {
274261args := []string {"show" ,"-json" ,"-no-color" ,planfilePath }
275262p := new (tfjson.Plan )
276263err := e .execParseJSON (ctx ,killCtx ,args ,e .basicEnv (),p )
277264return p ,err
278265}
279266
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 ) {
281269if ctx .Err ()!= nil {
282270return "" ,ctx .Err ()
283271}
@@ -302,9 +290,12 @@ func (e executor) graph(ctx, killCtx context.Context) (string, error) {
302290}
303291
304292// revive:disable-next-line:flag-parameter
305- func (e executor )apply (
293+ func (e * executor )apply (
306294ctx ,killCtx context.Context ,plan []byte ,env []string ,logr logSink ,
307295) (* proto.Provision_Response ,error ) {
296+ e .mut .Lock ()
297+ defer e .mut .Unlock ()
298+
308299planFile ,err := ioutil .TempFile ("" ,"coder-terrafrom-plan" )
309300if err != nil {
310301return nil ,xerrors .Errorf ("create plan file: %w" ,err )
@@ -356,7 +347,8 @@ func (e executor) apply(
356347},nil
357348}
358349
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 ) {
360352state ,err := e .state (ctx ,killCtx )
361353if err != nil {
362354return nil ,err
@@ -375,7 +367,8 @@ func (e executor) stateResources(ctx, killCtx context.Context) ([]*proto.Resourc
375367return resources ,nil
376368}
377369
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 ) {
379372args := []string {"show" ,"-json" ,"-no-color" }
380373state := & tfjson.State {}
381374err := e .execParseJSON (ctx ,killCtx ,args ,e .basicEnv (),state )