Skip to content

Commit 1f7ddc3

Browse files
committed
state: a bunch of state stuff
1 parent 492c6ef commit 1f7ddc3

9 files changed

Lines changed: 408 additions & 0 deletions

File tree

‎state/cache.go‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package state
2+
3+
import (
4+
"github.com/hashicorp/terraform/terraform"
5+
)
6+
7+
// CacheState is an implementation of the state interfaces that uses
8+
// a StateReadWriter for a local cache.
9+
type CacheState struct {
10+
Cache CacheStateCache
11+
Durable CacheStateDurable
12+
13+
state *terraform.State
14+
}
15+
16+
// StateReader impl.
17+
func (s *CacheState) State() *terraform.State {
18+
return s.state
19+
}
20+
21+
// WriteState will write and persist the state to the cache.
22+
//
23+
// StateWriter impl.
24+
func (s *CacheState) WriteState(state *terraform.State) error {
25+
if err := s.Cache.WriteState(state); err != nil {
26+
return err
27+
}
28+
29+
return s.Cache.PersistState()
30+
}
31+
32+
// RefreshState will refresh both the cache and the durable states. It
33+
// can return a myriad of errors (defined at the top of this file) depending
34+
// on potential conflicts that can occur while doing this.
35+
//
36+
// If the durable state is newer than the local cache, then the local cache
37+
// will be replaced with the durable.
38+
//
39+
// StateRefresher impl.
40+
func (s *CacheState) RefreshState() error {
41+
return nil
42+
}
43+
44+
// PersistState takes the local cache, assuming it is newer than the remote
45+
// state, and persists it to the durable storage. If you want to challenge the
46+
// assumption that the local state is the latest, call a RefreshState prior
47+
// to this.
48+
//
49+
// StatePersister impl.
50+
func (s *CacheState) PersistState() error {
51+
if err := s.Durable.WriteState(s.state); err != nil {
52+
return err
53+
}
54+
55+
return s.Durable.PersistState()
56+
}
57+
58+
// CacheStateCache is the meta-interface that must be implemented for
59+
// the cache for the CacheState.
60+
type CacheStateCache interface {
61+
StateReader
62+
StateWriter
63+
StatePersister
64+
}
65+
66+
// CacheStateDurable is the meta-interface that must be implemented for
67+
// the durable storage for CacheState.
68+
type CacheStateDurable interface {
69+
StateWriter
70+
StatePersister
71+
}

‎state/local.go‎

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package state
2+
3+
import (
4+
"os"
5+
6+
"github.com/hashicorp/terraform/terraform"
7+
)
8+
9+
// LocalState manages a state storage that is local to the filesystem.
10+
type LocalState struct {
11+
Path string
12+
13+
state *terraform.State
14+
}
15+
16+
// StateReader impl.
17+
func (s *LocalState) State() *terraform.State {
18+
return s.state
19+
}
20+
21+
// WriteState for LocalState always persists the state as well.
22+
//
23+
// StateWriter impl.
24+
func (s *LocalState) WriteState(state *terraform.State) error {
25+
s.state = state
26+
27+
f, err := os.Create(s.Path)
28+
if err != nil {
29+
return err
30+
}
31+
defer f.Close()
32+
33+
return terraform.WriteState(s.state, f)
34+
}
35+
36+
// PersistState for LocalState is a no-op since WriteState always persists.
37+
//
38+
// StatePersister impl.
39+
func (s *LocalState) PersistState() error {
40+
return nil
41+
}
42+
43+
// StateRefresher impl.
44+
func (s *LocalState) RefreshState() error {
45+
f, err := os.Open(s.Path)
46+
if err != nil {
47+
return err
48+
}
49+
defer f.Close()
50+
51+
state, err := terraform.ReadState(f)
52+
if err != nil {
53+
return err
54+
}
55+
56+
s.state = state
57+
return nil
58+
}

‎state/local_test.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package state
2+
3+
import (
4+
"io/ioutil"
5+
"os"
6+
"testing"
7+
8+
"github.com/hashicorp/terraform/terraform"
9+
)
10+
11+
func TestLocalState(t *testing.T) {
12+
f, err := ioutil.TempFile("", "tf")
13+
if err != nil {
14+
t.Fatalf("err: %s", err)
15+
}
16+
defer os.Remove(f.Name())
17+
18+
err = terraform.WriteState(TestStateInitial, f)
19+
f.Close()
20+
if err != nil {
21+
t.Fatalf("err: %s", err)
22+
}
23+
24+
TestState(t, &LocalState{
25+
Path: f.Name(),
26+
})
27+
}
28+
29+
func TestLocalState_impl(t *testing.T) {
30+
var _ StateReader = new(LocalState)
31+
var _ StateWriter = new(LocalState)
32+
var _ StatePersister = new(LocalState)
33+
var _ StateRefresher = new(LocalState)
34+
}

