Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 43c6bff

Browse files
authored
fix: Use "terraform state pull" instead of "terraform show" (#1262)
Although the terraform-exec docs don't indicate this, the result of "terraform show" isn't actually the state... it's a trimmed version of the state that excludes resource identifiers, essentially removing all state that did exist. Tests will be written to ensure Terraform state reconciliation can occur. This will happen in another PR, as dogfood is currently broken because of this.
1 parent fc642ed commit 43c6bff

File tree

2 files changed

+37
-19
lines changed

2 files changed

+37
-19
lines changed

provisioner/terraform/provision.go

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
6666
return xerrors.Errorf("terraform version %q is too old. required >= %q", version.String(), minimumTerraformVersion.String())
6767
}
6868

69+
statefilePath := filepath.Join(start.Directory, "terraform.tfstate")
70+
if len(start.State) > 0 {
71+
err := os.WriteFile(statefilePath, start.State, 0600)
72+
if err != nil {
73+
return xerrors.Errorf("write statefile %q: %w", statefilePath, err)
74+
}
75+
}
76+
6977
reader, writer := io.Pipe()
7078
defer reader.Close()
7179
defer writer.Close()
@@ -239,14 +247,7 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
239247
errorMessage := err.Error()
240248
// Terraform can fail and apply and still need to store it's state.
241249
// In this case, we return Complete with an explicit error message.
242-
state, err := terraform.Show(stream.Context())
243-
if err != nil {
244-
return xerrors.Errorf("show state: %w", err)
245-
}
246-
stateData, err := json.Marshal(state)
247-
if err != nil {
248-
return xerrors.Errorf("marshal state: %w", err)
249-
}
250+
stateData, _ := os.ReadFile(statefilePath)
250251
return stream.Send(&proto.Provision_Response{
251252
Type: &proto.Provision_Response_Complete{
252253
Complete: &proto.Provision_Complete{
@@ -263,7 +264,7 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
263264
if start.DryRun {
264265
resp, err = parseTerraformPlan(stream.Context(), terraform, planfilePath)
265266
} else {
266-
resp, err = parseTerraformApply(stream.Context(), terraform)
267+
resp, err = parseTerraformApply(stream.Context(), terraform, statefilePath)
267268
}
268269
if err != nil {
269270
return err
@@ -363,10 +364,26 @@ func parseTerraformPlan(ctx context.Context, terraform *tfexec.Terraform, planfi
363364
}, nil
364365
}
365366

366-
func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform) (*proto.Provision_Response, error) {
367-
state, err := terraform.Show(ctx)
367+
func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, statefilePath string) (*proto.Provision_Response, error) {
368+
_, err := os.Stat(statefilePath)
369+
statefileExisted := err == nil
370+
371+
statefile, err := os.OpenFile(statefilePath, os.O_CREATE|os.O_RDWR, 0600)
368372
if err != nil {
369-
return nil, xerrors.Errorf("show state file: %w", err)
373+
return nil, xerrors.Errorf("open statefile %q: %w", statefilePath, err)
374+
}
375+
defer statefile.Close()
376+
// #nosec
377+
cmd := exec.CommandContext(ctx, terraform.ExecPath(), "state", "pull")
378+
cmd.Dir = terraform.WorkingDir()
379+
cmd.Stdout = statefile
380+
err = cmd.Run()
381+
if err != nil {
382+
return nil, xerrors.Errorf("pull terraform state: %w", err)
383+
}
384+
state, err := terraform.ShowStateFile(ctx, statefilePath)
385+
if err != nil {
386+
return nil, xerrors.Errorf("show terraform state: %w", err)
370387
}
371388
resources := make([]*proto.Resource, 0)
372389
if state.Values != nil {
@@ -501,15 +518,19 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform) (*pro
501518
}
502519
}
503520

504-
statefileContent, err := json.Marshal(state)
505-
if err != nil {
506-
return nil, xerrors.Errorf("marshal state: %w", err)
521+
var stateContent []byte
522+
// We only want to restore state if it's not hosted remotely.
523+
if statefileExisted {
524+
stateContent, err = os.ReadFile(statefilePath)
525+
if err != nil {
526+
return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err)
527+
}
507528
}
508529

509530
return &proto.Provision_Response{
510531
Type: &proto.Provision_Response_Complete{
511532
Complete: &proto.Provision_Complete{
512-
State: statefileContent,
533+
State: stateContent,
513534
Resources: resources,
514535
},
515536
},

provisioner/terraform/provision_test.go

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,9 +480,6 @@ provider "coder" {
480480
}
481481

482482
require.NoError(t, err)
483-
if !request.GetStart().DryRun {
484-
require.Greater(t, len(msg.GetComplete().State), 0)
485-
}
486483

487484
// Remove randomly generated data.
488485
for _, resource := range msg.GetComplete().Resources {

0 commit comments

Comments
 (0)