Skip to content

Commit 654bdcf

Browse files
committed
Add Azure Blob storage backend
Allows tfmigrate to use an Azure Blob to store history.
1 parent 70e9f7d commit 654bdcf

File tree

9 files changed

+380
-4
lines changed

9 files changed

+380
-4
lines changed

README.md

+28
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,7 @@ The storage block has one label, which is a type of storage. Valid types are as
429429
- `local`: Save a history file to local filesystem.
430430
- `s3`: Save a history file to AWS S3.
431431
- `gcs`: Save a history file to GCS (Google Cloud Storage).
432+
- `azure`: Save a history file to Azure Blob storage.
432433

433434
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.
434435

@@ -512,6 +513,33 @@ tfmigrate {
512513

513514
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).
514515

516+
#### storage block (azure)
517+
518+
The `azure` storage has the following attributes:
519+
520+
- `access_key` (optional): Access key for the blob storage container. If omitted, this value is read from the `TFMIGRATE_AZURE_STORAGE_ACCESS_KEY` environment variable.
521+
- `account_name` (required): Name of the storage account.
522+
- `container_name` (required): Name of the storage container.
523+
- `blob_name` (optional): Name of the migration history file. Defaults to `history.json` if omitted.
524+
525+
Note that the `azure` backend expects that the storage account, container and blob already exist.
526+
527+
An example of configuration file is as follows.
528+
529+
```hcl
530+
tfmigrate {
531+
migration_dir = "./tfmigrate"
532+
history {
533+
storage "azure" {
534+
access_key = "<storage access key>"
535+
account_name = "storage"
536+
container_name = "tfmigrate-test"
537+
blob_name = "tfmigrate-history.json"
538+
}
539+
}
540+
}
541+
```
542+
515543
## Migration file
516544

517545
You can write terraform state operations in HCL. The syntax of migration file is as follows:

config/storage.go

