-
Notifications
You must be signed in to change notification settings - Fork 9.5k
/
backend_refresh.go
115 lines (99 loc) · 2.89 KB
/
backend_refresh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package local
import (
"context"
"fmt"
"log"
"os"
"strings"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/states/statemgr"
"github.com/hashicorp/terraform/tfdiags"
)
func (b *Local) opRefresh(
stopCtx context.Context,
cancelCtx context.Context,
op *backend.Operation,
runningOp *backend.RunningOperation) {
var diags tfdiags.Diagnostics
// Check if our state exists if we're performing a refresh operation. We
// only do this if we're managing state with this backend.
if b.Backend == nil {
if _, err := os.Stat(b.StatePath); err != nil {
if os.IsNotExist(err) {
err = nil
}
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Cannot read state file",
fmt.Sprintf("Failed to read %s: %s", b.StatePath, err),
))
b.ReportResult(runningOp, diags)
return
}
}
}
// Refresh now happens via a plan, so we need to ensure this is enabled
op.PlanRefresh = true
// Get our context
tfCtx, _, opState, contextDiags := b.context(op)
diags = diags.Append(contextDiags)
if contextDiags.HasErrors() {
b.ReportResult(runningOp, diags)
return
}
// the state was locked during succesfull context creation; unlock the state
// when the operation completes
defer func() {
err := op.StateLocker.Unlock(nil)
if err != nil {
b.ShowDiagnostics(err)
runningOp.Result = backend.OperationFailure
}
}()
// 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"))
}
}
// Perform the refresh in a goroutine so we can be interrupted
var newState *states.State
var refreshDiags tfdiags.Diagnostics
doneCh := make(chan struct{})
go func() {
defer close(doneCh)
newState, refreshDiags = tfCtx.Refresh()
log.Printf("[INFO] backend/local: refresh calling Refresh")
}()
if b.opWait(doneCh, stopCtx, cancelCtx, tfCtx, opState) {
return
}
// write the resulting state to the running op
runningOp.State = newState
diags = diags.Append(refreshDiags)
if refreshDiags.HasErrors() {
b.ReportResult(runningOp, diags)
return
}
err := statemgr.WriteAndPersist(opState, newState)
if err != nil {
diags = diags.Append(errwrap.Wrapf("Failed to write state: {{err}}", err))
b.ReportResult(runningOp, diags)
return
}
}
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.
`