Skip to content

Commit

Permalink
feat: Support for GCS history backend
Browse files Browse the repository at this point in the history
  • Loading branch information
KengoTODA committed Aug 24, 2022
1 parent 251bbb0 commit b9da17a
Show file tree
Hide file tree
Showing 10 changed files with 834 additions and 4 deletions.
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,7 @@ The storage block has one label, which is a type of storage. Valid types are as

- `local`: Save a history file to local filesystem.
- `s3`: Save a history file to AWS S3.
- `gcs`: Save a history file to GCS (Google Cloud Storage).

If your cloud provider has not been supported yet, as a workaround, you can use `local` storage and synchronize a history file to your cloud storage with a wrapper script.

Expand Down Expand Up @@ -479,6 +480,31 @@ tfmigrate {
}
```

#### storage block (gcs)

The `gcs` storage has the following attributes:

- `bucket` (required): Name of the bucket.
- `name` (required): Path to the migration history file.

Note that this storage implementation refers the Application Default Credentials (ADC) for authentication.

An example of configuration file is as follows.

```hcl
tfmigrate {
migration_dir = "./tfmigrate"
history {
storage "gcs" {
bucket = "tfstate-test"
name = "tfmigrate/history.json"
}
}
}
```

If you want to connect to an emulator instead of GCS, set the `STORAGE_EMULATOR_HOST` environment variable as required by the [Go library for GCS](https://pkg.go.dev/cloud.google.com/go/storage).

## Migration file

You can write terraform state operations in HCL. The syntax of migration file is as follows:
Expand Down
14 changes: 14 additions & 0 deletions config/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/gohcl"
storage "github.com/minamijoyo/tfmigrate-storage"
"github.com/minamijoyo/tfmigrate-storage/gcs"
"github.com/minamijoyo/tfmigrate-storage/local"
"github.com/minamijoyo/tfmigrate-storage/mock"
"github.com/minamijoyo/tfmigrate-storage/s3"
Expand Down Expand Up @@ -37,6 +38,9 @@ func parseStorageBlock(b StorageBlock) (storage.Config, error) {
case "s3":
return parseS3StorageBlock(b)

case "gcs":
return parseGCSStorageBlock(b)

default:
return nil, fmt.Errorf("unknown history storage type: %s", b.Type)
}
Expand Down Expand Up @@ -74,3 +78,13 @@ func parseS3StorageBlock(b StorageBlock) (storage.Config, error) {

return &config, nil
}

func parseGCSStorageBlock(b StorageBlock) (storage.Config, error) {
var config gcs.Config
diags := gohcl.DecodeBody(b.Remain, nil, &config)
if diags.HasErrors() {
return nil, diags
}

return &config, nil
}
101 changes: 101 additions & 0 deletions config/storage_gcs_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package config

import (
"reflect"
"testing"

storage "github.com/minamijoyo/tfmigrate-storage"
"github.com/minamijoyo/tfmigrate-storage/gcs"
)

func TestParseGCSStorageBlock(t *testing.T) {
cases := []struct {
desc string
source string
want storage.Config
ok bool
}{
{
desc: "valid (required)",
source: `
tfmigrate {
history {
storage "gcs" {
bucket = "tfmigrate-test"
name = "tfmigrate/history.json"
}
}
}
`,
want: &gcs.Config{
Bucket: "tfmigrate-test",
Name: "tfmigrate/history.json",
},
ok: true,
},
{
desc: "valid (with optional)",
source: `
tfmigrate {
history {
storage "gcs" {
bucket = "tfmigrate-test"
name = "tfmigrate/history.json"
}
}
}
`,
want: &gcs.Config{
Bucket: "tfmigrate-test",
Name: "tfmigrate/history.json",
},
ok: true,
},
{
desc: "missing required attribute (bucket)",
source: `
tfmigrate {
history {
storage "gcs" {
Name = "tfmigrate/history.json"
}
}
}
`,
want: nil,
ok: false,
},
{
desc: "missing required attribute (name)",
source: `
tfmigrate {
history {
storage "gcs" {
bucket = "tfmigrate-test"
}
}
}
`,
want: nil,
ok: false,
},
}

for _, tc := range cases {
t.Run(tc.desc, func(t *testing.T) {
config, err := ParseConfigurationFile("test.hcl", []byte(tc.source))
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", config)
}
if tc.ok {
got := config.History.Storage
if !reflect.DeepEqual(got, tc.want) {
t.Errorf("got: %#v, want: %#v", got, tc.want)
}
}
})
}
}
25 changes: 21 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,33 @@ go 1.19

require (
github.com/davecgh/go-spew v1.1.1
github.com/google/go-cmp v0.5.2
github.com/google/go-cmp v0.5.8
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/minamijoyo/tfmigrate-storage v0.0.0-20220824073652-6dd8f2fd2948
github.com/mitchellh/cli v1.1.1
github.com/spf13/pflag v1.0.2
)

require (
cloud.google.com/go v0.102.1 // indirect
cloud.google.com/go/compute v1.7.0 // indirect
cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/storage v1.25.0 // indirect
github.com/agext/levenshtein v1.2.1 // indirect
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/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
github.com/googleapis/gax-go/v2 v2.4.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
Expand All @@ -33,7 +42,15 @@ 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-20211216021012-1d35b9e2eb4e // indirect
go.opencensus.io v0.23.0 // indirect
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 // indirect
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
google.golang.org/api v0.88.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220720214146-176da50484ac // indirect
google.golang.org/grpc v1.48.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
)
Loading

0 comments on commit b9da17a

Please sign in to comment.