Skip to content

Commit 12e509b

Browse files
author
Pam Selle
authored
Merge pull request #24884 from hashicorp/leetrout/force-push-v0.12
Backport remote backend force push to v0.12
2 parents 588cf8c + 469b9bc commit 12e509b

File tree

5 files changed

+426
-80
lines changed

5 files changed

+426
-80
lines changed

backend/remote/backend_state.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type remoteClient struct {
2020
runID string
2121
stateUploadErr bool
2222
workspace *tfe.Workspace
23+
forcePush bool
2324
}
2425

2526
// Get the remote state.
@@ -69,6 +70,7 @@ func (r *remoteClient) Put(state []byte) error {
6970
Serial: tfe.Int64(int64(stateFile.Serial)),
7071
MD5: tfe.String(fmt.Sprintf("%x", md5.Sum(state))),
7172
State: tfe.String(base64.StdEncoding.EncodeToString(state)),
73+
Force: tfe.Bool(r.forcePush),
7274
}
7375

7476
// If we have a run ID, make sure to add it to the options
@@ -97,6 +99,12 @@ func (r *remoteClient) Delete() error {
9799
return nil
98100
}
99101

102+
// EnableForcePush to allow the remote client to overwrite state
103+
// by implementing remote.ClientForcePusher
104+
func (r *remoteClient) EnableForcePush() {
105+
r.forcePush = true
106+
}
107+
100108
// Lock the remote state.
101109
func (r *remoteClient) Lock(info *state.LockInfo) (string, error) {
102110
ctx := context.Background()

state/remote/remote.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ type Client interface {
1515
Delete() error
1616
}
1717

18+
// ClientForcePusher is an optional interface that allows a remote
19+
// state to force push by managing a flag on the client that is
20+
// toggled on by a call to EnableForcePush.
21+
type ClientForcePusher interface {
22+
Client
23+
EnableForcePush()
24+
}
25+
1826
// ClientLocker is an optional interface that allows a remote state
1927
// backend to enable state lock/unlock.
2028
type ClientLocker interface {

state/remote/remote_test.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ func (c nilClient) Delete() error { return nil }
6969
type mockClient struct {
7070
current []byte
7171
log []mockClientRequest
72+
force bool
7273
}
7374

7475
type mockClientRequest struct {
@@ -89,7 +90,11 @@ func (c *mockClient) Get() (*Payload, error) {
8990
}
9091

9192
func (c *mockClient) Put(data []byte) error {
92-
c.appendLog("Put", data)
93+
if c.force {
94+
c.appendLog("Force Put", data)
95+
} else {
96+
c.appendLog("Put", data)
97+
}
9398
c.current = data
9499
return nil
95100
}
@@ -100,6 +105,11 @@ func (c *mockClient) Delete() error {
100105
return nil
101106
}
102107

108+
// Implements remote.ClientForcePusher
109+
func (c *mockClient) EnableForcePush() {
110+
c.force = true
111+
}
112+
103113
func (c *mockClient) appendLog(method string, content []byte) {
104114
// For easier test assertions, we actually log the result of decoding
105115
// the content JSON rather than the raw bytes. Callers are in principle

state/remote/state.go

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,19 @@ type State struct {
2121

2222
Client Client
2323

24-
lineage string
25-
serial uint64
26-
state, readState *states.State
27-
disableLocks bool
24+
// We track two pieces of meta data in addition to the state itself:
25+
//
26+
// lineage - the state's unique ID
27+
// serial - the monotonic counter of "versions" of the state
28+
//
29+
// Both of these (along with state) have a sister field
30+
// that represents the values read in from an existing source.
31+
// All three of these values are used to determine if the new
32+
// state has changed from an existing state we read in.
33+
lineage, readLineage string
34+
serial, readSerial uint64
35+
state, readState *states.State
36+
disableLocks bool
2837
}
2938

3039
var _ statemgr.Full = (*State)(nil)
@@ -64,8 +73,15 @@ func (s *State) WriteStateForMigration(f *statefile.File, force bool) error {
6473
s.mu.Lock()
6574
defer s.mu.Unlock()
6675

67-
checkFile := statefile.New(s.state, s.lineage, s.serial)
68-
if !force {
76+
// `force` is passed down from the CLI flag and terminates here. Actual
77+
// force pushing with the remote backend happens when Put()'ing the contents
78+
// in the backend. If force is specified we skip verifications and hand the
79+
// context off to the client to use when persitence operations actually take place.
80+
c, isForcePusher := s.Client.(ClientForcePusher)
81+
if force && isForcePusher {
82+
c.EnableForcePush()
83+
} else {
84+
checkFile := statefile.New(s.state, s.lineage, s.serial)
6985
if err := statemgr.CheckValidImport(f, checkFile); err != nil {
7086
return err
7187
}
@@ -113,7 +129,12 @@ func (s *State) refreshState() error {
113129
s.lineage = stateFile.Lineage
114130
s.serial = stateFile.Serial
115131
s.state = stateFile.State
116-
s.readState = s.state.DeepCopy() // our states must be separate instances so we can track changes
132+
133+
// Properties from the remote must be separate so we can
134+
// track changes as lineage, serial and/or state are mutated
135+
s.readLineage = stateFile.Lineage
136+
s.readSerial = stateFile.Serial
137+
s.readState = s.state.DeepCopy()
117138
return nil
118139
}
119140

@@ -123,8 +144,11 @@ func (s *State) PersistState() error {
123144
defer s.mu.Unlock()
124145

125146
if s.readState != nil {
126-
if statefile.StatesMarshalEqual(s.state, s.readState) {
127-
// If the state hasn't changed at all then we have nothing to do.
147+
lineageUnchanged := s.readLineage != "" && s.lineage == s.readLineage
148+
serialUnchanged := s.readSerial != 0 && s.serial == s.readSerial
149+
stateUnchanged := statefile.StatesMarshalEqual(s.state, s.readState)
150+
if stateUnchanged && lineageUnchanged && serialUnchanged {
151+
// If the state, lineage or serial haven't changed at all then we have nothing to do.
128152
return nil
129153
}
130154
s.serial++
@@ -161,7 +185,13 @@ func (s *State) PersistState() error {
161185

162186
// After we've successfully persisted, what we just wrote is our new
163187
// reference state until someone calls RefreshState again.
188+
// We've potentially overwritten (via force) the state, lineage
189+
// and / or serial (and serial was incremented) so we copy over all
190+
// three fields so everything matches the new state and a subsequent
191+
// operation would correctly detect no changes to the lineage, serial or state.
164192
s.readState = s.state.DeepCopy()
193+
s.readLineage = s.lineage
194+
s.readSerial = s.serial
165195
return nil
166196
}
167197

0 commit comments

Comments
 (0)