‎state/remote/client_inmem.go‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package remote
2+
3+
import (
4+
"crypto/md5"
5+
)
6+
7+
// InmemClient is a Client implementation that stores data in memory.
8+
type InmemClient struct {
9+
Data []byte
10+
MD5 []byte
11+
}
12+
13+
func (c *InmemClient) Get() (*Payload, error) {
14+
return &Payload{
15+
Data: c.Data,
16+
MD5: c.MD5,
17+
}, nil
18+
}
19+
20+
func (c *InmemClient) Put(data []byte) error {
21+
md5 := md5.Sum(data)
22+
23+
c.Data = data
24+
c.MD5 = md5[:]
25+
return nil
26+
}
27+
28+
func (c *InmemClient) Delete() error {
29+
c.Data = nil
30+
c.MD5 = nil
31+
return nil
32+
}

‎state/remote/remote.go‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package remote
2+
3+
// Client is the interface that must be implemented for a remote state
4+
// driver. It supports dumb put/get/delete, and the higher level structs
5+
// handle persisting the state properly here.
6+
type Client interface {
7+
Get() (*Payload, error)
8+
Put([]byte) error
9+
Delete() error
10+
}
11+
12+
// Payload is the return value from the remote state storage.
13+
type Payload struct {
14+
MD5 []byte
15+
Data []byte
16+
}
17+
18+
// Factory is the factory function to create a remote client.
19+
type Factory func(map[string]string) (Client, error)

‎state/remote/state.go‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package remote
2+
3+
import (
4+
"bytes"
5+
6+
"github.com/hashicorp/terraform/terraform"
7+
)
8+
9+
// State implements the State interfaces in the state package to handle
10+
// reading and writing the remote state. This State on its own does no
11+
// local caching so every persist will go to the remote storage and local
12+
// writes will go to memory.
13+
type State struct {
14+
Client Client
15+
16+
state *terraform.State
17+
}
18+
19+
// StateReader impl.
20+
func (s *State) State() *terraform.State {
21+
return s.state
22+
}
23+
24+
// StateWriter impl.
25+
func (s *State) WriteState(state *terraform.State) error {
26+
s.state = state
27+
return nil
28+
}
29+
30+
// StateRefresher impl.
31+
func (s *State) RefreshState() error {
32+
payload, err := s.Client.Get()
33+
if err != nil {
34+
return err
35+
}
36+
37+
state, err := terraform.ReadState(bytes.NewReader(payload.Data))
38+
if err != nil {
39+
return err
40+
}
41+
42+
s.state = state
43+
return nil
44+
}
45+
46+
// StatePersister impl.
47+
func (s *State) PersistState() error {
48+
var buf bytes.Buffer
49+
if err := terraform.WriteState(s.state, &buf); err != nil {
50+
return err
51+
}
52+
53+
return s.Client.Put(buf.Bytes())
54+
}

‎state/remote/state_test.go‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package remote
2+
3+
import (
4+
"testing"
5+
6+
"github.com/hashicorp/terraform/state"
7+
)
8+
9+
func TestState(t *testing.T) {
10+
s := &State{Client: new(InmemClient)}
11+
s.WriteState(state.TestStateInitial)
12+
if err := s.PersistState(); err != nil {
13+
t.Fatalf("err: %s", err)
14+
}
15+
16+
state.TestState(t, s)
17+
}
18+
19+
func TestState_impl(t *testing.T) {
20+
var _ state.StateReader = new(State)
21+
var _ state.StateWriter = new(State)
22+
var _ state.StatePersister = new(State)
23+
var _ state.StateRefresher = new(State)
24+
}

‎state/state.go‎

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package state
2+
3+
import (
4+
"github.com/hashicorp/terraform/terraform"
5+
)
6+
7+
// StateReader is the interface for things that can return a state. Retrieving
8+
// the state here must not error. Loading the state fresh (an operation that
9+
// can likely error) should be implemented by RefreshState. If a state hasn't
10+
// been loaded yet, it is okay for State to return nil.
11+
type StateReader interface {
12+
State() *terraform.State
13+
}
14+
15+
// StateWriter is the interface that must be implemented by something that
16+
// can write a state. Writing the state can be cached or in-memory, as
17+
// full persistence should be implemented by StatePersister.
18+
type StateWriter interface {
19+
WriteState(*terraform.State) error
20+
}
21+
22+
// StateRefresher is the interface that is implemented by something that
23+
// can load a state. This might be refreshing it from a remote location or
24+
// it might simply be reloading it from disk.
25+
type StateRefresher interface {
26+
RefreshState() error
27+
}
28+
29+
// StatePersister is implemented to truly persist a state. Whereas StateWriter
30+
// is allowed to perhaps be caching in memory, PersistState must write the
31+
// state to some durable storage.
32+
type StatePersister interface {
33+
PersistState() error
34+
}

0 commit comments

Comments
 (0)