Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commitadb7d20

Browse files
authored
feat: skip terraform destroy if there is no state when deleting (#1594)
1 parenta03615a commitadb7d20

File tree

2 files changed

+124
-15
lines changed

2 files changed

+124
-15
lines changed

‎provisioner/terraform/provision.go

Lines changed: 77 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"os"
1010
"os/exec"
1111
"path/filepath"
12+
"regexp"
1213
"runtime"
1314
"strings"
1415

@@ -22,6 +23,11 @@ import (
2223
"github.com/coder/coder/provisionersdk/proto"
2324
)
2425

26+
var (
27+
// noStateRegex is matched against the output from `terraform state show`
28+
noStateRegex=regexp.MustCompile(`(?i)State read error.*no state`)
29+
)
30+
2531
// Provision executes `terraform apply`.
2632
func (t*terraform)Provision(stream proto.DRPCProvisioner_ProvisionStream)error {
2733
shutdown,shutdownFunc:=context.WithCancel(stream.Context())
@@ -190,6 +196,43 @@ func (t *terraform) Provision(stream proto.DRPCProvisioner_ProvisionStream) erro
190196
}
191197
}()
192198

199+
// If we're destroying, exit early if there's no state. This is necessary to
200+
// avoid any cases where a workspace is "locked out" of terraform due to
201+
// e.g. bad template param values and cannot be deleted. This is just for
202+
// contingency, in the future we will try harder to prevent workspaces being
203+
// broken this hard.
204+
ifstart.Metadata.WorkspaceTransition==proto.WorkspaceTransition_DESTROY {
205+
_,err:=getTerraformState(shutdown,terraform,statefilePath)
206+
ifxerrors.Is(err,os.ErrNotExist) {
207+
_=stream.Send(&proto.Provision_Response{
208+
Type:&proto.Provision_Response_Log{
209+
Log:&proto.Log{
210+
Level:proto.LogLevel_INFO,
211+
Output:"The terraform state does not exist, there is nothing to do",
212+
},
213+
},
214+
})
215+
216+
returnstream.Send(&proto.Provision_Response{
217+
Type:&proto.Provision_Response_Complete{
218+
Complete:&proto.Provision_Complete{},
219+
},
220+
})
221+
}
222+
iferr!=nil {
223+
err=xerrors.Errorf("get terraform state: %w",err)
224+
_=stream.Send(&proto.Provision_Response{
225+
Type:&proto.Provision_Response_Complete{
226+
Complete:&proto.Provision_Complete{
227+
Error:err.Error(),
228+
},
229+
},
230+
})
231+
232+
returnerr
233+
}
234+
}
235+
193236
planfilePath:=filepath.Join(start.Directory,"terraform.tfplan")
194237
varargs []string
195238
ifstart.DryRun {
@@ -378,23 +421,11 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state
378421
_,err:=os.Stat(statefilePath)
379422
statefileExisted:=err==nil
380423

381-
statefile,err:=os.OpenFile(statefilePath,os.O_CREATE|os.O_RDWR,0600)
382-
iferr!=nil {
383-
returnnil,xerrors.Errorf("open statefile %q: %w",statefilePath,err)
384-
}
385-
deferstatefile.Close()
386-
// #nosec
387-
cmd:=exec.CommandContext(ctx,terraform.ExecPath(),"state","pull")
388-
cmd.Dir=terraform.WorkingDir()
389-
cmd.Stdout=statefile
390-
err=cmd.Run()
391-
iferr!=nil {
392-
returnnil,xerrors.Errorf("pull terraform state: %w",err)
393-
}
394-
state,err:=terraform.ShowStateFile(ctx,statefilePath)
424+
state,err:=getTerraformState(ctx,terraform,statefilePath)
395425
iferr!=nil {
396-
returnnil,xerrors.Errorf("show terraform state: %w",err)
426+
returnnil,xerrors.Errorf("get terraform state: %w",err)
397427
}
428+
398429
resources:=make([]*proto.Resource,0)
399430
ifstate.Values!=nil {
400431
rawGraph,err:=terraform.Graph(ctx)
@@ -557,6 +588,37 @@ func parseTerraformApply(ctx context.Context, terraform *tfexec.Terraform, state
557588
},nil
558589
}
559590

591+
// getTerraformState pulls and merges any remote terraform state into the given
592+
// path and reads the merged state. If there is no state, `os.ErrNotExist` will
593+
// be returned.
594+
funcgetTerraformState(ctx context.Context,terraform*tfexec.Terraform,statefilePathstring) (*tfjson.State,error) {
595+
statefile,err:=os.OpenFile(statefilePath,os.O_CREATE|os.O_RDWR,0600)
596+
iferr!=nil {
597+
returnnil,xerrors.Errorf("open statefile %q: %w",statefilePath,err)
598+
}
599+
deferstatefile.Close()
600+
601+
// #nosec
602+
cmd:=exec.CommandContext(ctx,terraform.ExecPath(),"state","pull")
603+
cmd.Dir=terraform.WorkingDir()
604+
cmd.Stdout=statefile
605+
err=cmd.Run()
606+
iferr!=nil {
607+
returnnil,xerrors.Errorf("pull terraform state: %w",err)
608+
}
609+
610+
state,err:=terraform.ShowStateFile(ctx,statefilePath)
611+
iferr!=nil {
612+
ifnoStateRegex.MatchString(err.Error()) {
613+
returnnil,os.ErrNotExist
614+
}
615+
616+
returnnil,xerrors.Errorf("show terraform state: %w",err)
617+
}
618+
619+
returnstate,nil
620+
}
621+
560622
typeterraformProvisionLogstruct {
561623
Levelstring`json:"@level"`
562624
Messagestring`json:"@message"`

‎provisioner/terraform/provision_test.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"os"
99
"path/filepath"
1010
"sort"
11+
"strings"
1112
"testing"
1213

1314
"github.com/stretchr/testify/require"
@@ -509,4 +510,50 @@ provider "coder" {
509510
}
510511
})
511512
}
513+
514+
t.Run("DestroyNoState",func(t*testing.T) {
515+
t.Parallel()
516+
517+
consttemplate=`resource "null_resource" "A" {}`
518+
519+
directory:=t.TempDir()
520+
err:=os.WriteFile(filepath.Join(directory,"main.tf"), []byte(template),0600)
521+
require.NoError(t,err)
522+
523+
request:=&proto.Provision_Request{
524+
Type:&proto.Provision_Request_Start{
525+
Start:&proto.Provision_Start{
526+
State:nil,
527+
Directory:directory,
528+
Metadata:&proto.Provision_Metadata{
529+
WorkspaceTransition:proto.WorkspaceTransition_DESTROY,
530+
},
531+
},
532+
},
533+
}
534+
535+
response,err:=api.Provision(ctx)
536+
require.NoError(t,err)
537+
err=response.Send(request)
538+
require.NoError(t,err)
539+
540+
gotLog:=false
541+
for {
542+
msg,err:=response.Recv()
543+
require.NoError(t,err)
544+
require.NotNil(t,msg)
545+
546+
ifmsg.GetLog()!=nil&&strings.Contains(msg.GetLog().Output,"nothing to do") {
547+
gotLog=true
548+
continue
549+
}
550+
ifmsg.GetComplete()==nil {
551+
continue
552+
}
553+
554+
require.Empty(t,msg.GetComplete().Error)
555+
require.True(t,gotLog,"never received 'nothing to do' log")
556+
break
557+
}
558+
})
512559
}

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp