Skip to content

Commit b8c45d5

Browse files
committed
provisioner: split Plan and Apply steps
1 parent 71601f4 commit b8c45d5

File tree

5 files changed

+453
-238
lines changed

5 files changed

+453
-238
lines changed

provisioner/terraform/executor.go

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/json"
88
"fmt"
99
"io"
10+
"io/ioutil"
1011
"os"
1112
"os/exec"
1213
"path/filepath"
@@ -238,10 +239,15 @@ func (e executor) plan(ctx, killCtx context.Context, env, vars []string, logr lo
238239
if err != nil {
239240
return nil, err
240241
}
242+
planFileByt, err := os.ReadFile(planfilePath)
243+
if err != nil {
244+
return nil, err
245+
}
241246
return &proto.Provision_Response{
242247
Type: &proto.Provision_Response_Complete{
243248
Complete: &proto.Provision_Complete{
244249
Resources: resources,
250+
Plan: planFileByt,
245251
},
246252
},
247253
}, nil
@@ -292,21 +298,26 @@ func (e executor) graph(ctx, killCtx context.Context) (string, error) {
292298
}
293299

294300
// revive:disable-next-line:flag-parameter
295-
func (e executor) apply(ctx, killCtx context.Context, env, vars []string, logr logSink, destroy bool,
301+
func (e executor) apply(
302+
ctx, killCtx context.Context, plan []byte, logr logSink,
296303
) (*proto.Provision_Response, error) {
304+
planFile, err := ioutil.TempFile("", "coder-terrafrom-plan")
305+
if err != nil {
306+
return nil, xerrors.Errorf("create plan file: %w", err)
307+
}
308+
_, err = planFile.Write(plan)
309+
if err != nil {
310+
return nil, xerrors.Errorf("write plan file: %w", err)
311+
}
312+
defer os.Remove(planFile.Name())
313+
297314
args := []string{
298315
"apply",
299316
"-no-color",
300317
"-auto-approve",
301318
"-input=false",
302319
"-json",
303-
"-refresh=true",
304-
}
305-
if destroy {
306-
args = append(args, "-destroy")
307-
}
308-
for _, variable := range vars {
309-
args = append(args, "-var", variable)
320+
planFile.Name(),
310321
}
311322

312323
outWriter, doneOut := provisionLogWriter(logr)
@@ -318,7 +329,7 @@ func (e executor) apply(ctx, killCtx context.Context, env, vars []string, logr l
318329
<-doneErr
319330
}()
320331

321-
err := e.execWriteOutput(ctx, killCtx, args, env, outWriter, errWriter)
332+
err = e.execWriteOutput(ctx, killCtx, args, nil, outWriter, errWriter)
322333
if err != nil {
323334
return nil, xerrors.Errorf("terraform apply: %w", err)
324335
}

provisioner/terraform/provision.go

Lines changed: 53 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,21 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
2323
if request.GetCancel() != nil {
2424
return nil
2525
}
26-
// We expect the first message is start!
27-
if request.GetStart() == nil {
26+
27+
var (
28+
applyRequest = request.GetApply()
29+
planRequest = request.GetPlan()
30+
)
31+
32+
var (
33+
config *proto.Provision_Config
34+
)
35+
if applyRequest == nil && planRequest == nil {
2836
return nil
37+
} else if applyRequest != nil {
38+
config = applyRequest.Config
39+
} else if planRequest != nil {
40+
config = planRequest.Config
2941
}
3042

3143
// Create a context for graceful cancellation bound to the stream
@@ -73,17 +85,16 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
7385
logger: s.logger.Named("execution_logs"),
7486
stream: stream,
7587
}
76-
start := request.GetStart()
7788

78-
e := s.executor(start.Directory)
89+
e := s.executor(config.Directory)
7990
if err = e.checkMinVersion(ctx); err != nil {
8091
return err
8192
}
8293
logTerraformEnvVars(sink)
8394

84-
statefilePath := filepath.Join(start.Directory, "terraform.tfstate")
85-
if len(start.State) > 0 {
86-
err = os.WriteFile(statefilePath, start.State, 0o600)
95+
statefilePath := filepath.Join(config.Directory, "terraform.tfstate")
96+
if len(config.State) > 0 {
97+
err = os.WriteFile(statefilePath, config.State, 0o600)
8798
if err != nil {
8899
return xerrors.Errorf("write statefile %q: %w", statefilePath, err)
89100
}
@@ -94,7 +105,7 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
94105
// e.g. bad template param values and cannot be deleted. This is just for
95106
// contingency, in the future we will try harder to prevent workspaces being
96107
// broken this hard.
97-
if start.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY && len(start.State) == 0 {
108+
if config.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY && len(config.State) == 0 {
98109
_ = stream.Send(&proto.Provision_Response{
99110
Type: &proto.Provision_Response_Log{
100111
Log: &proto.Log{
@@ -127,24 +138,23 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
127138
}
128139
s.logger.Debug(ctx, "ran initialization")
129140

130-
env, err := provisionEnv(start)
131-
if err != nil {
132-
return err
133-
}
134-
vars, err := provisionVars(start)
135-
if err != nil {
136-
return err
137-
}
138141
var resp *proto.Provision_Response
139-
if start.DryRun {
140-
resp, err = e.plan(ctx, killCtx, env, vars, sink,
141-
start.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY)
142-
} else {
143-
resp, err = e.apply(ctx, killCtx, env, vars, sink,
144-
start.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY)
145-
}
146-
if err != nil {
147-
if start.DryRun {
142+
if planRequest != nil {
143+
env, err := planEnv(planRequest)
144+
if err != nil {
145+
return err
146+
}
147+
148+
vars, err := planVars(planRequest)
149+
if err != nil {
150+
return err
151+
}
152+
153+
resp, err = e.plan(
154+
ctx, killCtx, env, vars, sink,
155+
config.Metadata.WorkspaceTransition == proto.WorkspaceTransition_DESTROY,
156+
)
157+
if err != nil {
148158
if ctx.Err() != nil {
149159
return stream.Send(&proto.Provision_Response{
150160
Type: &proto.Provision_Response_Complete{
@@ -156,6 +166,13 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
156166
}
157167
return xerrors.Errorf("plan terraform: %w", err)
158168
}
169+
return stream.Send(resp)
170+
}
171+
// Must be apply
172+
resp, err = e.apply(
173+
ctx, killCtx, applyRequest.Plan, sink,
174+
)
175+
if err != nil {
159176
errorMessage := err.Error()
160177
// Terraform can fail and apply and still need to store it's state.
161178
// In this case, we return Complete with an explicit error message.
@@ -169,13 +186,12 @@ func (s *server) Provision(stream proto.DRPCProvisioner_ProvisionStream) error {
169186
},
170187
})
171188
}
172-
173189
return stream.Send(resp)
174190
}
175191

176-
func provisionVars(start *proto.Provision_Start) ([]string, error) {
192+
func planVars(plan *proto.Provision_Plan) ([]string, error) {
177193
vars := []string{}
178-
for _, param := range start.ParameterValues {
194+
for _, param := range plan.ParameterValues {
179195
switch param.DestinationScheme {
180196
case proto.ParameterDestination_ENVIRONMENT_VARIABLE:
181197
continue
@@ -188,21 +204,21 @@ func provisionVars(start *proto.Provision_Start) ([]string, error) {
188204
return vars, nil
189205
}
190206

191-
func provisionEnv(start *proto.Provision_Start) ([]string, error) {
207+
func planEnv(plan *proto.Provision_Plan) ([]string, error) {
192208
env := safeEnviron()
193209
env = append(env,
194-
"CODER_AGENT_URL="+start.Metadata.CoderUrl,
195-
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(start.Metadata.WorkspaceTransition.String()),
196-
"CODER_WORKSPACE_NAME="+start.Metadata.WorkspaceName,
197-
"CODER_WORKSPACE_OWNER="+start.Metadata.WorkspaceOwner,
198-
"CODER_WORKSPACE_OWNER_EMAIL="+start.Metadata.WorkspaceOwnerEmail,
199-
"CODER_WORKSPACE_ID="+start.Metadata.WorkspaceId,
200-
"CODER_WORKSPACE_OWNER_ID="+start.Metadata.WorkspaceOwnerId,
210+
"CODER_AGENT_URL="+plan.Config.Metadata.CoderUrl,
211+
"CODER_WORKSPACE_TRANSITION="+strings.ToLower(plan.Config.Metadata.WorkspaceTransition.String()),
212+
"CODER_WORKSPACE_NAME="+plan.Config.Metadata.WorkspaceName,
213+
"CODER_WORKSPACE_OWNER="+plan.Config.Metadata.WorkspaceOwner,
214+
"CODER_WORKSPACE_OWNER_EMAIL="+plan.Config.Metadata.WorkspaceOwnerEmail,
215+
"CODER_WORKSPACE_ID="+plan.Config.Metadata.WorkspaceId,
216+
"CODER_WORKSPACE_OWNER_ID="+plan.Config.Metadata.WorkspaceOwnerId,
201217
)
202218
for key, value := range provisionersdk.AgentScriptEnv() {
203219
env = append(env, key+"="+value)
204220
}
205-
for _, param := range start.ParameterValues {
221+
for _, param := range plan.ParameterValues {
206222
switch param.DestinationScheme {
207223
case proto.ParameterDestination_ENVIRONMENT_VARIABLE:
208224
env = append(env, fmt.Sprintf("%s=%s", param.Name, param.Value))

provisioner/terraform/provision_test.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,16 +113,17 @@ func TestProvision_Cancel(t *testing.T) {
113113
response, err := api.Provision(ctx)
114114
require.NoError(t, err)
115115
err = response.Send(&proto.Provision_Request{
116-
Type: &proto.Provision_Request_Start{
117-
Start: &proto.Provision_Start{
118-
Directory: dir,
119-
DryRun: false,
116+
Type: &proto.Provision_Request_Plan{
117+
Plan: &proto.Provision_Plan{
118+
Config: &proto.Provision_Config{
119+
Directory: dir,
120+
Metadata: &proto.Provision_Metadata{},
121+
},
120122
ParameterValues: []*proto.ParameterValue{{
121123
DestinationScheme: proto.ParameterDestination_PROVISIONER_VARIABLE,
122124
Name: "A",
123125
Value: "example",
124126
}},
125-
Metadata: &proto.Provision_Metadata{},
126127
},
127128
},
128129
})
@@ -175,7 +176,7 @@ func TestProvision(t *testing.T) {
175176
testCases := []struct {
176177
Name string
177178
Files map[string]string
178-
Request *proto.Provision_Request
179+
Request *proto.Provision_Plan
179180
// Response may be nil to not check the response.
180181
Response *proto.Provision_Response
181182
// If ErrorContains is not empty, then response.Recv() should return an

0 commit comments

Comments
 (0)