Skip to content

Commit

Permalink
Split config and storage files to clarify structure
Browse files Browse the repository at this point in the history
  • Loading branch information
minamijoyo committed Mar 20, 2022
1 parent 61c37ab commit 2c9b27e
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 251 deletions.
7 changes: 7 additions & 0 deletions storage/config.go
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)
}
18 changes: 18 additions & 0 deletions storage/local/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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) {
s := NewStorage(c.Path)
return s, nil
}
34 changes: 34 additions & 0 deletions storage/local/config_test.go
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)
}
})
}
}
15 changes: 0 additions & 15 deletions storage/local/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,6 @@ 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) {
s := NewStorage(c.Path)
return s, nil
}

// 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.
Expand Down
31 changes: 0 additions & 31 deletions storage/local/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,37 +8,6 @@ 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)
}
})
}
}

func TestStorageWrite(t *testing.T) {
cases := []struct {
desc string
Expand Down
33 changes: 33 additions & 0 deletions storage/mock/config.go
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 := NewStorage(c.Data, c.WriteError, c.ReadError)

// store a reference for test assertion.
c.s = s
return s, nil
}

// StorageData returns a raw data in mock storage for testing.
func (c *Config) StorageData() string {
return c.s.data
}
36 changes: 36 additions & 0 deletions storage/mock/config_test.go
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)
}
})
}
}
30 changes: 0 additions & 30 deletions storage/mock/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,36 +7,6 @@ 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 := NewStorage(c.Data, c.WriteError, c.ReadError)

// store a reference for test assertion.
c.s = s
return s, nil
}

// StorageData returns a raw data in mock storage for testing.
func (c *Config) StorageData() string {
return c.s.data
}

// Storage is a storage.Storage implementation for testing.
// It writes and reads data from memory.
type Storage struct {
Expand Down
33 changes: 0 additions & 33 deletions storage/mock/storage_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,39 +5,6 @@ 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)
}
})
}
}

func TestStorageWrite(t *testing.T) {
cases := []struct {
desc string
Expand Down
60 changes: 60 additions & 0 deletions storage/s3/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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...)
}
45 changes: 45 additions & 0 deletions storage/s3/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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://<HOST>/<BUCKET>
// instead of https://<BUCKET>.<HOST>).
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)
}

0 comments on commit 2c9b27e

Please sign in to comment.