Merge pull request #27786 from hashicorp/alisdair/refresh-empty-diag

backend/local: Return diag for refresh empty state
This commit is contained in:
Alisdair McDiarmid 2021-02-16 09:05:50 -05:00 committed by GitHub
commit 1301e38caa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 46 additions and 18 deletions

View File

@ -5,7 +5,6 @@ import (
"fmt"
"log"
"os"
"strings"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend"
@ -66,14 +65,11 @@ func (b *Local) opRefresh(
// Set our state
runningOp.State = opState.State()
if !runningOp.State.HasResources() {
if b.CLI != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
"Empty or non-existent state",
"There are currently no resources tracked in the state, so there is nothing to refresh.",
))
b.CLI.Output(b.Colorize().Color(strings.TrimSpace(refreshNoState) + "\n"))
}
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Warning,
"Empty or non-existent state",
"There are currently no resources tracked in the state, so there is nothing to refresh.",
))
}
// Perform the refresh in a goroutine so we can be interrupted
@ -90,7 +86,7 @@ func (b *Local) opRefresh(
return
}
// write the resulting state to the running op
// Write the resulting state to the running op
runningOp.State = newState
diags = diags.Append(refreshDiags)
if refreshDiags.HasErrors() {
@ -104,12 +100,7 @@ func (b *Local) opRefresh(
op.ReportResult(runningOp, diags)
return
}
// Show any remaining warnings before exiting
op.ReportResult(runningOp, diags)
}
const refreshNoState = `
[reset][bold][yellow]Empty or non-existent state file.[reset][yellow]
Refresh will do nothing. Refresh does not error or return an erroneous
exit status because many automation scripts use refresh, plan, then apply
and may not have a state file yet for the first run.
`

View File

@ -3,6 +3,7 @@ package local
import (
"context"
"fmt"
"strings"
"testing"
"github.com/hashicorp/terraform/addrs"
@ -214,6 +215,42 @@ func TestLocal_refresh_context_error(t *testing.T) {
assertBackendStateUnlocked(t, b)
}
func TestLocal_refreshEmptyState(t *testing.T) {
b, cleanup := TestLocal(t)
defer cleanup()
p := TestLocalProvider(t, b, "test", refreshFixtureSchema())
testStateFile(t, b.StatePath, states.NewState())
p.ReadResourceFn = nil
p.ReadResourceResponse = &providers.ReadResourceResponse{NewState: cty.ObjectVal(map[string]cty.Value{
"id": cty.StringVal("yes"),
})}
op, configCleanup := testOperationRefresh(t, "./testdata/refresh")
defer configCleanup()
record, playback := testRecordDiagnostics(t)
op.ShowDiagnostics = record
run, err := b.Operation(context.Background(), op)
if err != nil {
t.Fatalf("bad: %s", err)
}
<-run.Done()
diags := playback()
if diags.HasErrors() {
t.Fatalf("expected only warning diags, got errors: %s", diags.Err())
}
if got, want := diags.ErrWithWarnings().Error(), "Empty or non-existent state"; !strings.Contains(got, want) {
t.Errorf("wrong diags\n got: %s\nwant: %s", got, want)
}
// the backend should be unlocked after a run
assertBackendStateUnlocked(t, b)
}
func testOperationRefresh(t *testing.T, configDir string) (*backend.Operation, func()) {
t.Helper()