mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 12:42:58 -06:00
2c35869a32
In refactoring the force push code when implementing force push for the Terraform remote backend, a bug was introduced that meant that backends that don't implement the EnableForcePush method would still have their state validated. This commit fixes that, and adds test coverage such that there is a separate mockRemoteClient that has this method implemented.
170 lines
3.8 KiB
Go
170 lines
3.8 KiB
Go
package remote
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/md5"
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/terraform/states/statefile"
|
|
"github.com/hashicorp/terraform/states/statemgr"
|
|
)
|
|
|
|
// testClient is a generic function to test any client.
|
|
func testClient(t *testing.T, c Client) {
|
|
var buf bytes.Buffer
|
|
s := statemgr.TestFullInitialState()
|
|
sf := &statefile.File{State: s}
|
|
if err := statefile.Write(sf, &buf); err != nil {
|
|
t.Fatalf("err: %s", err)
|
|
}
|
|
data := buf.Bytes()
|
|
|
|
if err := c.Put(data); err != nil {
|
|
t.Fatalf("put: %s", err)
|
|
}
|
|
|
|
p, err := c.Get()
|
|
if err != nil {
|
|
t.Fatalf("get: %s", err)
|
|
}
|
|
if !bytes.Equal(p.Data, data) {
|
|
t.Fatalf("bad: %#v", p)
|
|
}
|
|
|
|
if err := c.Delete(); err != nil {
|
|
t.Fatalf("delete: %s", err)
|
|
}
|
|
|
|
p, err = c.Get()
|
|
if err != nil {
|
|
t.Fatalf("get: %s", err)
|
|
}
|
|
if p != nil {
|
|
t.Fatalf("bad: %#v", p)
|
|
}
|
|
}
|
|
|
|
func TestRemoteClient_noPayload(t *testing.T) {
|
|
s := &State{
|
|
Client: nilClient{},
|
|
}
|
|
if err := s.RefreshState(); err != nil {
|
|
t.Fatal("error refreshing empty remote state")
|
|
}
|
|
}
|
|
|
|
// nilClient returns nil for everything
|
|
type nilClient struct{}
|
|
|
|
func (nilClient) Get() (*Payload, error) { return nil, nil }
|
|
|
|
func (c nilClient) Put([]byte) error { return nil }
|
|
|
|
func (c nilClient) Delete() error { return nil }
|
|
|
|
// mockClient is a client that tracks persisted state snapshots only in
|
|
// memory and also logs what it has been asked to do for use in test
|
|
// assertions.
|
|
type mockClient struct {
|
|
current []byte
|
|
log []mockClientRequest
|
|
}
|
|
|
|
type mockClientRequest struct {
|
|
Method string
|
|
Content map[string]interface{}
|
|
}
|
|
|
|
func (c *mockClient) Get() (*Payload, error) {
|
|
c.appendLog("Get", c.current)
|
|
if c.current == nil {
|
|
return nil, nil
|
|
}
|
|
checksum := md5.Sum(c.current)
|
|
return &Payload{
|
|
Data: c.current,
|
|
MD5: checksum[:],
|
|
}, nil
|
|
}
|
|
|
|
func (c *mockClient) Put(data []byte) error {
|
|
c.appendLog("Put", data)
|
|
c.current = data
|
|
return nil
|
|
}
|
|
|
|
func (c *mockClient) Delete() error {
|
|
c.appendLog("Delete", c.current)
|
|
c.current = nil
|
|
return nil
|
|
}
|
|
|
|
func (c *mockClient) appendLog(method string, content []byte) {
|
|
// For easier test assertions, we actually log the result of decoding
|
|
// the content JSON rather than the raw bytes. Callers are in principle
|
|
// allowed to provide any arbitrary bytes here, but we know we're only
|
|
// using this to test our own State implementation here and that always
|
|
// uses the JSON state format, so this is fine.
|
|
|
|
var contentVal map[string]interface{}
|
|
if content != nil {
|
|
err := json.Unmarshal(content, &contentVal)
|
|
if err != nil {
|
|
panic(err) // should never happen because our tests control this input
|
|
}
|
|
}
|
|
c.log = append(c.log, mockClientRequest{method, contentVal})
|
|
}
|
|
|
|
// mockClientForcePusher is like mockClient, but also implements
|
|
// EnableForcePush, allowing testing for this behavior
|
|
type mockClientForcePusher struct {
|
|
current []byte
|
|
force bool
|
|
log []mockClientRequest
|
|
}
|
|
|
|
func (c *mockClientForcePusher) Get() (*Payload, error) {
|
|
c.appendLog("Get", c.current)
|
|
if c.current == nil {
|
|
return nil, nil
|
|
}
|
|
checksum := md5.Sum(c.current)
|
|
return &Payload{
|
|
Data: c.current,
|
|
MD5: checksum[:],
|
|
}, nil
|
|
}
|
|
|
|
func (c *mockClientForcePusher) Put(data []byte) error {
|
|
if c.force {
|
|
c.appendLog("Force Put", data)
|
|
} else {
|
|
c.appendLog("Put", data)
|
|
}
|
|
c.current = data
|
|
return nil
|
|
}
|
|
|
|
// Implements remote.ClientForcePusher
|
|
func (c *mockClientForcePusher) EnableForcePush() {
|
|
c.force = true
|
|
}
|
|
|
|
func (c *mockClientForcePusher) Delete() error {
|
|
c.appendLog("Delete", c.current)
|
|
c.current = nil
|
|
return nil
|
|
}
|
|
func (c *mockClientForcePusher) appendLog(method string, content []byte) {
|
|
var contentVal map[string]interface{}
|
|
if content != nil {
|
|
err := json.Unmarshal(content, &contentVal)
|
|
if err != nil {
|
|
panic(err) // should never happen because our tests control this input
|
|
}
|
|
}
|
|
c.log = append(c.log, mockClientRequest{method, contentVal})
|
|
}
|