mirror of
https://github.com/coder/coder.git
synced 2025-07-21 01:28:49 +00:00
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.
This commit is contained in:
@ -66,6 +66,14 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
|
||||
return xerrors.Errorf("terraform version %q is too old. required >= %q", version.String(), minimumTerraformVersion.String())
|
||||
}
|
||||
|
||||
statefilePath := filepath.Join(start.Directory, "terraform.tfstate")
|
||||
if len(start.State) > 0 {
|
||||
err := os.WriteFile(statefilePath, start.State, 0600)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("write statefile %q: %w", statefilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
reader, writer := io.Pipe()
|
||||
defer reader.Close()
|
||||
defer writer.Close()
|
||||
@ -239,14 +247,7 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
|
||||
errorMessage := err.Error()
|
||||
// Terraform can fail and apply and still need to store it's state.
|
||||
// In this case, we return Complete with an explicit error message.
|
||||
state, err := terraform.Show(stream.Context())
|
||||
if err != nil {
|
||||
return xerrors.Errorf("show state: %w", err)
|
||||
}
|
||||
stateData, err := json.Marshal(state)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal state: %w", err)
|
||||
}
|
||||
stateData, _ := os.ReadFile(statefilePath)
|
||||
return stream.Send(&proto.Provision_Response{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
@ -263,7 +264,7 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
|
||||
if start.DryRun {
|
||||
resp, err = parseTerraformPlan(stream.Context(), terraform, planfilePath)
|
||||
} else {
|
||||
resp, err = parseTerraformApply(stream.Context(), terraform)
|
||||
resp, err = parseTerraformApply(stream.Context(), terraform, statefilePath)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@ -363,10 +364,26 @@ func parseTerraformPlan(ctx context.Context, terraform *tfexec.Terraform, planfi
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform) (*proto.Provision_Response, error) {
|
||||
state, err := terraform.Show(ctx)
|
||||
func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, statefilePath string) (*proto.Provision_Response, error) {
|
||||
_, err := os.Stat(statefilePath)
|
||||
statefileExisted := err == nil
|
||||
|
||||
statefile, err := os.OpenFile(statefilePath, os.O_CREATE|os.O_RDWR, 0600)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("show state file: %w", err)
|
||||
return nil, xerrors.Errorf("open statefile %q: %w", statefilePath, err)
|
||||
}
|
||||
defer statefile.Close()
|
||||
// #nosec
|
||||
cmd := exec.CommandContext(ctx, terraform.ExecPath(), "state", "pull")
|
||||
cmd.Dir = terraform.WorkingDir()
|
||||
cmd.Stdout = statefile
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("pull terraform state: %w", err)
|
||||
}
|
||||
state, err := terraform.ShowStateFile(ctx, statefilePath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("show terraform state: %w", err)
|
||||
}
|
||||
resources := make([]*proto.Resource, 0)
|
||||
if state.Values != nil {
|
||||
@ -501,15 +518,19 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform) (*pro
|
||||
}
|
||||
}
|
||||
|
||||
statefileContent, err := json.Marshal(state)
|
||||
var stateContent []byte
|
||||
// We only want to restore state if it's not hosted remotely.
|
||||
if statefileExisted {
|
||||
stateContent, err = os.ReadFile(statefilePath)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("marshal state: %w", err)
|
||||
return nil, xerrors.Errorf("read statefile %q: %w", statefilePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return &proto.Provision_Response{
|
||||
Type: &proto.Provision_Response_Complete{
|
||||
Complete: &proto.Provision_Complete{
|
||||
State: statefileContent,
|
||||
State: stateContent,
|
||||
Resources: resources,
|
||||
},
|
||||
},
|
||||
|
@ -480,9 +480,6 @@ provider "coder" {
|
||||
}
|
||||
|
||||
require.NoError(t, err)
|
||||
if !request.GetStart().DryRun {
|
||||
require.Greater(t, len(msg.GetComplete().State), 0)
|
||||
}
|
||||
|
||||
// Remove randomly generated data.
|
||||
for _, resource := range msg.GetComplete().Resources {
|
||||
|
Reference in New Issue
Block a user