github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/backend/local/backend_local_test.go (about) 1 package local 2 3 import ( 4 "fmt" 5 "os" 6 "path/filepath" 7 "testing" 8 9 "github.com/zclconf/go-cty/cty" 10 11 "github.com/hashicorp/terraform/internal/backend" 12 "github.com/hashicorp/terraform/internal/command/arguments" 13 "github.com/hashicorp/terraform/internal/command/clistate" 14 "github.com/hashicorp/terraform/internal/command/views" 15 "github.com/hashicorp/terraform/internal/configs/configload" 16 "github.com/hashicorp/terraform/internal/configs/configschema" 17 "github.com/hashicorp/terraform/internal/initwd" 18 "github.com/hashicorp/terraform/internal/plans" 19 "github.com/hashicorp/terraform/internal/plans/planfile" 20 "github.com/hashicorp/terraform/internal/states" 21 "github.com/hashicorp/terraform/internal/states/statefile" 22 "github.com/hashicorp/terraform/internal/states/statemgr" 23 "github.com/hashicorp/terraform/internal/terminal" 24 "github.com/hashicorp/terraform/internal/terraform" 25 "github.com/hashicorp/terraform/internal/tfdiags" 26 ) 27 28 func TestLocalRun(t *testing.T) { 29 configDir := "./testdata/empty" 30 b := TestLocal(t) 31 32 _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) 33 defer configCleanup() 34 35 streams, _ := terminal.StreamsForTesting(t) 36 view := views.NewView(streams) 37 stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view)) 38 39 op := &backend.Operation{ 40 ConfigDir: configDir, 41 ConfigLoader: configLoader, 42 Workspace: backend.DefaultStateName, 43 StateLocker: stateLocker, 44 } 45 46 _, _, diags := b.LocalRun(op) 47 if diags.HasErrors() { 48 t.Fatalf("unexpected error: %s", diags.Err().Error()) 49 } 50 51 // LocalRun() retains a lock on success 52 assertBackendStateLocked(t, b) 53 } 54 55 func TestLocalRun_error(t *testing.T) { 56 configDir := "./testdata/invalid" 57 b := TestLocal(t) 58 59 // This backend will return an error when asked to RefreshState, which 60 // should then cause LocalRun to return with the state unlocked. 61 b.Backend = backendWithStateStorageThatFailsRefresh{} 62 63 _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) 64 defer configCleanup() 65 66 streams, _ := terminal.StreamsForTesting(t) 67 view := views.NewView(streams) 68 stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view)) 69 70 op := &backend.Operation{ 71 ConfigDir: configDir, 72 ConfigLoader: configLoader, 73 Workspace: backend.DefaultStateName, 74 StateLocker: stateLocker, 75 } 76 77 _, _, diags := b.LocalRun(op) 78 if !diags.HasErrors() { 79 t.Fatal("unexpected success") 80 } 81 82 // LocalRun() unlocks the state on failure 83 assertBackendStateUnlocked(t, b) 84 } 85 86 func TestLocalRun_stalePlan(t *testing.T) { 87 configDir := "./testdata/apply" 88 b := TestLocal(t) 89 90 _, configLoader, configCleanup := initwd.MustLoadConfigForTests(t, configDir) 91 defer configCleanup() 92 93 // Write an empty state file with serial 3 94 sf, err := os.Create(b.StatePath) 95 if err != nil { 96 t.Fatalf("unexpected error creating state file %s: %s", b.StatePath, err) 97 } 98 if err := statefile.Write(statefile.New(states.NewState(), "boop", 3), sf); err != nil { 99 t.Fatalf("unexpected error writing state file: %s", err) 100 } 101 102 // Refresh the state 103 sm, err := b.StateMgr("") 104 if err != nil { 105 t.Fatalf("unexpected error: %s", err) 106 } 107 if err := sm.RefreshState(); err != nil { 108 t.Fatalf("unexpected error refreshing state: %s", err) 109 } 110 111 // Create a minimal plan which also has state file serial 2, so is stale 112 backendConfig := cty.ObjectVal(map[string]cty.Value{ 113 "path": cty.NullVal(cty.String), 114 "workspace_dir": cty.NullVal(cty.String), 115 }) 116 backendConfigRaw, err := plans.NewDynamicValue(backendConfig, backendConfig.Type()) 117 if err != nil { 118 t.Fatal(err) 119 } 120 plan := &plans.Plan{ 121 UIMode: plans.NormalMode, 122 Changes: plans.NewChanges(), 123 Backend: plans.Backend{ 124 Type: "local", 125 Config: backendConfigRaw, 126 }, 127 PrevRunState: states.NewState(), 128 PriorState: states.NewState(), 129 } 130 prevStateFile := statefile.New(plan.PrevRunState, "boop", 1) 131 stateFile := statefile.New(plan.PriorState, "boop", 2) 132 133 // Roundtrip through serialization as expected by the operation 134 outDir := t.TempDir() 135 defer os.RemoveAll(outDir) 136 planPath := filepath.Join(outDir, "plan.tfplan") 137 planfileArgs := planfile.CreateArgs{ 138 ConfigSnapshot: configload.NewEmptySnapshot(), 139 PreviousRunStateFile: prevStateFile, 140 StateFile: stateFile, 141 Plan: plan, 142 } 143 if err := planfile.Create(planPath, planfileArgs); err != nil { 144 t.Fatalf("unexpected error writing planfile: %s", err) 145 } 146 planFile, err := planfile.Open(planPath) 147 if err != nil { 148 t.Fatalf("unexpected error reading planfile: %s", err) 149 } 150 151 streams, _ := terminal.StreamsForTesting(t) 152 view := views.NewView(streams) 153 stateLocker := clistate.NewLocker(0, views.NewStateLocker(arguments.ViewHuman, view)) 154 155 op := &backend.Operation{ 156 ConfigDir: configDir, 157 ConfigLoader: configLoader, 158 PlanFile: planFile, 159 Workspace: backend.DefaultStateName, 160 StateLocker: stateLocker, 161 } 162 163 _, _, diags := b.LocalRun(op) 164 if !diags.HasErrors() { 165 t.Fatal("unexpected success") 166 } 167 168 // LocalRun() unlocks the state on failure 169 assertBackendStateUnlocked(t, b) 170 } 171 172 type backendWithStateStorageThatFailsRefresh struct { 173 } 174 175 var _ backend.Backend = backendWithStateStorageThatFailsRefresh{} 176 177 func (b backendWithStateStorageThatFailsRefresh) StateMgr(workspace string) (statemgr.Full, error) { 178 return &stateStorageThatFailsRefresh{}, nil 179 } 180 181 func (b backendWithStateStorageThatFailsRefresh) ConfigSchema() *configschema.Block { 182 return &configschema.Block{} 183 } 184 185 func (b backendWithStateStorageThatFailsRefresh) PrepareConfig(in cty.Value) (cty.Value, tfdiags.Diagnostics) { 186 return in, nil 187 } 188 189 func (b backendWithStateStorageThatFailsRefresh) Configure(cty.Value) tfdiags.Diagnostics { 190 return nil 191 } 192 193 func (b backendWithStateStorageThatFailsRefresh) DeleteWorkspace(name string, force bool) error { 194 return fmt.Errorf("unimplemented") 195 } 196 197 func (b backendWithStateStorageThatFailsRefresh) Workspaces() ([]string, error) { 198 return []string{"default"}, nil 199 } 200 201 type stateStorageThatFailsRefresh struct { 202 locked bool 203 } 204 205 func (s *stateStorageThatFailsRefresh) Lock(info *statemgr.LockInfo) (string, error) { 206 if s.locked { 207 return "", fmt.Errorf("already locked") 208 } 209 s.locked = true 210 return "locked", nil 211 } 212 213 func (s *stateStorageThatFailsRefresh) Unlock(id string) error { 214 if !s.locked { 215 return fmt.Errorf("not locked") 216 } 217 s.locked = false 218 return nil 219 } 220 221 func (s *stateStorageThatFailsRefresh) State() *states.State { 222 return nil 223 } 224 225 func (s *stateStorageThatFailsRefresh) GetRootOutputValues() (map[string]*states.OutputValue, error) { 226 return nil, fmt.Errorf("unimplemented") 227 } 228 229 func (s *stateStorageThatFailsRefresh) WriteState(*states.State) error { 230 return fmt.Errorf("unimplemented") 231 } 232 233 func (s *stateStorageThatFailsRefresh) RefreshState() error { 234 return fmt.Errorf("intentionally failing for testing purposes") 235 } 236 237 func (s *stateStorageThatFailsRefresh) PersistState(schemas *terraform.Schemas) error { 238 return fmt.Errorf("unimplemented") 239 }