diff --git a/command/history_runner_test.go b/command/history_runner_test.go index f2d3ad3..b88795d 100644 --- a/command/history_runner_test.go +++ b/command/history_runner_test.go @@ -6,9 +6,9 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/minamijoyo/tfmigrate-storage/mock" "github.com/minamijoyo/tfmigrate/config" "github.com/minamijoyo/tfmigrate/history" - "github.com/minamijoyo/tfmigrate/storage/mock" ) func TestHistoryRunnerPlan(t *testing.T) { diff --git a/command/list_test.go b/command/list_test.go index 84cde04..7884865 100644 --- a/command/list_test.go +++ b/command/list_test.go @@ -4,9 +4,9 @@ import ( "context" "testing" + "github.com/minamijoyo/tfmigrate-storage/mock" "github.com/minamijoyo/tfmigrate/config" "github.com/minamijoyo/tfmigrate/history" - "github.com/minamijoyo/tfmigrate/storage/mock" ) func TestListMigrations(t *testing.T) { diff --git a/config/history_test.go b/config/history_test.go index 0d7e8e8..c4356ed 100644 --- a/config/history_test.go +++ b/config/history_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" + "github.com/minamijoyo/tfmigrate-storage/local" "github.com/minamijoyo/tfmigrate/history" - "github.com/minamijoyo/tfmigrate/storage/local" ) func TestParseHistoryBlock(t *testing.T) { diff --git a/config/storage.go b/config/storage.go index 0973882..0c6b72a 100644 --- a/config/storage.go +++ b/config/storage.go @@ -5,10 +5,10 @@ import ( "github.com/hashicorp/hcl/v2" "github.com/hashicorp/hcl/v2/gohcl" - "github.com/minamijoyo/tfmigrate/storage" - "github.com/minamijoyo/tfmigrate/storage/local" - "github.com/minamijoyo/tfmigrate/storage/mock" - "github.com/minamijoyo/tfmigrate/storage/s3" + storage "github.com/minamijoyo/tfmigrate-storage" + "github.com/minamijoyo/tfmigrate-storage/local" + "github.com/minamijoyo/tfmigrate-storage/mock" + "github.com/minamijoyo/tfmigrate-storage/s3" ) // StorageBlock represents a block for migration history data store in HCL. diff --git a/config/storage_local_test.go b/config/storage_local_test.go index f653823..be86d60 100644 --- a/config/storage_local_test.go +++ b/config/storage_local_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" - "github.com/minamijoyo/tfmigrate/storage" - "github.com/minamijoyo/tfmigrate/storage/local" + storage "github.com/minamijoyo/tfmigrate-storage" + "github.com/minamijoyo/tfmigrate-storage/local" ) func TestParseLocalStorageBlock(t *testing.T) { diff --git a/config/storage_mock_test.go b/config/storage_mock_test.go index 07fc8e6..7bf2dd1 100644 --- a/config/storage_mock_test.go +++ b/config/storage_mock_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" - "github.com/minamijoyo/tfmigrate/storage" - "github.com/minamijoyo/tfmigrate/storage/mock" + storage "github.com/minamijoyo/tfmigrate-storage" + "github.com/minamijoyo/tfmigrate-storage/mock" ) func TestParseMockStorageBlock(t *testing.T) { diff --git a/config/storage_s3_test.go b/config/storage_s3_test.go index a36b732..3824627 100644 --- a/config/storage_s3_test.go +++ b/config/storage_s3_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" - "github.com/minamijoyo/tfmigrate/storage" - "github.com/minamijoyo/tfmigrate/storage/s3" + storage "github.com/minamijoyo/tfmigrate-storage" + "github.com/minamijoyo/tfmigrate-storage/s3" ) func TestParseS3StorageBlock(t *testing.T) { diff --git a/config/storage_test.go b/config/storage_test.go index 0867deb..86a05d5 100644 --- a/config/storage_test.go +++ b/config/storage_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" - "github.com/minamijoyo/tfmigrate/storage" - "github.com/minamijoyo/tfmigrate/storage/local" + storage "github.com/minamijoyo/tfmigrate-storage" + "github.com/minamijoyo/tfmigrate-storage/local" ) func TestParseStorageBlock(t *testing.T) { diff --git a/config/tfmigrate_test.go b/config/tfmigrate_test.go index 507229b..082f9b4 100644 --- a/config/tfmigrate_test.go +++ b/config/tfmigrate_test.go @@ -4,8 +4,8 @@ import ( "reflect" "testing" + "github.com/minamijoyo/tfmigrate-storage/local" "github.com/minamijoyo/tfmigrate/history" - "github.com/minamijoyo/tfmigrate/storage/local" ) func TestParseConfigurationFile(t *testing.T) { diff --git a/go.mod b/go.mod index 8d237ff..865a01f 100644 --- a/go.mod +++ b/go.mod @@ -3,14 +3,13 @@ module github.com/minamijoyo/tfmigrate go 1.17 require ( - github.com/aws/aws-sdk-go v1.37.0 github.com/davecgh/go-spew v1.1.1 github.com/google/go-cmp v0.5.2 - github.com/hashicorp/aws-sdk-go-base v0.6.0 github.com/hashicorp/go-version v1.3.0 github.com/hashicorp/hcl/v2 v2.6.0 github.com/hashicorp/logutils v1.0.0 github.com/mattn/go-shellwords v1.0.10 + github.com/minamijoyo/tfmigrate-storage v0.0.0-20220325144224-f6ba9f1d2224 github.com/mitchellh/cli v1.1.1 github.com/spf13/pflag v1.0.2 ) @@ -20,8 +19,10 @@ require ( github.com/apparentlymart/go-textseg v1.0.0 // indirect github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 // indirect + github.com/aws/aws-sdk-go v1.43.22 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/fatih/color v1.7.0 // indirect + github.com/hashicorp/aws-sdk-go-base v1.1.0 // indirect github.com/hashicorp/errwrap v1.0.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.0 // indirect github.com/hashicorp/go-multierror v1.0.0 // indirect @@ -32,7 +33,7 @@ require ( github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect github.com/posener/complete v1.1.1 // indirect github.com/zclconf/go-cty v1.2.0 // indirect - golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f // indirect - golang.org/x/text v0.3.3 // indirect + golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect ) diff --git a/go.sum b/go.sum index 5f21f7e..3d4a9ce 100644 --- a/go.sum +++ b/go.sum @@ -8,8 +8,8 @@ github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJE github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 h1:BUAU3CGlLvorLI26FmByPp2eC2qla6E1Tw+scpcg/to= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= -github.com/aws/aws-sdk-go v1.37.0 h1:GzFnhOIsrGyQ69s7VgqtrG2BG8v7X7vwB3Xpbd/DBBk= -github.com/aws/aws-sdk-go v1.37.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro= +github.com/aws/aws-sdk-go v1.43.22 h1:QY9/1TZB73UDEVQ68sUVJXf/7QUiHZl7zbbLF1wpqlc= +github.com/aws/aws-sdk-go v1.43.22/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -24,8 +24,8 @@ github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/hashicorp/aws-sdk-go-base v0.6.0 h1:qmUbzM36msbBF59YctwuO5w0M2oNXjlilgKpnEhx1uw= -github.com/hashicorp/aws-sdk-go-base v0.6.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= +github.com/hashicorp/aws-sdk-go-base v1.1.0 h1:27urM3JAp6v+Oj/Ea5ULZwuFPK9cO1RUdEpV+rNdSAc= +github.com/hashicorp/aws-sdk-go-base v1.1.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= @@ -56,6 +56,8 @@ github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-shellwords v1.0.10 h1:Y7Xqm8piKOO3v10Thp7Z36h4FYFjt5xB//6XvOrs2Gw= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/minamijoyo/tfmigrate-storage v0.0.0-20220325144224-f6ba9f1d2224 h1:yRwQbk4gcvRugMrrQc2RUaDpdN2680Vm8gUl53qEmvQ= +github.com/minamijoyo/tfmigrate-storage v0.0.0-20220325144224-f6ba9f1d2224/go.mod h1:C5y/2qAq6qbVd53XEKZ5PVW21YVFBMlSVII7Nx+ilso= github.com/mitchellh/cli v1.1.1 h1:J64v/xD7Clql+JVKSvkYojLOXu1ibnY9ZjGLwSt/89w= github.com/mitchellh/cli v1.1.1/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= @@ -79,23 +81,24 @@ github.com/zclconf/go-cty v1.2.0 h1:sPHsy7ADcIZQP3vILvTjrh74ZA175TFP5vqiNK1UmlI= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= +golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f h1:+Nyd8tzPX9R7BWHguqsrbFdRx3WQ/1ib8I44HXV5yTA= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/history/config.go b/history/config.go index 4d25a27..dfafa77 100644 --- a/history/config.go +++ b/history/config.go @@ -1,6 +1,8 @@ package history -import "github.com/minamijoyo/tfmigrate/storage" +import ( + storage "github.com/minamijoyo/tfmigrate-storage" +) // Config is a set of configurations for migration history management. type Config struct { diff --git a/history/controller.go b/history/controller.go index bd1cdee..f343a43 100644 --- a/history/controller.go +++ b/history/controller.go @@ -9,7 +9,7 @@ import ( "strings" "time" - "github.com/minamijoyo/tfmigrate/storage" + storage "github.com/minamijoyo/tfmigrate-storage" ) // Controller manages a migration history. diff --git a/history/controller_test.go b/history/controller_test.go index 61bf64d..b402d08 100644 --- a/history/controller_test.go +++ b/history/controller_test.go @@ -10,8 +10,8 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/minamijoyo/tfmigrate/storage" - "github.com/minamijoyo/tfmigrate/storage/mock" + storage "github.com/minamijoyo/tfmigrate-storage" + "github.com/minamijoyo/tfmigrate-storage/mock" ) func TestLoadMigrationFileNames(t *testing.T) { diff --git a/storage/config.go b/storage/config.go deleted file mode 100644 index 728cef9..0000000 --- a/storage/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package storage - -// Config is an interface of factory method for Storage -type Config interface { - // NewStorage returns a new instance of Storage. - NewStorage() (Storage, error) -} diff --git a/storage/local/config.go b/storage/local/config.go deleted file mode 100644 index b09d849..0000000 --- a/storage/local/config.go +++ /dev/null @@ -1,17 +0,0 @@ -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) -} diff --git a/storage/local/config_test.go b/storage/local/config_test.go deleted file mode 100644 index f60ff00..0000000 --- a/storage/local/config_test.go +++ /dev/null @@ -1,34 +0,0 @@ -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) - } - }) - } -} diff --git a/storage/local/storage.go b/storage/local/storage.go deleted file mode 100644 index 1cd333c..0000000 --- a/storage/local/storage.go +++ /dev/null @@ -1,48 +0,0 @@ -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) -} diff --git a/storage/local/storage_test.go b/storage/local/storage_test.go deleted file mode 100644 index ada56d2..0000000 --- a/storage/local/storage_test.go +++ /dev/null @@ -1,128 +0,0 @@ -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)) - } - } - }) - } -} diff --git a/storage/mock/config.go b/storage/mock/config.go deleted file mode 100644 index 72e8c42..0000000 --- a/storage/mock/config.go +++ /dev/null @@ -1,33 +0,0 @@ -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 -} diff --git a/storage/mock/config_test.go b/storage/mock/config_test.go deleted file mode 100644 index 550b0f2..0000000 --- a/storage/mock/config_test.go +++ /dev/null @@ -1,36 +0,0 @@ -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) - } - }) - } -} diff --git a/storage/mock/storage.go b/storage/mock/storage.go deleted file mode 100644 index afa15d7..0000000 --- a/storage/mock/storage.go +++ /dev/null @@ -1,50 +0,0 @@ -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 -} diff --git a/storage/mock/storage_test.go b/storage/mock/storage_test.go deleted file mode 100644 index 6a1e9b8..0000000 --- a/storage/mock/storage_test.go +++ /dev/null @@ -1,114 +0,0 @@ -package mock - -import ( - "context" - "testing" -) - -func TestStorageWrite(t *testing.T) { - cases := []struct { - desc string - config *Config - contents []byte - ok bool - }{ - { - desc: "simple", - config: &Config{ - Data: "", - WriteError: false, - ReadError: false, - }, - contents: []byte("foo"), - ok: true, - }, - { - desc: "write error", - config: &Config{ - Data: "", - WriteError: true, - ReadError: false, - }, - contents: []byte("foo"), - ok: false, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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 := []byte(s.data) - 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{ - Data: "foo", - WriteError: false, - ReadError: false, - }, - contents: []byte("foo"), - ok: true, - }, - { - desc: "read error", - config: &Config{ - Data: "foo", - WriteError: false, - ReadError: true, - }, - contents: nil, - ok: false, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - 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)) - } - } - }) - } -} diff --git a/storage/s3/client.go b/storage/s3/client.go deleted file mode 100644 index 03d5ac9..0000000 --- a/storage/s3/client.go +++ /dev/null @@ -1,60 +0,0 @@ -package s3 - -import ( - "fmt" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/aws/aws-sdk-go/service/s3/s3iface" - awsbase "github.com/hashicorp/aws-sdk-go-base" -) - -// Client is an abstraction layer for AWS S3 API. -// It is intended to be replaced with a mock for testing. -type Client interface { - // PutObjectWithContext puts a file to S3. - PutObjectWithContext(ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) - // GetObjectWithContext gets a file from S3. - GetObjectWithContext(ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) -} - -// client is a real implementation of the Client. -type client struct { - s3api s3iface.S3API -} - -// newClient returns a new instance of Client. -func newClient(config *Config) (Client, error) { - cfg := &awsbase.Config{ - AccessKey: config.AccessKey, - AssumeRoleARN: config.RoleARN, - Profile: config.Profile, - Region: config.Region, - SecretKey: config.SecretKey, - SkipCredsValidation: config.SkipCredentialsValidation, - SkipMetadataApiCheck: config.SkipMetadataAPICheck, - } - - sess, err := awsbase.GetSession(cfg) - if err != nil { - return nil, fmt.Errorf("failed to new s3 client: %s", err) - } - - client := s3.New(sess.Copy(&aws.Config{ - Endpoint: aws.String(config.Endpoint), - S3ForcePathStyle: aws.Bool(config.ForcePathStyle), - })) - - return client, nil -} - -// PutObjectWithContext puts a file to S3. -func (c *client) PutObjectWithContext(ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) { - return c.s3api.PutObjectWithContext(ctx, input, opts...) -} - -// GetObjectWithContext gets a file from S3. -func (c *client) GetObjectWithContext(ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) { - return c.s3api.GetObjectWithContext(ctx, input, opts...) -} diff --git a/storage/s3/config.go b/storage/s3/config.go deleted file mode 100644 index a42c412..0000000 --- a/storage/s3/config.go +++ /dev/null @@ -1,45 +0,0 @@ -package s3 - -import "github.com/minamijoyo/tfmigrate/storage" - -// Config is a config for s3 storage. -// This is expected to have almost the same options as Terraform s3 backend. -// https://www.terraform.io/docs/backends/types/s3.html -// However, it has many minor options and it's a pain to test all options from -// first, so we added only options we need for now. -type Config struct { - // Name of the bucket. - Bucket string `hcl:"bucket"` - // Path to the migration history file. - Key string `hcl:"key"` - - // AWS region. - Region string `hcl:"region,optional"` - // Custom endpoint for the AWS S3 API. - Endpoint string `hcl:"endpoint,optional"` - // AWS access key. - AccessKey string `hcl:"access_key,optional"` - // AWS secret key. - SecretKey string `hcl:"secret_key,optional"` - // Name of AWS profile in AWS shared credentials file. - Profile string `hcl:"profile,optional"` - // Amazon Resource Name (ARN) of the IAM Role to assume. - RoleARN string `hcl:"role_arn,optional"` - // Skip credentials validation via the STS API. - SkipCredentialsValidation bool `hcl:"skip_credentials_validation,optional"` - // Skip usage of EC2 Metadata API. - SkipMetadataAPICheck bool `hcl:"skip_metadata_api_check,optional"` - // Enable path-style S3 URLs (https:/// - // instead of https://.). - ForcePathStyle bool `hcl:"force_path_style,optional"` - // SSE KMS Key Id for optional server-side encryption enablement - KmsKeyID string `hcl:"kms_key_id,optional"` -} - -// 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, nil) -} diff --git a/storage/s3/config_test.go b/storage/s3/config_test.go deleted file mode 100644 index 1ede9f9..0000000 --- a/storage/s3/config_test.go +++ /dev/null @@ -1,43 +0,0 @@ -package s3 - -import "testing" - -func TestConfigNewStorage(t *testing.T) { - cases := []struct { - desc string - config *Config - ok bool - }{ - { - desc: "valid", - config: &Config{ - Bucket: "tfmigrate-test", - Key: "tfmigrate/history.json", - Region: "ap-northeast-1", - Endpoint: "http://localstack:4566", - AccessKey: "dummy", - SecretKey: "dummy", - Profile: "dev", - SkipCredentialsValidation: true, - SkipMetadataAPICheck: true, - ForcePathStyle: true, - }, - 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) - } - }) - } -} diff --git a/storage/s3/storage.go b/storage/s3/storage.go deleted file mode 100644 index c2d1d67..0000000 --- a/storage/s3/storage.go +++ /dev/null @@ -1,87 +0,0 @@ -package s3 - -import ( - "bytes" - "context" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/minamijoyo/tfmigrate/storage" -) - -// Storage is a storage.Storage implementation for AWS S3. -type Storage struct { - // config is a storage config for s3. - config *Config - // client is an instance of S3Client interface to call API. - // It is intended to be replaced with a mock for testing. - client Client -} - -var _ storage.Storage = (*Storage)(nil) - -// NewStorage returns a new instance of Storage. -func NewStorage(config *Config, client Client) (*Storage, error) { - if client == nil { - var err error - client, err = newClient(config) - if err != nil { - return nil, err - } - } - - s := &Storage{ - config: config, - client: client, - } - - return s, nil -} - -// Write writes migration history data to storage. -func (s *Storage) Write(ctx context.Context, b []byte) error { - input := &s3.PutObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(s.config.Key), - Body: bytes.NewReader(b), - } - if s.config.KmsKeyID != "" { - input.SSEKMSKeyId = &s.config.KmsKeyID - input.ServerSideEncryption = aws.String("aws:kms") - } - - _, err := s.client.PutObjectWithContext(ctx, input) - - return err -} - -// 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) { - input := &s3.GetObjectInput{ - Bucket: aws.String(s.config.Bucket), - Key: aws.String(s.config.Key), - } - - output, err := s.client.GetObjectWithContext(ctx, input) - if err != nil { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "NoSuchKey" { - // If the key does not exist - return []byte{}, nil - } - // unexpected error - return nil, err - } - - defer output.Body.Close() - - buf := bytes.NewBuffer(nil) - _, err = buf.ReadFrom(output.Body) - if err != nil { - return nil, err - } - - return buf.Bytes(), nil -} diff --git a/storage/s3/storage_test.go b/storage/s3/storage_test.go deleted file mode 100644 index 9095dd3..0000000 --- a/storage/s3/storage_test.go +++ /dev/null @@ -1,157 +0,0 @@ -package s3 - -import ( - "context" - "io/ioutil" - "strings" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/aws/request" - "github.com/aws/aws-sdk-go/service/s3" -) - -// mockClient is a mock implementation for testing. -type mockClient struct { - putOutput *s3.PutObjectOutput - getOutput *s3.GetObjectOutput - err error -} - -// PutObjectWithContext returns a mocked response. -func (c *mockClient) PutObjectWithContext(ctx aws.Context, input *s3.PutObjectInput, opts ...request.Option) (*s3.PutObjectOutput, error) { - return c.putOutput, c.err -} - -// GetObjectWithContext returns a mocked response. -func (c *mockClient) GetObjectWithContext(ctx aws.Context, input *s3.GetObjectInput, opts ...request.Option) (*s3.GetObjectOutput, error) { - return c.getOutput, c.err -} - -func TestStorageWrite(t *testing.T) { - cases := []struct { - desc string - config *Config - client Client - contents []byte - ok bool - }{ - { - desc: "simple", - config: &Config{ - Bucket: "tfmigrate-test", - Key: "tfmigrate/history.json", - }, - client: &mockClient{ - putOutput: &s3.PutObjectOutput{}, - err: nil, - }, - contents: []byte("foo"), - ok: true, - }, - { - desc: "bucket does not exist", - config: &Config{ - Bucket: "not-exist-bucket", - Key: "tfmigrate/history.json", - }, - client: &mockClient{ - putOutput: nil, - err: awserr.New("NoSuchBucket", "The specified bucket does not exist.", nil), - }, - contents: []byte("foo"), - ok: false, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - s, err := NewStorage(tc.config, tc.client) - 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") - } - }) - } -} - -func TestStorageRead(t *testing.T) { - cases := []struct { - desc string - config *Config - client Client - contents []byte - ok bool - }{ - { - desc: "simple", - config: &Config{ - Bucket: "tfmigrate-test", - Key: "tfmigrate/history.json", - }, - client: &mockClient{ - getOutput: &s3.GetObjectOutput{ - Body: ioutil.NopCloser(strings.NewReader("foo")), - }, - err: nil, - }, - contents: []byte("foo"), - ok: true, - }, - { - desc: "bucket does not exist", - config: &Config{ - Bucket: "not-exist-bucket", - Key: "tfmigrate/history.json", - }, - client: &mockClient{ - getOutput: nil, - err: awserr.New("NoSuchBucket", "The specified bucket does not exist.", nil), - }, - contents: nil, - ok: false, - }, - { - desc: "key does not exist", - config: &Config{ - Bucket: "tfmigrate-test", - Key: "not_exist.json", - }, - client: &mockClient{ - getOutput: nil, - err: awserr.New("NoSuchKey", "The specified key does not exist.", nil), - }, - contents: []byte{}, - ok: true, - }, - } - - for _, tc := range cases { - t.Run(tc.desc, func(t *testing.T) { - s, err := NewStorage(tc.config, tc.client) - 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: %s", 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)) - } - } - }) - } -} diff --git a/storage/storage.go b/storage/storage.go deleted file mode 100644 index 6bc15c1..0000000 --- a/storage/storage.go +++ /dev/null @@ -1,17 +0,0 @@ -package storage - -import "context" - -// Storage is an abstraction layer for migration history data store. -// As you know, this is the equivalent of Terraform's backend, but we have -// implemented it by ourselves not to depend on Terraform internals directly. -// To support multiple cloud storages, write and read operations are limited to -// simple byte operations and a domain specific logic should not be included. -type Storage interface { - // Write writes migration history data to storage. - Write(ctx context.Context, b []byte) error - // 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. - Read(ctx context.Context) ([]byte, error) -}