-
Notifications
You must be signed in to change notification settings - Fork 59
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Copy the storage package from minamijoyo/tfmigrate
https://github.com/minamijoyo/tfmigrate/tree/49ea331bbf97b9effe3344428db52d0f29719800/storage It's natural that tfmigrate users will expect to be authenticated cloud providers with the same options and precedences as terraform backend. This was not a problem in the s3 storage (I think) because the authentication logic for AWS is implemented in an external library hashicorp/aws-sdk-go-base. However, except for AWS, to expand support for other history storage types, we need to reuse the upstream code. The problem is that the license of hashicorp/terraform is the MPL2, but the tfmigrate is currently distributed under the terms of MIT. Before expanding support for other storage types, I'll split the storage implementations into a new separate repository, which will be distributed as the MPL2.
- Loading branch information
1 parent
1617c7b
commit 8d7eaae
Showing
15 changed files
with
876 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package storage | ||
|
||
// Config is an interface of factory method for Storage | ||
type Config interface { | ||
// NewStorage returns a new instance of Storage. | ||
NewStorage() (Storage, error) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package local | ||
|
||
import "github.com/minamijoyo/tfmigrate/storage" | ||
|
||
// Config is a config for local storage. | ||
type Config struct { | ||
// Path to a migration history file. Relative to the current working directory. | ||
Path string `hcl:"path"` | ||
} | ||
|
||
// Config implements a storage.Config. | ||
var _ storage.Config = (*Config)(nil) | ||
|
||
// NewStorage returns a new instance of storage.Storage. | ||
func (c *Config) NewStorage() (storage.Storage, error) { | ||
return NewStorage(c) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package local | ||
|
||
import "testing" | ||
|
||
func TestConfigNewStorage(t *testing.T) { | ||
cases := []struct { | ||
desc string | ||
config *Config | ||
ok bool | ||
}{ | ||
{ | ||
desc: "valid", | ||
config: &Config{ | ||
Path: "tmp/history.json", | ||
}, | ||
ok: true, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.desc, func(t *testing.T) { | ||
got, err := tc.config.NewStorage() | ||
if tc.ok && err != nil { | ||
t.Fatalf("unexpected err: %s", err) | ||
} | ||
if !tc.ok && err == nil { | ||
t.Fatalf("expected to return an error, but no error, got: %#v", got) | ||
} | ||
if tc.ok { | ||
_ = got.(*Storage) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package local | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"os" | ||
|
||
"github.com/minamijoyo/tfmigrate/storage" | ||
) | ||
|
||
// Storage is a storage.Storage implementation for local file. | ||
// This was originally intended for debugging purposes, but it can also be used | ||
// as a workaround if Storage doesn't support your cloud provider. | ||
// That is, you can manually synchronize local output files to the remote. | ||
type Storage struct { | ||
// config is a storage config for local. | ||
config *Config | ||
} | ||
|
||
var _ storage.Storage = (*Storage)(nil) | ||
|
||
// NewStorage returns a new instance of Storage. | ||
func NewStorage(config *Config) (*Storage, error) { | ||
s := &Storage{ | ||
config: config, | ||
} | ||
return s, nil | ||
} | ||
|
||
// Write writes migration history data to storage. | ||
func (s *Storage) Write(ctx context.Context, b []byte) error { | ||
// nolint gosec | ||
// G306: Expect WriteFile permissions to be 0600 or less | ||
// We ignore it because a history file doesn't contains sensitive data. | ||
// Note that changing a permission to 0600 is breaking change. | ||
return ioutil.WriteFile(s.config.Path, b, 0644) | ||
} | ||
|
||
// Read reads migration history data from storage. | ||
// If the key does not exist, it is assumed to be uninitialized and returns | ||
// an empty array instead of an error. | ||
func (s *Storage) Read(ctx context.Context) ([]byte, error) { | ||
if _, err := os.Stat(s.config.Path); os.IsNotExist(err) { | ||
// If the key does not exist | ||
return []byte{}, nil | ||
} | ||
return ioutil.ReadFile(s.config.Path) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
package local | ||
|
||
import ( | ||
"context" | ||
"io/ioutil" | ||
"os" | ||
"path/filepath" | ||
"testing" | ||
) | ||
|
||
func TestStorageWrite(t *testing.T) { | ||
cases := []struct { | ||
desc string | ||
config *Config | ||
contents []byte | ||
ok bool | ||
}{ | ||
{ | ||
desc: "simple", | ||
config: &Config{ | ||
Path: "history.json", | ||
}, | ||
contents: []byte("foo"), | ||
ok: true, | ||
}, | ||
{ | ||
desc: "dir does not exist", | ||
config: &Config{ | ||
Path: "not_exist/history.json", | ||
}, | ||
contents: []byte("foo"), | ||
ok: false, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.desc, func(t *testing.T) { | ||
localDir, err := ioutil.TempDir("", "localDir") | ||
if err != nil { | ||
t.Fatalf("failed to craete temp dir: %s", err) | ||
} | ||
t.Cleanup(func() { os.RemoveAll(localDir) }) | ||
|
||
tc.config.Path = filepath.Join(localDir, tc.config.Path) | ||
s, err := NewStorage(tc.config) | ||
if err != nil { | ||
t.Fatalf("failed to NewStorage: %s", err) | ||
} | ||
err = s.Write(context.Background(), tc.contents) | ||
if tc.ok && err != nil { | ||
t.Fatalf("unexpected err: %s", err) | ||
} | ||
if !tc.ok && err == nil { | ||
t.Fatal("expected to return an error, but no error") | ||
} | ||
|
||
if tc.ok { | ||
got, err := ioutil.ReadFile(tc.config.Path) | ||
if err != nil { | ||
t.Fatalf("failed to read contents: %s", err) | ||
} | ||
if string(got) != string(tc.contents) { | ||
t.Errorf("got: %s, want: %s", string(got), string(tc.contents)) | ||
} | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestStorageRead(t *testing.T) { | ||
cases := []struct { | ||
desc string | ||
config *Config | ||
contents []byte | ||
ok bool | ||
}{ | ||
{ | ||
desc: "simple", | ||
config: &Config{ | ||
Path: "history.json", | ||
}, | ||
contents: []byte("foo"), | ||
ok: true, | ||
}, | ||
{ | ||
desc: "file does not exist", | ||
config: &Config{ | ||
Path: "not_exist.json", | ||
}, | ||
contents: []byte{}, | ||
ok: true, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.desc, func(t *testing.T) { | ||
localDir, err := ioutil.TempDir("", "localDir") | ||
if err != nil { | ||
t.Fatalf("failed to craete temp dir: %s", err) | ||
} | ||
t.Cleanup(func() { os.RemoveAll(localDir) }) | ||
|
||
err = ioutil.WriteFile(filepath.Join(localDir, "history.json"), tc.contents, 0600) | ||
if err != nil { | ||
t.Fatalf("failed to write contents: %s", err) | ||
} | ||
|
||
tc.config.Path = filepath.Join(localDir, tc.config.Path) | ||
s, err := NewStorage(tc.config) | ||
if err != nil { | ||
t.Fatalf("failed to NewStorage: %s", err) | ||
} | ||
got, err := s.Read(context.Background()) | ||
if tc.ok && err != nil { | ||
t.Fatalf("unexpected err: %#v", err) | ||
} | ||
if !tc.ok && err == nil { | ||
t.Fatal("expected to return an error, but no error") | ||
} | ||
|
||
if tc.ok { | ||
if string(got) != string(tc.contents) { | ||
t.Errorf("got: %s, want: %s", string(got), string(tc.contents)) | ||
} | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package mock | ||
|
||
import "github.com/minamijoyo/tfmigrate/storage" | ||
|
||
// Config is a config for mock storage. | ||
type Config struct { | ||
// Data stores a serialized data for history. | ||
Data string `hcl:"data"` | ||
// WriteError is a flag to return an error on Write(). | ||
WriteError bool `hcl:"write_error"` | ||
// ReadError is a flag to return an error on Read(). | ||
ReadError bool `hcl:"read_error"` | ||
|
||
// A reference to an instance of mock storage for testing. | ||
s *Storage | ||
} | ||
|
||
// Config implements a storage.Config. | ||
var _ storage.Config = (*Config)(nil) | ||
|
||
// NewStorage returns a new instance of storage.Storage. | ||
func (c *Config) NewStorage() (storage.Storage, error) { | ||
s, err := NewStorage(c) | ||
|
||
// store a reference for test assertion. | ||
c.s = s | ||
return s, err | ||
} | ||
|
||
// Storage returns a reference to mock storage for testing. | ||
func (c *Config) Storage() *Storage { | ||
return c.s | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package mock | ||
|
||
import "testing" | ||
|
||
func TestConfigNewStorage(t *testing.T) { | ||
cases := []struct { | ||
desc string | ||
config *Config | ||
ok bool | ||
}{ | ||
{ | ||
desc: "valid", | ||
config: &Config{ | ||
Data: "foo", | ||
WriteError: true, | ||
ReadError: false, | ||
}, | ||
ok: true, | ||
}, | ||
} | ||
|
||
for _, tc := range cases { | ||
t.Run(tc.desc, func(t *testing.T) { | ||
got, err := tc.config.NewStorage() | ||
if tc.ok && err != nil { | ||
t.Fatalf("unexpected err: %s", err) | ||
} | ||
if !tc.ok && err == nil { | ||
t.Fatalf("expected to return an error, but no error, got: %#v", got) | ||
} | ||
if tc.ok { | ||
_ = got.(*Storage) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package mock | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/minamijoyo/tfmigrate/storage" | ||
) | ||
|
||
// Storage is a storage.Storage implementation for mock. | ||
// It writes and reads data from memory. | ||
type Storage struct { | ||
// config is a storage config for mock | ||
config *Config | ||
// data stores a serialized data for history. | ||
data string | ||
} | ||
|
||
var _ storage.Storage = (*Storage)(nil) | ||
|
||
// NewStorage returns a new instance of Storage. | ||
func NewStorage(config *Config) (*Storage, error) { | ||
s := &Storage{ | ||
config: config, | ||
data: config.Data, | ||
} | ||
return s, nil | ||
} | ||
|
||
// Data returns a raw data in mock storage for testing. | ||
func (s *Storage) Data() string { | ||
return s.data | ||
} | ||
|
||
// Write writes migration history data to storage. | ||
func (s *Storage) Write(ctx context.Context, b []byte) error { | ||
if s.config.WriteError { | ||
return fmt.Errorf("failed to write mock storage: writeError = %t", s.config.WriteError) | ||
} | ||
s.data = string(b) | ||
return nil | ||
} | ||
|
||
// Read reads migration history data from storage. | ||
func (s *Storage) Read(ctx context.Context) ([]byte, error) { | ||
if s.config.ReadError { | ||
return nil, fmt.Errorf("failed to read mock storage: readError = %t", s.config.ReadError) | ||
} | ||
return []byte(s.data), nil | ||
} |
Oops, something went wrong.