Skip to content

Commit 8503c45

Browse files
authored
Update backend.Backend's StateMgr method to return diagnostics instead of primitive errors (#37496)
* Fix S3 backend test affected by making the Workspaces method return errors via diagnostics * Address diagnostics comparison issues in test by ensuring expected diagnostics are defined in the context of the config they're triggered by * Fix failing test case `TestBackendConfig_EC2MetadataEndpoint/envvar_invalid_mode` by making `diagnosticBase` struct comparable * Add compile-time checks that diagnostic types fulfil interfaces * Stop diagnosticBase implementing ComparableDiagnostic, re-add S3-specific comparer code to s3 package * Update tests to use the S3-specific comparer again * Fix test case missed in refactoring * Update the backend.Backend interface to use diagnostics as return value from StateMgr method * Fix calls to `Fatalf`
1 parent 61a7914 commit 8503c45

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+663
-622
lines changed

internal/backend/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ type Backend interface {
9191
// If the named workspace doesn't exist, or if it has no state, it will
9292
// be created either immediately on this call or the first time
9393
// PersistState is called, depending on the state manager implementation.
94-
StateMgr(workspace string) (statemgr.Full, error)
94+
StateMgr(workspace string) (statemgr.Full, tfdiags.Diagnostics)
9595

9696
// DeleteWorkspace removes the workspace with the given name if it exists.
9797
//

internal/backend/local/backend.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -257,18 +257,20 @@ func (b *Local) DeleteWorkspace(name string, force bool) tfdiags.Diagnostics {
257257
return diags
258258
}
259259

260-
func (b *Local) StateMgr(name string) (statemgr.Full, error) {
260+
func (b *Local) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
261+
var diags tfdiags.Diagnostics
262+
261263
// If we have a backend handling state, delegate to that.
262264
if b.Backend != nil {
263265
return b.Backend.StateMgr(name)
264266
}
265267

266268
if s, ok := b.states[name]; ok {
267-
return s, nil
269+
return s, diags
268270
}
269271

270272
if err := b.createState(name); err != nil {
271-
return nil, err
273+
return nil, diags.Append(err)
272274
}
273275

274276
statePath, stateOutPath, backupPath := b.StatePaths(name)
@@ -283,7 +285,7 @@ func (b *Local) StateMgr(name string) (statemgr.Full, error) {
283285
b.states = map[string]statemgr.Full{}
284286
}
285287
b.states[name] = s
286-
return s, nil
288+
return s, diags
287289
}
288290

289291
// Operation implements backendrun.OperationsBackend

internal/backend/local/backend_apply_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ type backendWithFailingState struct {
347347
Local
348348
}
349349

350-
func (b *backendWithFailingState) StateMgr(name string) (statemgr.Full, error) {
350+
func (b *backendWithFailingState) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
351351
return &failingState{
352352
statemgr.NewFilesystem("failing-state.tfstate"),
353353
}, nil

internal/backend/local/backend_local.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,9 @@ func (b *Local) localRun(op *backendrun.Operation) (*backendrun.LocalRun, *confi
4545

4646
// Get the latest state.
4747
log.Printf("[TRACE] backend/local: requesting state manager for workspace %q", op.Workspace)
48-
s, err := b.StateMgr(op.Workspace)
49-
if err != nil {
50-
diags = diags.Append(fmt.Errorf("error loading state: %w", err))
48+
s, sDiags := b.StateMgr(op.Workspace)
49+
if sDiags.HasErrors() {
50+
diags = diags.Append(fmt.Errorf("error loading state: %w", sDiags.Err()))
5151
return nil, nil, nil, diags
5252
}
5353
log.Printf("[TRACE] backend/local: requesting state lock for workspace %q", op.Workspace)

internal/backend/local/backend_local_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,9 +140,9 @@ func TestLocalRun_stalePlan(t *testing.T) {
140140
}
141141

142142
// Refresh the state
143-
sm, err := b.StateMgr("")
144-
if err != nil {
145-
t.Fatalf("unexpected error: %s", err)
143+
sm, sDiags := b.StateMgr("")
144+
if sDiags.HasErrors() {
145+
t.Fatalf("unexpected error: %s", sDiags.Err())
146146
}
147147
if err := sm.RefreshState(); err != nil {
148148
t.Fatalf("unexpected error refreshing state: %s", err)
@@ -214,7 +214,7 @@ type backendWithStateStorageThatFailsRefresh struct {
214214

215215
var _ backend.Backend = backendWithStateStorageThatFailsRefresh{}
216216

217-
func (b backendWithStateStorageThatFailsRefresh) StateMgr(workspace string) (statemgr.Full, error) {
217+
func (b backendWithStateStorageThatFailsRefresh) StateMgr(workspace string) (statemgr.Full, tfdiags.Diagnostics) {
218218
return &stateStorageThatFailsRefresh{}, nil
219219
}
220220

internal/backend/local/backend_test.go

Lines changed: 27 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ func TestLocal_useOfPathAttribute(t *testing.T) {
116116

117117
// State file at the `path` location doesn't exist yet
118118
workspace := backend.DefaultStateName
119-
stmgr, err := b.StateMgr(workspace)
120-
if err != nil {
121-
t.Fatalf("unexpected error returned from StateMgr")
119+
stmgr, sDiags := b.StateMgr(workspace)
120+
if sDiags.HasErrors() {
121+
t.Fatalf("unexpected error returned from StateMgr: %s", sDiags.Err())
122122
}
123123
defaultStatePath := fmt.Sprintf("%s/%s", td, path)
124124
if _, err := os.Stat(defaultStatePath); !strings.Contains(err.Error(), "no such file or directory") {
@@ -137,7 +137,7 @@ func TestLocal_useOfPathAttribute(t *testing.T) {
137137
Value: cty.StringVal("foobar"),
138138
},
139139
}
140-
err = stmgr.WriteState(s)
140+
err := stmgr.WriteState(s)
141141
if err != nil {
142142
t.Fatalf("unexpected error returned from WriteState")
143143
}
@@ -150,9 +150,9 @@ func TestLocal_useOfPathAttribute(t *testing.T) {
150150
// Writing to a non-default workspace's state creates a file
151151
// that's unaffected by the `path` location
152152
workspace = "fizzbuzz"
153-
stmgr, err = b.StateMgr(workspace)
154-
if err != nil {
155-
t.Fatalf("unexpected error returned from StateMgr")
153+
stmgr, sDiags = b.StateMgr(workspace)
154+
if sDiags.HasErrors() {
155+
t.Fatalf("unexpected error returned from StateMgr: %s", sDiags.Err())
156156
}
157157
fizzbuzzStatePath := fmt.Sprintf("%s/terraform.tfstate.d/%s/terraform.tfstate", td, workspace)
158158
err = stmgr.WriteState(s)
@@ -186,17 +186,17 @@ func TestLocal_pathAttributeWrongExtension(t *testing.T) {
186186

187187
// Writing to the default workspace's state creates a file
188188
workspace := backend.DefaultStateName
189-
stmgr, err := b.StateMgr(workspace)
190-
if err != nil {
191-
t.Fatalf("unexpected error returned from StateMgr")
189+
stmgr, sDiags := b.StateMgr(workspace)
190+
if sDiags.HasErrors() {
191+
t.Fatalf("unexpected error returned from StateMgr: %s", sDiags.Err())
192192
}
193193
s := states.NewState()
194194
s.RootOutputValues = map[string]*states.OutputValue{
195195
"foobar": {
196196
Value: cty.StringVal("foobar"),
197197
},
198198
}
199-
err = stmgr.WriteState(s)
199+
err := stmgr.WriteState(s)
200200
if err != nil {
201201
t.Fatalf("unexpected error returned from WriteState")
202202
}
@@ -233,17 +233,17 @@ func TestLocal_useOfWorkspaceDirAttribute(t *testing.T) {
233233
// Unaffected by the `workspace_dir` location.
234234
workspace := backend.DefaultStateName
235235
defaultStatePath := fmt.Sprintf("%s/terraform.tfstate", td)
236-
stmgr, err := b.StateMgr(workspace)
237-
if err != nil {
238-
t.Fatalf("unexpected error returned from StateMgr")
236+
stmgr, sDiags := b.StateMgr(workspace)
237+
if sDiags.HasErrors() {
238+
t.Fatalf("unexpected error returned from StateMgr: %s", sDiags.Err())
239239
}
240240
s := states.NewState()
241241
s.RootOutputValues = map[string]*states.OutputValue{
242242
"foobar": {
243243
Value: cty.StringVal("foobar"),
244244
},
245245
}
246-
err = stmgr.WriteState(s)
246+
err := stmgr.WriteState(s)
247247
if err != nil {
248248
t.Fatalf("unexpected error returned from WriteState")
249249
}
@@ -254,9 +254,9 @@ func TestLocal_useOfWorkspaceDirAttribute(t *testing.T) {
254254
// that's affected by the `workspace_dir` location
255255
workspace = "fizzbuzz"
256256
fizzbuzzStatePath := fmt.Sprintf("%s/%s/%s/terraform.tfstate", td, workspaceDir, workspace)
257-
stmgr, err = b.StateMgr(workspace)
258-
if err != nil {
259-
t.Fatalf("unexpected error returned from StateMgr")
257+
stmgr, sDiags = b.StateMgr(workspace)
258+
if sDiags.HasErrors() {
259+
t.Fatalf("unexpected error returned from StateMgr: %s", sDiags.Err())
260260
}
261261
err = stmgr.WriteState(s)
262262
if err != nil {
@@ -324,8 +324,8 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
324324

325325
// Calling StateMgr with a new workspace name creates that workspace's state file.
326326
expectedA := "test_A"
327-
if _, err := b.StateMgr(expectedA); err != nil {
328-
t.Fatal(err)
327+
if _, sDiags := b.StateMgr(expectedA); sDiags.HasErrors() {
328+
t.Fatal(sDiags)
329329
}
330330

331331
states, wDiags = b.Workspaces()
@@ -340,8 +340,8 @@ func TestLocal_addAndRemoveStates(t *testing.T) {
340340

341341
// Creating another workspace appends it to the list of present workspaces.
342342
expectedB := "test_B"
343-
if _, err := b.StateMgr(expectedB); err != nil {
344-
t.Fatal(err)
343+
if _, sDiags := b.StateMgr(expectedB); sDiags.HasErrors() {
344+
t.Fatal(sDiags.Err())
345345
}
346346

347347
states, wDiags = b.Workspaces()
@@ -626,8 +626,9 @@ func (b *testDelegateBackend) Configure(obj cty.Value) tfdiags.Diagnostics {
626626
return diags.Append(errTestDelegateConfigure)
627627
}
628628

629-
func (b *testDelegateBackend) StateMgr(name string) (statemgr.Full, error) {
630-
return nil, errTestDelegateState
629+
func (b *testDelegateBackend) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
630+
var diags tfdiags.Diagnostics
631+
return nil, diags.Append(errTestDelegateState)
631632
}
632633

633634
func (b *testDelegateBackend) Workspaces() ([]string, tfdiags.Diagnostics) {
@@ -661,8 +662,8 @@ func TestLocal_callsMethodsOnStateBackend(t *testing.T) {
661662
t.Fatal("expected errTestDelegateConfigure error, got:", diags.Err())
662663
}
663664

664-
if _, err := b.StateMgr("test"); err != errTestDelegateState {
665-
t.Fatal("expected errTestDelegateState, got:", err)
665+
if _, sDiags := b.StateMgr("test"); sDiags.Err().Error() != errTestDelegateState.Error() {
666+
t.Fatal("expected errTestDelegateState, got:", sDiags.Err())
666667
}
667668

668669
if _, diags := b.Workspaces(); !diags.HasErrors() || diags.Err().Error() != errTestDelegateStates.Error() {

internal/backend/local/testing.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,10 @@ func (b *TestLocalSingleState) DeleteWorkspace(string, bool) tfdiags.Diagnostics
126126
return tfdiags.Diagnostics{}.Append(backend.ErrWorkspacesNotSupported)
127127
}
128128

129-
func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, error) {
129+
func (b *TestLocalSingleState) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
130+
var diags tfdiags.Diagnostics
130131
if name != backend.DefaultStateName {
131-
return nil, backend.ErrWorkspacesNotSupported
132+
return nil, diags.Append(backend.ErrWorkspacesNotSupported)
132133
}
133134

134135
return b.Local.StateMgr(name)
@@ -172,9 +173,10 @@ func (b *TestLocalNoDefaultState) DeleteWorkspace(name string, force bool) tfdia
172173
return b.Local.DeleteWorkspace(name, force)
173174
}
174175

175-
func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, error) {
176+
func (b *TestLocalNoDefaultState) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
177+
var diags tfdiags.Diagnostics
176178
if name == backend.DefaultStateName {
177-
return nil, backend.ErrDefaultWorkspaceNotSupported
179+
return nil, diags.Append(backend.ErrDefaultWorkspaceNotSupported)
178180
}
179181
return b.Local.StateMgr(name)
180182
}

internal/backend/pluggable/pluggable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ func (p *Pluggable) DeleteWorkspace(workspace string, force bool) tfdiags.Diagno
133133
// state storage provider to interact with state.
134134
//
135135
// StateMgr implements backend.Backend
136-
func (p *Pluggable) StateMgr(workspace string) (statemgr.Full, error) {
136+
func (p *Pluggable) StateMgr(workspace string) (statemgr.Full, tfdiags.Diagnostics) {
137137
// repackages the provider's methods inside a state manager,
138138
// to be passed to the calling code that expects a statemgr.Full
139139
return remote.NewRemoteGRPC(p.provider, p.typeName, workspace), nil

internal/backend/remote-state/azure/backend_state.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -85,11 +85,13 @@ func (b *Backend) DeleteWorkspace(name string, _ bool) tfdiags.Diagnostics {
8585
return diags
8686
}
8787

88-
func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
88+
func (b *Backend) StateMgr(name string) (statemgr.Full, tfdiags.Diagnostics) {
8989
ctx := newCtx()
90+
var diags tfdiags.Diagnostics
91+
9092
blobClient, err := b.apiClient.getBlobClient(ctx)
9193
if err != nil {
92-
return nil, err
94+
return nil, diags.Append(err)
9395
}
9496

9597
client := &RemoteClient{
@@ -104,7 +106,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
104106

105107
// Grab the value
106108
if err := stateMgr.RefreshState(); err != nil {
107-
return nil, err
109+
return nil, diags.Append(err)
108110
}
109111
//if this isn't the default state name, we need to create the object so
110112
//it's listed by States.
@@ -114,7 +116,7 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
114116
lockInfo.Operation = "init"
115117
lockId, err := client.Lock(lockInfo)
116118
if err != nil {
117-
return nil, fmt.Errorf("failed to lock azure state: %s", err)
119+
return nil, diags.Append(fmt.Errorf("failed to lock azure state: %s", err))
118120
}
119121

120122
// Local helper function so we can call it multiple places
@@ -128,29 +130,29 @@ func (b *Backend) StateMgr(name string) (statemgr.Full, error) {
128130
// Grab the value
129131
if err := stateMgr.RefreshState(); err != nil {
130132
err = lockUnlock(err)
131-
return nil, err
133+
return nil, diags.Append(err)
132134
}
133135
//if this isn't the default state name, we need to create the object so
134136
//it's listed by States.
135137
if v := stateMgr.State(); v == nil {
136138
// If we have no state, we have to create an empty state
137139
if err := stateMgr.WriteState(states.NewState()); err != nil {
138140
err = lockUnlock(err)
139-
return nil, err
141+
return nil, diags.Append(err)
140142
}
141143
if err := stateMgr.PersistState(nil); err != nil {
142144
err = lockUnlock(err)
143-
return nil, err
145+
return nil, diags.Append(err)
144146
}
145147

146148
// Unlock, the state should now be initialized
147149
if err := lockUnlock(nil); err != nil {
148-
return nil, err
150+
return nil, diags.Append(err)
149151
}
150152
}
151153
}
152154

153-
return stateMgr, nil
155+
return stateMgr, diags
154156
}
155157

156158
func (b *Backend) client() *RemoteClient {

0 commit comments

Comments
 (0)