+15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/hashicorp/hcl/v2"
77
"github.com/hashicorp/hcl/v2/gohcl"
88
"github.com/minamijoyo/tfmigrate/storage"
9+
"github.com/minamijoyo/tfmigrate/storage/azure"
910
"github.com/minamijoyo/tfmigrate/storage/gcs"
1011
"github.com/minamijoyo/tfmigrate/storage/local"
1112
"github.com/minamijoyo/tfmigrate/storage/mock"
@@ -35,6 +36,9 @@ func parseStorageBlock(b StorageBlock) (storage.Config, error) {
3536
case "local":
3637
return parseLocalStorageBlock(b)
3738

39+
case "azure":
40+
return parseAzureStorageBlock(b)
41+
3842
case "s3":
3943
return parseS3StorageBlock(b)
4044

@@ -68,6 +72,17 @@ func parseLocalStorageBlock(b StorageBlock) (storage.Config, error) {
6872
return &config, nil
6973
}
7074

75+
// parseAzureStorageBlock parses a storage block for azure and returns a storage.Config.
76+
func parseAzureStorageBlock(b StorageBlock) (storage.Config, error) {
77+
var config azure.Config
78+
diags := gohcl.DecodeBody(b.Remain, nil, &config)
79+
if diags.HasErrors() {
80+
return nil, diags
81+
}
82+
83+
return &config, nil
84+
}
85+
7186
// parseS3StorageBlock parses a storage block for s3 and returns a storage.Config.
7287
func parseS3StorageBlock(b StorageBlock) (storage.Config, error) {
7388
var config s3.Config

go.mod

+13-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ go 1.22
44

55
require (
66
cloud.google.com/go/storage v1.25.0
7+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1
8+
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1
79
github.com/aws/aws-sdk-go v1.43.22
810
github.com/davecgh/go-spew v1.1.1
911
github.com/google/go-cmp v0.5.8
@@ -20,32 +22,39 @@ require (
2022
cloud.google.com/go v0.102.1 // indirect
2123
cloud.google.com/go/compute v1.7.0 // indirect
2224
cloud.google.com/go/iam v0.3.0 // indirect
25+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 // indirect
26+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 // indirect
27+
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 // indirect
2328
github.com/agext/levenshtein v1.2.1 // indirect
2429
github.com/apparentlymart/go-textseg v1.0.0 // indirect
2530
github.com/apparentlymart/go-textseg/v12 v12.0.0 // indirect
2631
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310 // indirect
2732
github.com/bgentry/speakeasy v0.1.0 // indirect
2833
github.com/fatih/color v1.7.0 // indirect
34+
github.com/golang-jwt/jwt/v5 v5.2.0 // indirect
2935
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
3036
github.com/golang/protobuf v1.5.2 // indirect
31-
github.com/google/uuid v1.3.0 // indirect
37+
github.com/google/uuid v1.6.0 // indirect
3238
github.com/googleapis/enterprise-certificate-proxy v0.1.0 // indirect
3339
github.com/googleapis/gax-go/v2 v2.4.0 // indirect
3440
github.com/hashicorp/errwrap v1.0.0 // indirect
3541
github.com/hashicorp/go-cleanhttp v0.5.0 // indirect
3642
github.com/hashicorp/go-multierror v1.0.0 // indirect
3743
github.com/jmespath/go-jmespath v0.4.0 // indirect
44+
github.com/kylelemons/godebug v1.1.0 // indirect
3845
github.com/mattn/go-colorable v0.0.9 // indirect
3946
github.com/mattn/go-isatty v0.0.3 // indirect
4047
github.com/mitchellh/go-homedir v1.1.0 // indirect
4148
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
49+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
4250
github.com/posener/complete v1.1.1 // indirect
4351
github.com/zclconf/go-cty v1.2.0 // indirect
4452
go.opencensus.io v0.23.0 // indirect
45-
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e // indirect
53+
golang.org/x/crypto v0.18.0 // indirect
54+
golang.org/x/net v0.20.0 // indirect
4655
golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 // indirect
47-
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 // indirect
48-
golang.org/x/text v0.3.7 // indirect
56+
golang.org/x/sys v0.16.0 // indirect
57+
golang.org/x/text v0.14.0 // indirect
4958
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
5059
google.golang.org/api v0.88.0 // indirect
5160
google.golang.org/appengine v1.6.7 // indirect

go.sum

+40
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,20 @@ cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq
6060
cloud.google.com/go/storage v1.25.0 h1:D2Dn0PslpK7Z3B2AvuUHyIC762bDbGJdlmQlCBR71os=
6161
cloud.google.com/go/storage v1.25.0/go.mod h1:Qys4JU+jeup3QnuKKAosWuxrD95C4MSqxfVDnSirDsI=
6262
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
63+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1 h1:lGlwhPtrX6EVml1hO0ivjkUxsSyl4dsiw9qcA1k/3IQ=
64+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.1/go.mod h1:RKUqNu35KJYcVG/fqTRqmuXJZYNhYkBrnC/hX7yGbTA=
65+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2 h1:c4k2FIYIh4xtwqrQwV0Ct1v5+ehlNXj5NI/MWVsiTkQ=
66+
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.9.2/go.mod h1:5FDJtLEO/GxwNgUxbwrY3LP0pEoThTQJtk2oysdXHxM=
67+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1 h1:sO0/P7g68FrryJzljemN+6GTssUXdANk6aJ7T1ZxnsQ=
68+
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.5.1/go.mod h1:h8hyGFDsU5HMivxiS2iYFZsgDbU9OnnJ163x5UGVKYo=
69+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1 h1:6oNBlSdi1QqM1PNW7FPA6xOGA5UNsXnkaYZz9vdPGhA=
70+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.1/go.mod h1:s4kgfzA0covAXNicZHDMN58jExvcng2mC/DepXiF1EI=
71+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2 h1:LqbJ/WzJUwBf8UiaSzgX7aMclParm9/5Vgp+TY51uBQ=
72+
github.com/Azure/azure-sdk-for-go/sdk/internal v1.5.2/go.mod h1:yInRyqWXAuaPrgI7p70+lDDgh3mlBohis29jGMISnmc=
73+
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1 h1:fXPMAmuh0gDuRDey0atC8cXBuKIlqCzCkL8sm1n9Ov0=
74+
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.3.1/go.mod h1:SUZc9YRRHfx2+FAQKNDGrssXehqLpxmwRv2mC/5ntj4=
75+
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1 h1:DzHpqpoJVaCgOUdVHxE8QB52S6NiVdDQvGlny1qvPqA=
76+
github.com/AzureAD/microsoft-authentication-library-for-go v1.2.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
6377
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
6478
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
6579
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@@ -116,6 +130,8 @@ github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2
116130
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
117131
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
118132
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
133+
github.com/golang-jwt/jwt/v5 v5.2.0 h1:d/ix8ftRUorsN+5eMIlF4T6J8CAt9rch3My2winC1Jw=
134+
github.com/golang-jwt/jwt/v5 v5.2.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
119135
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
120136
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
121137
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -191,6 +207,10 @@ github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm4
191207
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
192208
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
193209
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
210+
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
211+
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
212+
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
213+
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
194214
github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
195215
github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw=
196216
github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8=
@@ -237,6 +257,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
237257
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
238258
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 h1:MtvEpTB6LX3vkb4ax0b5D2DHbNAUsen0Gx5wZoq3lV4=
239259
github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k=
260+
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
261+
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
240262
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
241263
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
242264
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
@@ -249,6 +271,8 @@ github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG
249271
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
250272
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 h1:DpOJ2HYzCv8LZP15IdmG+YdwD2luVPHITV96TkirNBM=
251273
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
274+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
275+
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
252276
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
253277
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
254278
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -291,6 +315,10 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
291315
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
292316
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
293317
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
318+
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
319+
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
320+
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
321+
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
294322
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
295323
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
296324
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -370,6 +398,10 @@ golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su
370398
golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
371399
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e h1:TsQ7F31D3bUCLeqPT0u+yjp1guoArKaNKmCr22PYgTQ=
372400
golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
401+
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
402+
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
403+
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
404+
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
373405
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
374406
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
375407
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -466,6 +498,11 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
466498
golang.org/x/sys v0.0.0-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
467499
golang.org/x/sys v0.0.0-20220624220833-87e55d714810 h1:rHZQSjJdAI4Xf5Qzeh2bBc5YJIkPFVM6oDtMFYmgws0=
468500
golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
501+
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
502+
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
503+
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
504+
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU=
505+
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
469506
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
470507
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
471508
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -478,6 +515,8 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
478515
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
479516
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
480517
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
518+
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
519+
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
481520
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
482521
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
483522
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -727,6 +766,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
727766
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
728767
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
729768
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
769+
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
730770
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
731771
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
732772
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

storage/azure/client.go

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package azure
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"fmt"
7+
"os"
8+
9+
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
10+
)
11+
12+
type Client interface {
13+
// Read an object from an Azure blob.
14+
Read(ctx context.Context, container, blob string) ([]byte, error)
15+
16+
// Write an object onto an Azure blob.
17+
Write(ctx context.Context, container, blob string, p []byte) error
18+
}
19+
20+
type client struct {
21+
BlobAPI *azblob.Client
22+
}
23+
24+
// newClient returns a new instance of Client.
25+
func newClient(config *Config) (Client, error) {
26+
// If the access key isn't defined in the configuration, try to read it from the environment.
27+
if config.AccessKey == "" {
28+
config.AccessKey = os.Getenv("TFMIGRATE_AZURE_STORAGE_ACCESS_KEY")
29+
}
30+
31+
cred, err := azblob.NewSharedKeyCredential(config.AccountName, config.AccessKey)
32+
if err != nil {
33+
return nil, err
34+
}
35+
36+
url := fmt.Sprintf("https://%s.blob.core.windows.net/", config.AccountName)
37+
c, err := azblob.NewClientWithSharedKeyCredential(url, cred, nil)
38+
39+
return &client{c}, err
40+
}
41+
42+
// Read an object from an Azure blob.
43+
func (c *client) Read(ctx context.Context, container, blob string) ([]byte, error) {
44+
resp, err := c.BlobAPI.DownloadStream(ctx, container, blob, nil)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
bs := bytes.Buffer{}
50+
r := resp.NewRetryReader(ctx, &azblob.RetryReaderOptions{})
51+
defer r.Close()
52+
53+
_, err = bs.ReadFrom(r)
54+
55+
return bs.Bytes(), err
56+
}
57+
58+
// Write an object onto an Azure blob.
59+
func (c *client) Write(ctx context.Context, container, blob string, p []byte) error {
60+
_, err := c.BlobAPI.UploadBuffer(ctx, container, blob, p, nil)
61+
62+
return err
63+
}

storage/azure/config.go

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package azure
2+
3+
import "github.com/minamijoyo/tfmigrate/storage"
4+
5+
type Config struct {
6+
AccessKey string `hcl:"access_key,optional"`
7+
AccountName string `hcl:"account_name"`
8+
ContainerName string `hcl:"container_name"`
9+
BlobName string `hcl:"blob_name,optional"`
10+
}
11+
12+
// Config implements a storage.Config.
13+
var _ storage.Config = (*Config)(nil)
14+
15+
// NewStorage returns a new instance of storage.Storage.
16+
func (c *Config) NewStorage() (storage.Storage, error) {
17+
return NewStorage(c, nil)
18+
}

storage/azure/config_test.go

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package azure
2+
3+
import "testing"
4+
5+
func TestConfigNewStorage(t *testing.T) {
6+
cases := []struct {
7+
desc string
8+
config *Config
9+
ok bool
10+
}{
11+
{
12+
desc: "valid",
13+
config: &Config{
14+
AccountName: "tfmigrate-test",
15+
ContainerName: "tfmigrate",
16+
BlobName: "history.json",
17+
},
18+
ok: true,
19+
},
20+
{
21+
desc: "valid",
22+
config: &Config{
23+
AccessKey: "ZHVtbXkK", // expected to be a base64-encoded string
24+
AccountName: "tfmigrate-test",
25+
ContainerName: "tfmigrate",
26+
BlobName: "history.json",
27+
},
28+
ok: true,
29+
},
30+
}
31+
32+
for _, tc := range cases {
33+
t.Run(tc.desc, func(t *testing.T) {
34+
got, err := tc.config.NewStorage()
35+
if tc.ok && err != nil {
36+
t.Fatalf("unexpected err: %s", err)
37+
}
38+
if !tc.ok && err == nil {
39+
t.Fatalf("expected to return an error, but no error, got: %#v", got)
40+
}
41+
if tc.ok {
42+
_ = got.(*Storage)
43+
}
44+
})
45+
}
46+
}

0 commit comments

Comments
 (0)