Updates `aws-sdk-go-base` to `v2`

pull/33860/head
Graham Davison 3 years ago
parent 3d98730a0b
commit f0fdd3c77c

@ -17,7 +17,10 @@ require (
github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13
github.com/apparentlymart/go-versions v1.0.1
github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2
github.com/aws/aws-sdk-go v1.44.122
github.com/aws/aws-sdk-go-v2 v1.20.0
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.1
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1
github.com/bgentry/speakeasy v0.1.0
github.com/bmatcuk/doublestar v1.1.5
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e
@ -28,7 +31,7 @@ require (
github.com/golang/mock v1.6.0
github.com/google/go-cmp v0.5.9
github.com/google/uuid v1.3.0
github.com/hashicorp/aws-sdk-go-base v0.7.1
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.32
github.com/hashicorp/consul/api v1.13.0
github.com/hashicorp/consul/sdk v0.8.0
github.com/hashicorp/copywrite v0.16.3
@ -37,7 +40,7 @@ require (
github.com/hashicorp/go-checkpoint v0.5.0
github.com/hashicorp/go-cleanhttp v0.5.2
github.com/hashicorp/go-getter v1.7.2
github.com/hashicorp/go-hclog v1.4.0
github.com/hashicorp/go-hclog v1.5.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-plugin v1.4.3
github.com/hashicorp/go-retryablehttp v0.7.4
@ -133,6 +136,24 @@ require (
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da // indirect
github.com/armon/go-radix v1.0.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
github.com/aws/aws-sdk-go v1.44.122 // indirect
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 // indirect
github.com/aws/aws-sdk-go-v2/config v1.18.28 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 // indirect
github.com/aws/aws-sdk-go-v2/service/iam v1.21.1 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.31 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 // indirect
github.com/aws/smithy-go v1.14.0 // indirect
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
@ -146,7 +167,7 @@ require (
github.com/creack/pty v1.1.18 // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/dylanmei/iso8601 v0.1.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/fsnotify/fsnotify v1.5.4 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
@ -171,6 +192,7 @@ require (
github.com/hashicorp/go-slug v0.11.1 // indirect
github.com/hashicorp/golang-lru v0.5.1 // indirect
github.com/hashicorp/serf v0.9.6 // indirect
github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect
github.com/henvic/httpretty v0.0.6 // indirect
github.com/huandu/xstrings v1.3.3 // indirect
@ -220,7 +242,7 @@ require (
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.16.0 // indirect
go.opentelemetry.io/otel/metric v1.16.0 // indirect
go.opentelemetry.io/proto/otlp v0.20.0 // indirect
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 // indirect
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea // indirect
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/time v0.3.0 // indirect

@ -300,19 +300,65 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef h1:46PFijGLmAjMPwCCCo7Jf0W6f9slllCkkv7vyc1yOSg=
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw=
github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo=
github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo=
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
github.com/aws/aws-sdk-go-v2 v1.19.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2 v1.20.0 h1:INUDpYLt4oiPOJl0XwZDK2OVAVf0Rzo+MGVTv9f+gy8=
github.com/aws/aws-sdk-go-v2 v1.20.0/go.mod h1:uWOr0m0jDsiWw8nnXiqZ+YG6LdvAlGYDLLf2NmHZoy4=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11 h1:/MS8AzqYNAhhRNalOmxUvYs8VEbNGifTnzhPFdcRQkQ=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.11/go.mod h1:va22++AdXht4ccO3kH2SHkHHYvZ2G9Utz+CXKmm2CaU=
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
github.com/aws/aws-sdk-go-v2/config v1.18.28 h1:TINEaKyh1Td64tqFvn09iYpKiWjmHYrG1fa91q2gnqw=
github.com/aws/aws-sdk-go-v2/config v1.18.28/go.mod h1:nIL+4/8JdAuNHEjn/gPEXqtnS02Q3NXB/9Z7o5xE4+A=
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
github.com/aws/aws-sdk-go-v2/credentials v1.13.27 h1:dz0yr/yR1jweAnsCx+BmjerUILVPQ6FS5AwF/OyG1kA=
github.com/aws/aws-sdk-go-v2/credentials v1.13.27/go.mod h1:syOqAek45ZXZp29HlnRS/BNgMIW6uiRmeuQsz4Qh2UE=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5 h1:kP3Me6Fy3vdi+9uHd7YLr6ewPxRL+PU6y15urfTaamU=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.13.5/go.mod h1:Gj7tm95r+QsDoN2Fhuz/3npQvcZbkEf5mL70n3Xfluc=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.35/go.mod h1:ipR5PvpSPqIqL5Mi82BxLnfMkHVbmco8kUwO2xrCi0M=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37 h1:zr/gxAZkMcvP71ZhQOcvdm8ReLjFgIXnIn0fw5AM7mo=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.37/go.mod h1:Pdn4j43v49Kk6+82spO3Tu5gSeQXRsxo56ePPQAvFiA=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.29/go.mod h1:M/eUABlDbw2uVrdAn+UsI6M727qp2fxkp8K0ejcBDUY=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31 h1:0HCMIkAkVY9KMgueD8tf4bRTUanzEYvhw7KkPXIMpO0=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.31/go.mod h1:fTJDMe8LOFYtqiFFFeHA+SVMAwqLhoq0kcInYoLa9Js=
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36 h1:8r5m1BoAWkn0TDC34lUculryf7nUF25EgIMdjvGCkgo=
github.com/aws/aws-sdk-go-v2/internal/ini v1.3.36/go.mod h1:Rmw2M1hMVTwiUhjwMoIBFWFJMhvJbct06sSidxInkhY=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0 h1:U5yySdwt2HPo/pnQec04DImLzWORbeWML1fJiLkKruI=
github.com/aws/aws-sdk-go-v2/internal/v4a v1.1.0/go.mod h1:EhC/83j8/hL/UB1WmExo3gkElaja/KlmZM/gl1rTfjM=
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.1 h1:E9giR4LylJO/iu/75Sb8golqceDcM26k7RZ8ng5MQ2k=
github.com/aws/aws-sdk-go-v2/service/dynamodb v1.21.1/go.mod h1:HVZN4RDNEO/u7XvWytqUBKm9BsBjt5OKVnRTW8NMMVc=
github.com/aws/aws-sdk-go-v2/service/iam v1.21.1 h1:VTCWgsrromZqnlRgfziqqWWcW7LFkQLwJVYgf/5zgWA=
github.com/aws/aws-sdk-go-v2/service/iam v1.21.1/go.mod h1:LBsjrFczXiQLASO6FtDGTeHuZh6oHuIH6VKaOozFghg=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12 h1:uAiiHnWihGP2rVp64fHwzLDrswGjEjsPszwRYMiYQPU=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.9.12/go.mod h1:fUTHpOXqRQpXvEpDPSa3zxCc2fnpW6YnBoba+eQr+Bg=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32 h1:kvN1jPHr9UffqqG3bSgZ8tx4+1zKVHz/Ktw/BwW6hX8=
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.1.32/go.mod h1:QmMEM7es84EUkbYWcpnkx8i5EW2uERPfrTFeOch128Y=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.31 h1:L6ya7BMQ12LV6rsE1jiKm9ajsrnkRAYalatWRwFawHk=
github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.7.31/go.mod h1:tp7VzPEi+bKtSCP5fSrsZrB271L6oC8CWP3g2cZLofU=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.29/go.mod h1:fDbkK4o7fpPXWn8YAPmTieAMuB9mk/VgvW64uaUqxd4=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31 h1:auGDJ0aLZahF5SPvkJ6WcUuX7iQ7kyl2MamV7Tm8QBk=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.31/go.mod h1:3+lloe3sZuBQw1aBc5MyndvodzQlyqCZ7x1QPDHaWP4=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0 h1:Wgjft9X4W5pMeuqgPCHIQtbZ87wsgom7S5F8obreg+c=
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.15.0/go.mod h1:FWNzS4+zcWAP05IF7TDYTY1ysZAzIvogxWaDT9p8fsA=
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1 h1:mTgFVlfQT8gikc5+/HwD8UL9jnUro5MGv8n/VEYF12I=
github.com/aws/aws-sdk-go-v2/service/s3 v1.38.1/go.mod h1:6SOWLiobcZZshbmECRTADIRYliPL0etqFSigauQEeT0=
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13 h1:sWDv7cMITPcZ21QdreULwxOOAmE05JjEsT6fCDtDA9k=
github.com/aws/aws-sdk-go-v2/service/sso v1.12.13/go.mod h1:DfX0sWuT46KpcqbMhJ9QWtxAIP1VozkDWf8VAkByjYY=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13 h1:BFubHS/xN5bjl818QaroN6mQdjneYQ+AOx44KNXlyH4=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.13/go.mod h1:BzqsVVFduubEmzrVtUFQQIQdFqvUItF8XUq2EnS8Wog=
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3 h1:e5mnydVdCVWxP+5rPAGi2PYxC7u2OZgH1ypC114H04U=
github.com/aws/aws-sdk-go-v2/service/sts v1.19.3/go.mod h1:yVGZA1CPkmUhBdA039jXNJJG7/6t+G+EBWmFq23xqnY=
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/aws/smithy-go v1.14.0 h1:+X90sB94fizKjDmwb4vyl2cTTPXTE5E2G/1mjByb0io=
github.com/aws/smithy-go v1.14.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA=
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
@ -398,8 +444,9 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/evanphx/json-patch v4.12.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
github.com/form3tech-oss/jwt-go v3.2.3+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@ -436,7 +483,6 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
github.com/go-openapi/strfmt v0.21.3 h1:xwhj5X6CjXEZZHMWy1zKJxvW9AfHC9pkyUjLvHtKG7o=
github.com/go-openapi/strfmt v0.21.3/go.mod h1:k+RzNO0Da+k3FrrynSNN8F7n/peCmQQqbbXjtDfvmGg=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
@ -577,8 +623,8 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg=
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
github.com/hashicorp/aws-sdk-go-base v0.7.1 h1:7s/aR3hFn74tYPVihzDyZe7y/+BorN70rr9ZvpV3j3o=
github.com/hashicorp/aws-sdk-go-base v0.7.1/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.32 h1:ZkBqGzdgFExZhP/dFNPm1wgz8kw/fQwYLNjxZMzHz7w=
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.32/go.mod h1:YsIAxyM3YRbiAggDy640u+LcY9YghjHqUIfjrKLqC1s=
github.com/hashicorp/consul/api v1.13.0 h1:2hnLQ0GjQvw7f3O61jMO8gbasZviZTrt9R8WzgiirHc=
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
github.com/hashicorp/consul/sdk v0.8.0 h1:OJtKBtEjboEZvG6AOUdh4Z1Zbyu0WcxQ0qatRrZHTVU=
@ -604,8 +650,8 @@ github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrj
github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I=
github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c=
github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@ -661,6 +707,8 @@ github.com/hashicorp/memberlist v0.3.0 h1:8+567mCcFDnS5ADl7lrpxPMWiFCElyUEeW0gtj
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
github.com/hashicorp/serf v0.9.6 h1:uuEX1kLR6aoda1TBttmJQKDLZE1Ob7KN0NPdE7EtCDc=
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c h1:D8aRO6+mTqHfLsK/BC3j5OAoogv1WLRWzY1AaTo3rBg=
github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c/go.mod h1:Wn3Na71knbXc1G8Lh+yu/dQWWJeFQEpDeJMtWMtlmNI=
github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg=
@ -697,7 +745,6 @@ github.com/jedib0t/go-pretty/v6 v6.4.4/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgO
github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE=
github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@ -1072,8 +1119,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17 h1:3MTrJm4PyNL9NBqvYDSj3DHl46qQakyfqfWo4jgfaEM=
golang.org/x/exp v0.0.0-20220303212507-bbda1eaf7a17/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE=
golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=

@ -4,6 +4,7 @@
package s3
import (
"context"
"encoding/base64"
"fmt"
"os"
@ -12,13 +13,13 @@ import (
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/s3"
awsbase "github.com/hashicorp/aws-sdk-go-base"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
"github.com/aws/aws-sdk-go-v2/service/s3"
awsbase "github.com/hashicorp/aws-sdk-go-base/v2"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/terraform/version"
"github.com/zclconf/go-cty/cty"
@ -30,8 +31,9 @@ func New() backend.Backend {
}
type Backend struct {
s3Client *s3.S3
dynClient *dynamodb.DynamoDB
awsConfig aws.Config
s3Client *s3.Client
dynClient *dynamodb.Client
bucketName string
keyName string
@ -385,6 +387,8 @@ func formatDeprecations(attrs map[string]string) string {
// against the schema returned by ConfigSchema and passed validation
// via PrepareConfig.
func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
ctx := context.TODO()
var diags tfdiags.Diagnostics
if obj.IsNull() {
return diags
@ -454,75 +458,87 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
}
cfg := &awsbase.Config{
AccessKey: stringAttr(obj, "access_key"),
AccessKey: stringAttr(obj, "access_key"),
APNInfo: stdUserAgentProducts(),
// AssumeRoleWithWebIdentity
CallerDocumentationURL: "https://www.terraform.io/docs/language/settings/backends/s3.html",
CallerName: "S3 Backend",
CredsFilename: stringAttr(obj, "shared_credentials_file"),
DebugLogging: logging.IsDebugOrHigher(),
IamEndpoint: stringAttrDefaultEnvVar(obj, "iam_endpoint", "AWS_IAM_ENDPOINT"),
MaxRetries: intAttrDefault(obj, "max_retries", 5),
Profile: stringAttr(obj, "profile"),
Region: stringAttr(obj, "region"),
SecretKey: stringAttr(obj, "secret_key"),
SkipCredsValidation: boolAttr(obj, "skip_credentials_validation"),
SkipMetadataApiCheck: boolAttr(obj, "skip_metadata_api_check"),
StsEndpoint: stringAttrDefaultEnvVar(obj, "sts_endpoint", "AWS_STS_ENDPOINT"),
Token: stringAttr(obj, "token"),
UserAgentProducts: []*awsbase.UserAgentProduct{
{Name: "APN", Version: "1.0"},
{Name: "HashiCorp", Version: "1.0"},
{Name: "Terraform", Version: version.String()},
},
}
if val, ok := boolAttrOk(obj, "skip_metadata_api_check"); ok {
if val {
cfg.EC2MetadataServiceEnableState = imds.ClientDisabled
} else {
cfg.EC2MetadataServiceEnableState = imds.ClientEnabled
}
}
if val, ok := stringAttrOk(obj, "shared_credentials_file"); ok {
cfg.SharedCredentialsFiles = []string{
val,
}
}
if assumeRole := obj.GetAttr("assume_role"); !assumeRole.IsNull() {
ar := &awsbase.AssumeRole{}
if val, ok := stringAttrOk(assumeRole, "role_arn"); ok {
cfg.AssumeRoleARN = val
ar.RoleARN = val
}
if val, ok := stringAttrOk(assumeRole, "duration"); ok {
duration, _ := time.ParseDuration(val)
cfg.AssumeRoleDurationSeconds = int(duration.Seconds())
ar.Duration = duration
}
if val, ok := stringAttrOk(assumeRole, "external_id"); ok {
cfg.AssumeRoleExternalID = val
ar.ExternalID = val
}
if val, ok := stringAttrOk(assumeRole, "policy"); ok {
cfg.AssumeRolePolicy = strings.TrimSpace(val)
ar.Policy = strings.TrimSpace(val)
}
if val, ok := stringSetAttrOk(assumeRole, "policy_arns"); ok {
cfg.AssumeRolePolicyARNs = val
ar.PolicyARNs = val
}
if val, ok := stringAttrOk(assumeRole, "session_name"); ok {
cfg.AssumeRoleSessionName = val
ar.SessionName = val
}
if val, ok := stringMapAttrOk(assumeRole, "tags"); ok {
cfg.AssumeRoleTags = val
ar.Tags = val
}
if val, ok := stringSetAttrOk(assumeRole, "transitive_tag_keys"); ok {
cfg.AssumeRoleTransitiveTagKeys = val
ar.TransitiveTagKeys = val
}
} else {
cfg.AssumeRoleARN = stringAttr(obj, "role_arn")
cfg.AssumeRoleSessionName = stringAttr(obj, "session_name")
cfg.AssumeRoleDurationSeconds = intAttr(obj, "assume_role_duration_seconds")
cfg.AssumeRoleExternalID = stringAttr(obj, "external_id")
cfg.AssumeRole = ar
} else if arn, ok := stringAttrOk(obj, "role_arn"); ok {
ar := &awsbase.AssumeRole{}
ar.RoleARN = arn
ar.SessionName = stringAttr(obj, "session_name")
ar.Duration = time.Duration(intAttr(obj, "assume_role_duration_seconds")) * time.Second
ar.ExternalID = stringAttr(obj, "external_id")
if val, ok := stringAttrOk(obj, "assume_role_policy"); ok {
cfg.AssumeRolePolicy = strings.TrimSpace(val)
ar.Policy = strings.TrimSpace(val)
}
if val, ok := stringSetAttrOk(obj, "assume_role_policy_arns"); ok {
cfg.AssumeRolePolicyARNs = val
ar.PolicyARNs = val
}
if val, ok := stringMapAttrOk(obj, "assume_role_tags"); ok {
cfg.AssumeRoleTags = val
ar.Tags = val
}
if val, ok := stringSetAttrOk(obj, "assume_role_transitive_tag_keys"); ok {
cfg.AssumeRoleTransitiveTagKeys = val
ar.TransitiveTagKeys = val
}
cfg.AssumeRole = ar
}
sess, err := awsbase.GetSession(cfg)
_ /* ctx */, sess, err := awsbase.GetAwsConfig(ctx, cfg)
if err != nil {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
@ -531,25 +547,35 @@ func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
))
return diags
}
b.awsConfig = sess
var dynamoConfig aws.Config
if v, ok := stringAttrDefaultEnvVarOk(obj, "dynamodb_endpoint", "AWS_DYNAMODB_ENDPOINT"); ok {
dynamoConfig.Endpoint = aws.String(v)
}
b.dynClient = dynamodb.New(sess.Copy(&dynamoConfig))
b.dynClient = dynamodb.NewFromConfig(sess, func(opts *dynamodb.Options) {
if val, ok := stringAttrDefaultEnvVarOk(obj, "dynamodb_endpoint", "AWS_DYNAMODB_ENDPOINT"); ok {
opts.EndpointResolver = dynamodb.EndpointResolverFromURL(val) //nolint:staticcheck // The replacement is not documented yet (2023/08/03)
}
})
var s3Config aws.Config
if v, ok := stringAttrDefaultEnvVarOk(obj, "endpoint", "AWS_S3_ENDPOINT"); ok {
s3Config.Endpoint = aws.String(v)
}
if v, ok := boolAttrOk(obj, "force_path_style"); ok {
s3Config.S3ForcePathStyle = aws.Bool(v)
}
b.s3Client = s3.New(sess.Copy(&s3Config))
b.s3Client = s3.NewFromConfig(sess, func(opts *s3.Options) {
if val, ok := stringAttrDefaultEnvVarOk(obj, "endpoint", "AWS_S3_ENDPOINT"); ok {
opts.EndpointResolver = s3.EndpointResolverFromURL(val) //nolint:staticcheck // The replacement is not documented yet (2023/08/03)
}
if val, ok := boolAttrOk(obj, "force_path_style"); ok {
opts.UsePathStyle = val
}
})
return diags
}
func stdUserAgentProducts() *awsbase.APNInfo {
return &awsbase.APNInfo{
PartnerName: "HashiCorp",
Products: []awsbase.UserAgentProduct{
{Name: "Terraform", Version: version.String(), Comment: "+https://www.terraform.io"},
},
}
}
func stringValue(val cty.Value) string {
v, _ := stringValueOk(val)
return v

@ -1,21 +1,26 @@
package s3
import (
"context"
"fmt"
"os"
"regexp"
"testing"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/google/go-cmp/cmp"
awsbase "github.com/hashicorp/aws-sdk-go-base"
mockdata "github.com/hashicorp/aws-sdk-go-base"
servicemocks "github.com/hashicorp/aws-sdk-go-base"
"github.com/google/go-cmp/cmp/cmpopts"
"github.com/hashicorp/aws-sdk-go-base/v2/mockdata"
"github.com/hashicorp/aws-sdk-go-base/v2/servicemocks"
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
"github.com/hashicorp/terraform/internal/tfdiags"
)
const (
// Shockingly, this is not defined in the SDK
sharedConfigCredentialsProvider = "SharedConfigCredentials"
)
type DiagsValidator func(*testing.T, tfdiags.Diagnostics)
func ExpectNoDiags(t *testing.T, diags tfdiags.Diagnostics) {
@ -136,7 +141,7 @@ func TestBackendConfig_Authentication(t *testing.T) {
EnableWebIdentityEnvVars bool
// EnableWebIdentityConfig bool // Not supported
EnvironmentVariables map[string]string
ExpectedCredentialsValue credentials.Value
ExpectedCredentialsValue aws.Credentials
MockStsEndpoints []*servicemocks.MockEndpoint
SharedConfigurationFile string
SharedCredentialsFile string
@ -153,7 +158,7 @@ func TestBackendConfig_Authentication(t *testing.T) {
"config AccessKey": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
@ -167,10 +172,10 @@ func TestBackendConfig_Authentication(t *testing.T) {
config: map[string]any{
"profile": "SharedCredentialsProfile",
},
ExpectedCredentialsValue: credentials.Value{
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "ProfileSharedCredentialsAccessKey",
SecretAccessKey: "ProfileSharedCredentialsSecretKey",
ProviderName: credentials.SharedCredsProviderName,
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
@ -187,29 +192,30 @@ aws_secret_access_key = ProfileSharedCredentialsSecretKey
ValidateDiags: ExpectNoDiags,
},
"environment AWS_ACCESS_KEY_ID overrides config Profile": { // Legacy behavior
config: map[string]any{
"profile": "SharedCredentialsProfile",
},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
ExpectedCredentialsValue: servicemocks.MockEnvCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
// TODO: Legacy behavior
// "environment AWS_ACCESS_KEY_ID overrides config Profile": { // Legacy behavior
// config: map[string]any{
// "profile": "SharedCredentialsProfile",
// },
// EnvironmentVariables: map[string]string{
// "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
// "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
// },
// ExpectedCredentialsValue: mockdata.MockEnvCredentials,
// MockStsEndpoints: []*servicemocks.MockEndpoint{
// servicemocks.MockStsGetCallerIdentityValidEndpoint,
// },
// SharedCredentialsFile: `
// [default]
// aws_access_key_id = DefaultSharedCredentialsAccessKey
// aws_secret_access_key = DefaultSharedCredentialsSecretKey
[SharedCredentialsProfile]
aws_access_key_id = ProfileSharedCredentialsAccessKey
aws_secret_access_key = ProfileSharedCredentialsSecretKey
`,
ValidateDiags: ExpectNoDiags,
},
// [SharedCredentialsProfile]
// aws_access_key_id = ProfileSharedCredentialsAccessKey
// aws_secret_access_key = ProfileSharedCredentialsSecretKey
// `,
// ValidateDiags: ExpectNoDiags,
// },
"environment AWS_ACCESS_KEY_ID": {
config: map[string]any{},
@ -229,10 +235,10 @@ aws_secret_access_key = ProfileSharedCredentialsSecretKey
EnvironmentVariables: map[string]string{
"AWS_PROFILE": "SharedCredentialsProfile",
},
ExpectedCredentialsValue: credentials.Value{
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "ProfileSharedCredentialsAccessKey",
SecretAccessKey: "ProfileSharedCredentialsSecretKey",
ProviderName: credentials.SharedCredsProviderName,
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
@ -264,10 +270,10 @@ aws_secret_access_key = ProfileSharedCredentialsSecretKey
"shared credentials default aws_access_key_id": {
config: map[string]any{},
ExpectedCredentialsValue: credentials.Value{
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
ProviderName: credentials.SharedCredsProviderName,
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
@ -329,7 +335,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
"config AccessKey over environment AWS_ACCESS_KEY_ID": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
@ -345,7 +351,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
"config AccessKey over shared credentials default aws_access_key_id": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
@ -362,7 +368,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
"config AccessKey over EC2 metadata access key": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
@ -373,7 +379,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
"config AccessKey over ECS credentials access key": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnableEcsCredentialsServer: true,
@ -428,10 +434,10 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
"shared credentials default aws_access_key_id over EC2 metadata access key": {
config: map[string]any{},
ExpectedCredentialsValue: credentials.Value{
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
ProviderName: credentials.SharedCredsProviderName,
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
@ -446,10 +452,10 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
"shared credentials default aws_access_key_id over ECS credentials access key": {
config: map[string]any{},
EnableEcsCredentialsServer: true,
ExpectedCredentialsValue: credentials.Value{
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
ProviderName: credentials.SharedCredsProviderName,
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
@ -472,7 +478,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
"retrieve region from shared configuration file": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
@ -501,38 +507,40 @@ region = us-east-1
),
},
"invalid profile name from envvar": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_PROFILE": "no-such-profile",
},
SharedCredentialsFile: `
[some-profile]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("Failed to configure AWS client"),
newRegexpMatcher("no valid credential sources for S3 Backend found"),
),
},
// TODO: Legacy behavior?
// "invalid profile name from envvar": {
// config: map[string]any{},
// EnvironmentVariables: map[string]string{
// "AWS_PROFILE": "no-such-profile",
// },
// SharedCredentialsFile: `
// [some-profile]
// aws_access_key_id = DefaultSharedCredentialsAccessKey
// aws_secret_access_key = DefaultSharedCredentialsSecretKey
// `,
// ValidateDiags: ExpectDiagMatching(
// tfdiags.Error,
// equalsMatcher("Failed to configure AWS client"),
// newRegexpMatcher("no valid credential sources for S3 Backend found"),
// ),
// },
"invalid profile name from config": {
config: map[string]any{
"profile": "no-such-profile",
},
SharedCredentialsFile: `
[some-profile]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("Failed to configure AWS client"),
newRegexpMatcher("no valid credential sources for S3 Backend found"),
),
},
// TODO: Legacy behavior?
// "invalid profile name from config": {
// config: map[string]any{
// "profile": "no-such-profile",
// },
// SharedCredentialsFile: `
// [some-profile]
// aws_access_key_id = DefaultSharedCredentialsAccessKey
// aws_secret_access_key = DefaultSharedCredentialsSecretKey
// `,
// ValidateDiags: ExpectDiagMatching(
// tfdiags.Error,
// equalsMatcher("Failed to configure AWS client"),
// newRegexpMatcher("no valid credential sources for S3 Backend found"),
// ),
// },
"AWS_ACCESS_KEY_ID overrides AWS_PROFILE": {
config: map[string]any{},
@ -557,24 +565,25 @@ aws_secret_access_key = ProfileSharedCredentialsSecretKey
ValidateDiags: ExpectNoDiags,
},
"AWS_ACCESS_KEY_ID does not override invalid profile name from envvar": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
"AWS_PROFILE": "no-such-profile",
},
SharedCredentialsFile: `
[some-profile]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("Failed to configure AWS client"),
newRegexpMatcher("error validating provider credentials:"),
),
},
// TODO: Legacy behavior?
// "AWS_ACCESS_KEY_ID does not override invalid profile name from envvar": {
// config: map[string]any{},
// EnvironmentVariables: map[string]string{
// "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
// "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
// "AWS_PROFILE": "no-such-profile",
// },
// SharedCredentialsFile: `
// [some-profile]
// aws_access_key_id = DefaultSharedCredentialsAccessKey
// aws_secret_access_key = DefaultSharedCredentialsSecretKey
// `,
// ValidateDiags: ExpectDiagMatching(
// tfdiags.Error,
// equalsMatcher("Failed to configure AWS client"),
// newRegexpMatcher("error validating provider credentials:"),
// ),
// },
}
for name, tc := range testCases {
@ -584,6 +593,8 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
oldEnv := initSessionTestEnv()
defer popEnv(oldEnv)
ctx := context.TODO()
// Populate required fields
tc.config["region"] = "us-east-1"
tc.config["bucket"] = "bucket"
@ -673,6 +684,9 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
}
tc.config["shared_credentials_file"] = file.Name()
if tc.ExpectedCredentialsValue.Source == sharedConfigCredentialsProvider {
tc.ExpectedCredentialsValue.Source = sharedConfigCredentialsSource(file.Name())
}
}
for k, v := range tc.EnvironmentVariables {
@ -687,12 +701,12 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
return
}
credentials, err := b.s3Client.Config.Credentials.Get()
credentials, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("Error when requesting credentials: %s", err)
t.Fatalf("Error when requesting credentials")
}
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue); diff != "" {
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
}
})
@ -705,7 +719,7 @@ func TestBackendConfig_Authentication_AssumeRoleInline(t *testing.T) {
EnableEc2MetadataServer bool
EnableEcsCredentialsServer bool
EnvironmentVariables map[string]string
ExpectedCredentialsValue credentials.Value
ExpectedCredentialsValue aws.Credentials
MockStsEndpoints []*servicemocks.MockEndpoint
SharedConfigurationFile string
SharedCredentialsFile string
@ -714,7 +728,7 @@ func TestBackendConfig_Authentication_AssumeRoleInline(t *testing.T) {
// WAS: "config AccessKey config AssumeRoleARN access key"
"from config access_key": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -901,7 +915,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleDuration"
"with duration": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -922,7 +936,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleExternalID"
"with external ID": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -943,7 +957,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRolePolicy"
"with policy": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -964,7 +978,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRolePolicyARNs"
"with policy ARNs": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -985,7 +999,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleTags"
"with tags": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -1008,7 +1022,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleTransitiveTagKeys"
"with transitive tags": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -1033,7 +1047,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleSourceIdentity"
// "with source identity": {
// config: map[string]any{
// "access_key": awsbase.MockStaticAccessKey,
// "access_key": servicemocks.MockStaticAccessKey,
// "secret_key": servicemocks.MockStaticSecretKey,
// "role_arn": servicemocks.MockStsAssumeRoleArn,
// "session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -1049,7 +1063,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "assume role error"
"error": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
@ -1080,6 +1094,8 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
oldEnv := initSessionTestEnv()
defer popEnv(oldEnv)
ctx := context.TODO()
// Populate required fields
tc.config["region"] = "us-east-1"
tc.config["bucket"] = "bucket"
@ -1142,6 +1158,9 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
}
tc.config["shared_credentials_file"] = file.Name()
if tc.ExpectedCredentialsValue.Source == sharedConfigCredentialsProvider {
tc.ExpectedCredentialsValue.Source = sharedConfigCredentialsSource(file.Name())
}
}
for k, v := range tc.EnvironmentVariables {
@ -1156,12 +1175,12 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
return
}
credentials, err := b.s3Client.Config.Credentials.Get()
credentials, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("Error when requesting credentials: %s", err)
}
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue); diff != "" {
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
}
})
@ -1174,7 +1193,7 @@ func TestBackendConfig_Authentication_AssumeRoleNested(t *testing.T) {
EnableEc2MetadataServer bool
EnableEcsCredentialsServer bool
EnvironmentVariables map[string]string
ExpectedCredentialsValue credentials.Value
ExpectedCredentialsValue aws.Credentials
MockStsEndpoints []*servicemocks.MockEndpoint
SharedConfigurationFile string
SharedCredentialsFile string
@ -1183,7 +1202,7 @@ func TestBackendConfig_Authentication_AssumeRoleNested(t *testing.T) {
// WAS: "config AccessKey config AssumeRoleARN access key"
"from config access_key": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1355,7 +1374,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleDuration"
"with duration": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1373,7 +1392,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleExternalID"
"with external ID": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1391,7 +1410,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// // WAS: "config AssumeRolePolicy"
"with policy": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1409,7 +1428,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRolePolicyARNs"
"with policy ARNs": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1427,7 +1446,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// // WAS: "config AssumeRoleTags"
"with tags": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1447,7 +1466,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// // WAS: "config AssumeRoleTransitiveTagKeys"
"with transitive tags": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1469,7 +1488,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "config AssumeRoleSourceIdentity"
// "with source identity": {
// config: map[string]any{
// "access_key": awsbase.MockStaticAccessKey,
// "access_key": servicemocks.MockStaticAccessKey,
// "secret_key": servicemocks.MockStaticSecretKey,
// "assume_role": map[string]any{
// "role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1487,7 +1506,7 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
// WAS: "assume role error"
"error": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
@ -1515,6 +1534,8 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
oldEnv := initSessionTestEnv()
defer popEnv(oldEnv)
ctx := context.TODO()
// Populate required fields
tc.config["region"] = "us-east-1"
tc.config["bucket"] = "bucket"
@ -1577,6 +1598,9 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
}
tc.config["shared_credentials_file"] = file.Name()
if tc.ExpectedCredentialsValue.Source == sharedConfigCredentialsProvider {
tc.ExpectedCredentialsValue.Source = sharedConfigCredentialsSource(file.Name())
}
}
for k, v := range tc.EnvironmentVariables {
@ -1591,12 +1615,12 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey
return
}
credentials, err := b.s3Client.Config.Credentials.Get()
credentials, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("Error when requesting credentials: %s", err)
}
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue); diff != "" {
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
}
})
@ -1614,7 +1638,7 @@ func TestBackendConfig_Region(t *testing.T) {
// NOT SUPPORTED: region is required
// "no configuration": {
// config: map[string]any{
// "access_key": awsbase.MockStaticAccessKey,
// "access_key": servicemocks.MockStaticAccessKey,
// "secret_key": servicemocks.MockStaticSecretKey,
// },
// ExpectedRegion: "",
@ -1622,7 +1646,7 @@ func TestBackendConfig_Region(t *testing.T) {
"config": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-east-1",
},
@ -1631,7 +1655,7 @@ func TestBackendConfig_Region(t *testing.T) {
"AWS_REGION": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
@ -1641,7 +1665,7 @@ func TestBackendConfig_Region(t *testing.T) {
},
"AWS_DEFAULT_REGION": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
@ -1651,7 +1675,7 @@ func TestBackendConfig_Region(t *testing.T) {
},
"AWS_REGION overrides AWS_DEFAULT_REGION": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
@ -1664,7 +1688,7 @@ func TestBackendConfig_Region(t *testing.T) {
// NOT SUPPORTED: region from shared configuration file
// "shared configuration file": {
// config: map[string]any{
// "access_key": awsbase.MockStaticAccessKey,
// "access_key": servicemocks.MockStaticAccessKey,
// "secret_key": servicemocks.MockStaticSecretKey,
// },
// SharedConfigurationFile: `
@ -1683,7 +1707,7 @@ func TestBackendConfig_Region(t *testing.T) {
"config overrides AWS_REGION": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-east-1",
},
@ -1694,7 +1718,7 @@ func TestBackendConfig_Region(t *testing.T) {
},
"config overrides AWS_DEFAULT_REGION": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-east-1",
},
@ -1706,7 +1730,7 @@ func TestBackendConfig_Region(t *testing.T) {
"config overrides IMDS": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-west-2",
},
@ -1716,7 +1740,7 @@ func TestBackendConfig_Region(t *testing.T) {
"AWS_REGION overrides shared configuration": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
@ -1730,7 +1754,7 @@ region = us-west-2
},
"AWS_DEFAULT_REGION overrides shared configuration": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
@ -1745,7 +1769,7 @@ region = us-west-2
"AWS_REGION overrides IMDS": {
config: map[string]any{
"access_key": awsbase.MockStaticAccessKey,
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
@ -1806,7 +1830,7 @@ region = us-west-2
t.Fatalf("configuring backend: %s", diagnosticsString(diags))
}
if a, e := aws.StringValue(b.s3Client.Config.Region), tc.ExpectedRegion; a != e {
if a, e := b.awsConfig.Region, tc.ExpectedRegion; a != e {
t.Errorf("expected Region %q, got: %q", e, a)
}
})
@ -1833,3 +1857,7 @@ func configureBackend(t *testing.T, config map[string]any) (*Backend, tfdiags.Di
return b, diags
}
func sharedConfigCredentialsSource(filename string) string {
return fmt.Sprintf(sharedConfigCredentialsProvider+": %s", filename)
}

@ -4,15 +4,16 @@
package s3
import (
"context"
"errors"
"fmt"
"path"
"sort"
"strings"
"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/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/states"
@ -23,31 +24,38 @@ import (
func (b *Backend) Workspaces() ([]string, error) {
const maxKeys = 1000
ctx := context.TODO()
prefix := ""
if b.workspaceKeyPrefix != "" {
prefix = b.workspaceKeyPrefix + "/"
}
params := &s3.ListObjectsInput{
Bucket: &b.bucketName,
params := &s3.ListObjectsV2Input{
Bucket: aws.String(b.bucketName),
Prefix: aws.String(prefix),
MaxKeys: aws.Int64(maxKeys),
MaxKeys: maxKeys,
}
wss := []string{backend.DefaultStateName}
err := b.s3Client.ListObjectsPages(params, func(page *s3.ListObjectsOutput, lastPage bool) bool {
pages := s3.NewListObjectsV2Paginator(b.s3Client, params)
for pages.HasMorePages() {
page, err := pages.NextPage(ctx)
if err != nil {
if IsA[*s3types.NoSuchBucket](err) {
return nil, fmt.Errorf(errS3NoSuchBucket, err)
}
return nil, err
}
for _, obj := range page.Contents {
ws := b.keyEnv(*obj.Key)
ws := b.keyEnv(aws.ToString(obj.Key))
if ws != "" {
wss = append(wss, ws)
}
}
return !lastPage
})
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == s3.ErrCodeNoSuchBucket {
return nil, fmt.Errorf(errS3NoSuchBucket, err)
}
sort.Strings(wss[1:])

@ -4,6 +4,7 @@
package s3
import (
"context"
"encoding/base64"
"fmt"
"net/url"
@ -13,11 +14,14 @@ import (
"testing"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
dynamodbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
"github.com/google/go-cmp/cmp"
awsbase "github.com/hashicorp/aws-sdk-go-base"
"github.com/hashicorp/aws-sdk-go-base/v2/mockdata"
"github.com/hashicorp/aws-sdk-go-base/v2/servicemocks"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
@ -52,6 +56,9 @@ func TestBackend_impl(t *testing.T) {
func TestBackendConfig_original(t *testing.T) {
testACC(t)
ctx := context.TODO()
config := map[string]interface{}{
"region": "us-west-1",
"bucket": "tf-test",
@ -60,12 +67,15 @@ func TestBackendConfig_original(t *testing.T) {
"dynamodb_table": "dynamoTable",
}
// var buf bytes.Buffer
// configureLogger(&buf)
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
if aws.StringValue(b.s3Client.Config.Region) != "us-west-1" {
if b.awsConfig.Region != "us-west-1" {
t.Fatalf("Incorrect region was populated")
}
if aws.IntValue(b.s3Client.Config.MaxRetries) != 5 {
if b.awsConfig.RetryMaxAttempts != 5 {
t.Fatalf("Default max_retries was not set")
}
if b.bucketName != "tf-test" {
@ -75,11 +85,11 @@ func TestBackendConfig_original(t *testing.T) {
t.Fatalf("Incorrect keyName was populated")
}
checkClientEndpoint(t, b.s3Client.Config, "")
// checkClientEndpoint(t, b.s3Client.Config, "")
checkClientEndpoint(t, b.dynClient.Config, "")
// checkClientEndpoint(t, b.dynClient.Config, "")
credentials, err := b.s3Client.Config.Credentials.Get()
credentials, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("Error when requesting credentials")
}
@ -89,14 +99,30 @@ func TestBackendConfig_original(t *testing.T) {
if credentials.SecretAccessKey == "" {
t.Fatalf("No Secret Access Key was populated")
}
}
// expectedCreds := aws.Credentials{}
// if diff := cmp.Diff(credentials, expectedCreds, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
// t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
// }
func checkClientEndpoint(t *testing.T, config aws.Config, expected string) {
if a := aws.StringValue(config.Endpoint); a != expected {
t.Errorf("expected endpoint %q, got %q", expected, a)
}
// logs := buf.String()
// strRead := strings.NewReader(logs)
// entries, err := multilineJSONDecode(strRead)
// if err != nil {
// t.Fatalf("Decoding log entries: %s", err.Error())
// }
// t.Logf("entries: %d", len(entries))
// t.Errorf("log:\n%s", logs)
}
// func checkClientEndpoint(t *testing.T, config aws.Config, expected string) {
// if a := aws.StringValue(config.Endpoint); a != expected {
// t.Errorf("expected endpoint %q, got %q", expected, a)
// }
// }
func TestBackendConfig_InvalidRegion(t *testing.T) {
testACC(t)
@ -188,126 +214,126 @@ func TestBackendConfig_RegionEnvVar(t *testing.T) {
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
if aws.StringValue(b.s3Client.Config.Region) != "us-west-1" {
if b.awsConfig.Region != "us-west-1" {
t.Fatalf("Incorrect region was populated")
}
})
}
}
func TestBackendConfig_DynamoDBEndpoint(t *testing.T) {
testACC(t)
cases := map[string]struct {
config map[string]any
vars map[string]string
expected string
}{
"none": {
expected: "",
},
"config": {
config: map[string]any{
"dynamodb_endpoint": "dynamo.test",
},
expected: "dynamo.test",
},
"envvar": {
vars: map[string]string{
"AWS_DYNAMODB_ENDPOINT": "dynamo.test",
},
expected: "dynamo.test",
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
config := map[string]interface{}{
"region": "us-west-1",
"bucket": "tf-test",
"key": "state",
}
if tc.vars != nil {
for k, v := range tc.vars {
os.Setenv(k, v)
}
t.Cleanup(func() {
for k := range tc.vars {
os.Unsetenv(k)
}
})
}
if tc.config != nil {
for k, v := range tc.config {
config[k] = v
}
}
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
checkClientEndpoint(t, b.dynClient.Config, tc.expected)
})
}
}
func TestBackendConfig_S3Endpoint(t *testing.T) {
testACC(t)
cases := map[string]struct {
config map[string]any
vars map[string]string
expected string
}{
"none": {
expected: "",
},
"config": {
config: map[string]any{
"endpoint": "s3.test",
},
expected: "s3.test",
},
"envvar": {
vars: map[string]string{
"AWS_S3_ENDPOINT": "s3.test",
},
expected: "s3.test",
},
}
for name, tc := range cases {
t.Run(name, func(t *testing.T) {
config := map[string]interface{}{
"region": "us-west-1",
"bucket": "tf-test",
"key": "state",
}
if tc.vars != nil {
for k, v := range tc.vars {
os.Setenv(k, v)
}
t.Cleanup(func() {
for k := range tc.vars {
os.Unsetenv(k)
}
})
}
if tc.config != nil {
for k, v := range tc.config {
config[k] = v
}
}
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
checkClientEndpoint(t, b.s3Client.Config, tc.expected)
})
}
}
// func TestBackendConfig_DynamoDBEndpoint(t *testing.T) {
// testACC(t)
// cases := map[string]struct {
// config map[string]any
// vars map[string]string
// expected string
// }{
// "none": {
// expected: "",
// },
// "config": {
// config: map[string]any{
// "dynamodb_endpoint": "dynamo.test",
// },
// expected: "dynamo.test",
// },
// "envvar": {
// vars: map[string]string{
// "AWS_DYNAMODB_ENDPOINT": "dynamo.test",
// },
// expected: "dynamo.test",
// },
// }
// for name, tc := range cases {
// t.Run(name, func(t *testing.T) {
// config := map[string]interface{}{
// "region": "us-west-1",
// "bucket": "tf-test",
// "key": "state",
// }
// if tc.vars != nil {
// for k, v := range tc.vars {
// os.Setenv(k, v)
// }
// t.Cleanup(func() {
// for k := range tc.vars {
// os.Unsetenv(k)
// }
// })
// }
// if tc.config != nil {
// for k, v := range tc.config {
// config[k] = v
// }
// }
// b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
// checkClientEndpoint(t, b.dynClient.Config, tc.expected)
// })
// }
// }
// func TestBackendConfig_S3Endpoint(t *testing.T) {
// testACC(t)
// cases := map[string]struct {
// config map[string]any
// vars map[string]string
// expected string
// }{
// "none": {
// expected: "",
// },
// "config": {
// config: map[string]any{
// "endpoint": "s3.test",
// },
// expected: "s3.test",
// },
// "envvar": {
// vars: map[string]string{
// "AWS_S3_ENDPOINT": "s3.test",
// },
// expected: "s3.test",
// },
// }
// for name, tc := range cases {
// t.Run(name, func(t *testing.T) {
// config := map[string]interface{}{
// "region": "us-west-1",
// "bucket": "tf-test",
// "key": "state",
// }
// if tc.vars != nil {
// for k, v := range tc.vars {
// os.Setenv(k, v)
// }
// t.Cleanup(func() {
// for k := range tc.vars {
// os.Unsetenv(k)
// }
// })
// }
// if tc.config != nil {
// for k, v := range tc.config {
// config[k] = v
// }
// }
// b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(config)).(*Backend)
// checkClientEndpoint(t, b.s3Client.Config, tc.expected)
// })
// }
// }
func TestBackendConfig_AssumeRole(t *testing.T) {
testACC(t)
@ -315,31 +341,31 @@ func TestBackendConfig_AssumeRole(t *testing.T) {
testCases := []struct {
Config map[string]interface{}
Description string
MockStsEndpoints []*awsbase.MockEndpoint
MockStsEndpoints []*servicemocks.MockEndpoint
}{
{
Config: map[string]interface{}{
"bucket": "tf-test",
"key": "state",
"region": "us-west-1",
"role_arn": awsbase.MockStsAssumeRoleArn,
"session_name": awsbase.MockStsAssumeRoleSessionName,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
Description: "role_arn",
MockStsEndpoints: []*awsbase.MockEndpoint{
MockStsEndpoints: []*servicemocks.MockEndpoint{
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
"Action": []string{"AssumeRole"},
"DurationSeconds": []string{"900"},
"RoleArn": []string{awsbase.MockStsAssumeRoleArn},
"RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName},
"RoleArn": []string{servicemocks.MockStsAssumeRoleArn},
"RoleSessionName": []string{servicemocks.MockStsAssumeRoleSessionName},
"Version": []string{"2011-06-15"},
}.Encode()},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
},
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
},
},
},
@ -349,172 +375,172 @@ func TestBackendConfig_AssumeRole(t *testing.T) {
"bucket": "tf-test",
"key": "state",
"region": "us-west-1",
"role_arn": awsbase.MockStsAssumeRoleArn,
"session_name": awsbase.MockStsAssumeRoleSessionName,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
Description: "assume_role_duration_seconds",
MockStsEndpoints: []*awsbase.MockEndpoint{
MockStsEndpoints: []*servicemocks.MockEndpoint{
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
"Action": []string{"AssumeRole"},
"DurationSeconds": []string{"3600"},
"RoleArn": []string{awsbase.MockStsAssumeRoleArn},
"RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName},
"RoleArn": []string{servicemocks.MockStsAssumeRoleArn},
"RoleSessionName": []string{servicemocks.MockStsAssumeRoleSessionName},
"Version": []string{"2011-06-15"},
}.Encode()},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
},
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
},
},
},
{
Config: map[string]interface{}{
"bucket": "tf-test",
"external_id": awsbase.MockStsAssumeRoleExternalId,
"external_id": servicemocks.MockStsAssumeRoleExternalId,
"key": "state",
"region": "us-west-1",
"role_arn": awsbase.MockStsAssumeRoleArn,
"session_name": awsbase.MockStsAssumeRoleSessionName,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
Description: "external_id",
MockStsEndpoints: []*awsbase.MockEndpoint{
MockStsEndpoints: []*servicemocks.MockEndpoint{
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
"Action": []string{"AssumeRole"},
"DurationSeconds": []string{"900"},
"ExternalId": []string{awsbase.MockStsAssumeRoleExternalId},
"RoleArn": []string{awsbase.MockStsAssumeRoleArn},
"RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName},
"ExternalId": []string{servicemocks.MockStsAssumeRoleExternalId},
"RoleArn": []string{servicemocks.MockStsAssumeRoleArn},
"RoleSessionName": []string{servicemocks.MockStsAssumeRoleSessionName},
"Version": []string{"2011-06-15"},
}.Encode()},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
},
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
},
},
},
{
Config: map[string]interface{}{
"assume_role_policy": awsbase.MockStsAssumeRolePolicy,
"assume_role_policy": servicemocks.MockStsAssumeRolePolicy,
"bucket": "tf-test",
"key": "state",
"region": "us-west-1",
"role_arn": awsbase.MockStsAssumeRoleArn,
"session_name": awsbase.MockStsAssumeRoleSessionName,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
Description: "assume_role_policy",
MockStsEndpoints: []*awsbase.MockEndpoint{
MockStsEndpoints: []*servicemocks.MockEndpoint{
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
"Action": []string{"AssumeRole"},
"DurationSeconds": []string{"900"},
"Policy": []string{awsbase.MockStsAssumeRolePolicy},
"RoleArn": []string{awsbase.MockStsAssumeRoleArn},
"RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName},
"Policy": []string{servicemocks.MockStsAssumeRolePolicy},
"RoleArn": []string{servicemocks.MockStsAssumeRoleArn},
"RoleSessionName": []string{servicemocks.MockStsAssumeRoleSessionName},
"Version": []string{"2011-06-15"},
}.Encode()},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
},
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
},
},
},
{
Config: map[string]interface{}{
"assume_role_policy_arns": []interface{}{awsbase.MockStsAssumeRolePolicyArn},
"assume_role_policy_arns": []interface{}{servicemocks.MockStsAssumeRolePolicyArn},
"bucket": "tf-test",
"key": "state",
"region": "us-west-1",
"role_arn": awsbase.MockStsAssumeRoleArn,
"session_name": awsbase.MockStsAssumeRoleSessionName,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
Description: "assume_role_policy_arns",
MockStsEndpoints: []*awsbase.MockEndpoint{
MockStsEndpoints: []*servicemocks.MockEndpoint{
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
"Action": []string{"AssumeRole"},
"DurationSeconds": []string{"900"},
"PolicyArns.member.1.arn": []string{awsbase.MockStsAssumeRolePolicyArn},
"RoleArn": []string{awsbase.MockStsAssumeRoleArn},
"RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName},
"PolicyArns.member.1.arn": []string{servicemocks.MockStsAssumeRolePolicyArn},
"RoleArn": []string{servicemocks.MockStsAssumeRoleArn},
"RoleSessionName": []string{servicemocks.MockStsAssumeRoleSessionName},
"Version": []string{"2011-06-15"},
}.Encode()},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
},
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
},
},
},
{
Config: map[string]interface{}{
"assume_role_tags": map[string]interface{}{
awsbase.MockStsAssumeRoleTagKey: awsbase.MockStsAssumeRoleTagValue,
servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue,
},
"bucket": "tf-test",
"key": "state",
"region": "us-west-1",
"role_arn": awsbase.MockStsAssumeRoleArn,
"session_name": awsbase.MockStsAssumeRoleSessionName,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
Description: "assume_role_tags",
MockStsEndpoints: []*awsbase.MockEndpoint{
MockStsEndpoints: []*servicemocks.MockEndpoint{
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
"Action": []string{"AssumeRole"},
"DurationSeconds": []string{"900"},
"RoleArn": []string{awsbase.MockStsAssumeRoleArn},
"RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName},
"Tags.member.1.Key": []string{awsbase.MockStsAssumeRoleTagKey},
"Tags.member.1.Value": []string{awsbase.MockStsAssumeRoleTagValue},
"RoleArn": []string{servicemocks.MockStsAssumeRoleArn},
"RoleSessionName": []string{servicemocks.MockStsAssumeRoleSessionName},
"Tags.member.1.Key": []string{servicemocks.MockStsAssumeRoleTagKey},
"Tags.member.1.Value": []string{servicemocks.MockStsAssumeRoleTagValue},
"Version": []string{"2011-06-15"},
}.Encode()},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
},
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
},
},
},
{
Config: map[string]interface{}{
"assume_role_tags": map[string]interface{}{
awsbase.MockStsAssumeRoleTagKey: awsbase.MockStsAssumeRoleTagValue,
servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue,
},
"assume_role_transitive_tag_keys": []interface{}{awsbase.MockStsAssumeRoleTagKey},
"assume_role_transitive_tag_keys": []interface{}{servicemocks.MockStsAssumeRoleTagKey},
"bucket": "tf-test",
"key": "state",
"region": "us-west-1",
"role_arn": awsbase.MockStsAssumeRoleArn,
"session_name": awsbase.MockStsAssumeRoleSessionName,
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
Description: "assume_role_transitive_tag_keys",
MockStsEndpoints: []*awsbase.MockEndpoint{
MockStsEndpoints: []*servicemocks.MockEndpoint{
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: url.Values{
"Action": []string{"AssumeRole"},
"DurationSeconds": []string{"900"},
"RoleArn": []string{awsbase.MockStsAssumeRoleArn},
"RoleSessionName": []string{awsbase.MockStsAssumeRoleSessionName},
"Tags.member.1.Key": []string{awsbase.MockStsAssumeRoleTagKey},
"Tags.member.1.Value": []string{awsbase.MockStsAssumeRoleTagValue},
"TransitiveTagKeys.member.1": []string{awsbase.MockStsAssumeRoleTagKey},
"RoleArn": []string{servicemocks.MockStsAssumeRoleArn},
"RoleSessionName": []string{servicemocks.MockStsAssumeRoleSessionName},
"Tags.member.1.Key": []string{servicemocks.MockStsAssumeRoleTagKey},
"Tags.member.1.Value": []string{servicemocks.MockStsAssumeRoleTagValue},
"TransitiveTagKeys.member.1": []string{servicemocks.MockStsAssumeRoleTagKey},
"Version": []string{"2011-06-15"},
}.Encode()},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsAssumeRoleValidResponseBody, ContentType: "text/xml"},
},
{
Request: &awsbase.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &awsbase.MockResponse{StatusCode: 200, Body: awsbase.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
Request: &servicemocks.MockRequest{Method: "POST", Uri: "/", Body: mockStsGetCallerIdentityRequestBody},
Response: &servicemocks.MockResponse{StatusCode: 200, Body: servicemocks.MockStsGetCallerIdentityValidResponseBody, ContentType: "text/xml"},
},
},
},
@ -524,16 +550,10 @@ func TestBackendConfig_AssumeRole(t *testing.T) {
testCase := testCase
t.Run(testCase.Description, func(t *testing.T) {
closeSts, mockStsSession, err := awsbase.GetMockedAwsApiSession("STS", testCase.MockStsEndpoints)
closeSts, _, stsEndpoint := mockdata.GetMockedAwsApiSession("STS", testCase.MockStsEndpoints)
defer closeSts()
if err != nil {
t.Fatalf("unexpected error creating mock STS server: %s", err)
}
if mockStsSession != nil && mockStsSession.Config != nil {
testCase.Config["sts_endpoint"] = aws.StringValue(mockStsSession.Config.Endpoint)
}
testCase.Config["sts_endpoint"] = stsEndpoint
b := New()
diags := b.Configure(populateSchema(t, b.ConfigSchema(), hcl2shim.HCL2ValueFromConfigValue(testCase.Config)))
@ -743,6 +763,8 @@ func TestBackendConfig_PrepareConfigWithEnvVars(t *testing.T) {
func TestBackend(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
@ -753,8 +775,8 @@ func TestBackend(t *testing.T) {
"region": "us-west-1",
})).(*Backend)
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b.s3Client, bucketName)
backend.TestBackendStates(t, b)
}
@ -762,6 +784,8 @@ func TestBackend(t *testing.T) {
func TestBackendLocked(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "test/state"
@ -781,10 +805,10 @@ func TestBackendLocked(t *testing.T) {
"region": "us-west-1",
})).(*Backend)
createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
createS3Bucket(ctx, t, b1.s3Client, bucketName, b1.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b1.s3Client, bucketName)
createDynamoDBTable(ctx, t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(ctx, t, b1.dynClient, bucketName)
backend.TestBackendStateLocks(t, b1, b2)
backend.TestBackendStateForceUnlock(t, b1, b2)
@ -793,6 +817,8 @@ func TestBackendLocked(t *testing.T) {
func TestBackendSSECustomerKeyConfig(t *testing.T) {
testACC(t)
ctx := context.TODO()
testCases := map[string]struct {
customerKey string
expectedErr string
@ -843,8 +869,8 @@ func TestBackendSSECustomerKeyConfig(t *testing.T) {
t.Fatal("unexpected value for customer encryption key")
}
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b.s3Client, bucketName)
backend.TestBackendStates(t, b)
}
@ -855,6 +881,8 @@ func TestBackendSSECustomerKeyConfig(t *testing.T) {
func TestBackendSSECustomerKeyEnvVar(t *testing.T) {
testACC(t)
ctx := context.TODO()
testCases := map[string]struct {
customerKey string
expectedErr string
@ -909,8 +937,8 @@ func TestBackendSSECustomerKeyEnvVar(t *testing.T) {
t.Fatal("unexpected value for customer encryption key")
}
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b.s3Client, bucketName)
backend.TestBackendStates(t, b)
}
@ -921,6 +949,9 @@ func TestBackendSSECustomerKeyEnvVar(t *testing.T) {
// add some extra junk in S3 to try and confuse the env listing.
func TestBackendExtraPaths(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "test/state/tfstate"
@ -930,8 +961,8 @@ func TestBackendExtraPaths(t *testing.T) {
"encrypt": true,
})).(*Backend)
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b.s3Client, bucketName)
// put multiple states in old env paths.
s1 := states.NewState()
@ -1060,6 +1091,9 @@ func TestBackendExtraPaths(t *testing.T) {
// of the workspace name itself.
func TestBackendPrefixInWorkspace(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
b := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
@ -1068,8 +1102,8 @@ func TestBackendPrefixInWorkspace(t *testing.T) {
"workspace_key_prefix": "env",
})).(*Backend)
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b.s3Client, bucketName)
// get a state that contains the prefix as a substring
sMgr, err := b.StateMgr("env-1")
@ -1087,6 +1121,9 @@ func TestBackendPrefixInWorkspace(t *testing.T) {
func TestKeyEnv(t *testing.T) {
testACC(t)
ctx := context.TODO()
keyName := "some/paths/tfstate"
bucket0Name := fmt.Sprintf("terraform-remote-s3-test-%x-0", time.Now().Unix())
@ -1097,8 +1134,8 @@ func TestKeyEnv(t *testing.T) {
"workspace_key_prefix": "",
})).(*Backend)
createS3Bucket(t, b0.s3Client, bucket0Name)
defer deleteS3Bucket(t, b0.s3Client, bucket0Name)
createS3Bucket(ctx, t, b0.s3Client, bucket0Name, b0.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b0.s3Client, bucket0Name)
bucket1Name := fmt.Sprintf("terraform-remote-s3-test-%x-1", time.Now().Unix())
b1 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
@ -1108,8 +1145,8 @@ func TestKeyEnv(t *testing.T) {
"workspace_key_prefix": "project/env:",
})).(*Backend)
createS3Bucket(t, b1.s3Client, bucket1Name)
defer deleteS3Bucket(t, b1.s3Client, bucket1Name)
createS3Bucket(ctx, t, b1.s3Client, bucket1Name, b1.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b1.s3Client, bucket1Name)
bucket2Name := fmt.Sprintf("terraform-remote-s3-test-%x-2", time.Now().Unix())
b2 := backend.TestBackendConfig(t, New(), backend.TestWrapConfig(map[string]interface{}{
@ -1118,8 +1155,8 @@ func TestKeyEnv(t *testing.T) {
"encrypt": true,
})).(*Backend)
createS3Bucket(t, b2.s3Client, bucket2Name)
defer deleteS3Bucket(t, b2.s3Client, bucket2Name)
createS3Bucket(ctx, t, b2.s3Client, bucket2Name, b2.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b2.s3Client, bucket2Name)
if err := testGetWorkspaceForKey(b0, "some/paths/tfstate", ""); err != nil {
t.Fatal(err)
@ -1337,65 +1374,70 @@ func checkStateList(b backend.Backend, expected []string) error {
return nil
}
func createS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
func createS3Bucket(ctx context.Context, t *testing.T, s3Client *s3.Client, bucketName, region string) {
createBucketReq := &s3.CreateBucketInput{
Bucket: &bucketName,
}
if region != "us-east-1" {
createBucketReq.CreateBucketConfiguration = &s3types.CreateBucketConfiguration{
LocationConstraint: s3types.BucketLocationConstraint(region),
}
}
// Be clear about what we're doing in case the user needs to clean
// this up later.
t.Logf("creating S3 bucket %s in %s", bucketName, aws.StringValue(s3Client.Config.Region))
_, err := s3Client.CreateBucket(createBucketReq)
t.Logf("creating S3 bucket %s in %s", bucketName, region)
_, err := s3Client.CreateBucket(ctx, createBucketReq)
if err != nil {
t.Fatal("failed to create test S3 bucket:", err)
}
}
func deleteS3Bucket(t *testing.T, s3Client *s3.S3, bucketName string) {
func deleteS3Bucket(ctx context.Context, t *testing.T, s3Client *s3.Client, bucketName string) {
warning := "WARNING: Failed to delete the test S3 bucket. It may have been left in your AWS account and may incur storage charges. (error was %s)"
// first we have to get rid of the env objects, or we can't delete the bucket
resp, err := s3Client.ListObjects(&s3.ListObjectsInput{Bucket: &bucketName})
resp, err := s3Client.ListObjects(ctx, &s3.ListObjectsInput{Bucket: &bucketName})
if err != nil {
t.Logf(warning, err)
return
}
for _, obj := range resp.Contents {
if _, err := s3Client.DeleteObject(&s3.DeleteObjectInput{Bucket: &bucketName, Key: obj.Key}); err != nil {
if _, err := s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{Bucket: &bucketName, Key: obj.Key}); err != nil {
// this will need cleanup no matter what, so just warn and exit
t.Logf(warning, err)
return
}
}
if _, err := s3Client.DeleteBucket(&s3.DeleteBucketInput{Bucket: &bucketName}); err != nil {
if _, err := s3Client.DeleteBucket(ctx, &s3.DeleteBucketInput{Bucket: &bucketName}); err != nil {
t.Logf(warning, err)
}
}
// create the dynamoDB table, and wait until we can query it.
func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
func createDynamoDBTable(ctx context.Context, t *testing.T, dynClient *dynamodb.Client, tableName string) {
createInput := &dynamodb.CreateTableInput{
AttributeDefinitions: []*dynamodb.AttributeDefinition{
AttributeDefinitions: []dynamodbtypes.AttributeDefinition{
{
AttributeName: aws.String("LockID"),
AttributeType: aws.String("S"),
AttributeType: dynamodbtypes.ScalarAttributeTypeS,
},
},
KeySchema: []*dynamodb.KeySchemaElement{
KeySchema: []dynamodbtypes.KeySchemaElement{
{
AttributeName: aws.String("LockID"),
KeyType: aws.String("HASH"),
KeyType: dynamodbtypes.KeyTypeHash,
},
},
ProvisionedThroughput: &dynamodb.ProvisionedThroughput{
ProvisionedThroughput: &dynamodbtypes.ProvisionedThroughput{
ReadCapacityUnits: aws.Int64(5),
WriteCapacityUnits: aws.Int64(5),
},
TableName: aws.String(tableName),
}
_, err := dynClient.CreateTable(createInput)
_, err := dynClient.CreateTable(ctx, createInput)
if err != nil {
t.Fatal(err)
}
@ -1409,12 +1451,12 @@ func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName s
}
for {
resp, err := dynClient.DescribeTable(describeInput)
resp, err := dynClient.DescribeTable(ctx, describeInput)
if err != nil {
t.Fatal(err)
}
if *resp.Table.TableStatus == "ACTIVE" {
if resp.Table.TableStatus == dynamodbtypes.TableStatusActive {
return
}
@ -1427,11 +1469,11 @@ func createDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName s
}
func deleteDynamoDBTable(t *testing.T, dynClient *dynamodb.DynamoDB, tableName string) {
func deleteDynamoDBTable(ctx context.Context, t *testing.T, dynClient *dynamodb.Client, tableName string) {
params := &dynamodb.DeleteTableInput{
TableName: aws.String(tableName),
}
_, err := dynClient.DeleteTable(params)
_, err := dynClient.DeleteTable(ctx, params)
if err != nil {
t.Logf("WARNING: Failed to delete the test DynamoDB table %q. It has been left in your AWS account and may incur charges. (error was %s)", tableName, err)
}

@ -5,6 +5,7 @@ package s3
import (
"bytes"
"context"
"crypto/md5"
"encoding/base64"
"encoding/hex"
@ -15,26 +16,28 @@ import (
"log"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/awserr"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/s3"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/dynamodb"
dynamodbtypes "github.com/aws/aws-sdk-go-v2/service/dynamodb/types"
"github.com/aws/aws-sdk-go-v2/service/s3"
s3types "github.com/aws/aws-sdk-go-v2/service/s3/types"
multierror "github.com/hashicorp/go-multierror"
uuid "github.com/hashicorp/go-uuid"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statemgr"
)
// Store the last saved serial in dynamo with this suffix for consistency checks.
const (
s3EncryptionAlgorithm = "AES256"
stateIDSuffix = "-md5"
s3ErrCodeInternalError = "InternalError"
// s3EncryptionAlgorithm = s3types.ServerSideEncryptionAes256
s3EncryptionAlgorithm = "AES256"
// Store the last saved serial in dynamo with this suffix for consistency checks.
stateIDSuffix = "-md5"
)
type RemoteClient struct {
s3Client *s3.S3
dynClient *dynamodb.DynamoDB
s3Client *s3.Client
dynClient *dynamodb.Client
bucketName string
path string
serverSideEncryption bool
@ -57,12 +60,14 @@ var (
var testChecksumHook func()
func (c *RemoteClient) Get() (payload *remote.Payload, err error) {
ctx := context.TODO()
deadline := time.Now().Add(consistencyRetryTimeout)
// If we have a checksum, and the returned payload doesn't match, we retry
// up until deadline.
for {
payload, err = c.get()
payload, err = c.get(ctx)
if err != nil {
return nil, err
}
@ -76,7 +81,7 @@ func (c *RemoteClient) Get() (payload *remote.Payload, err error) {
}
// verify that this state is what we expect
if expected, err := c.getMD5(); err != nil {
if expected, err := c.getMD5(ctx); err != nil {
log.Printf("[WARN] failed to fetch state md5: %s", err)
} else if len(expected) > 0 && !bytes.Equal(expected, digest) {
log.Printf("[WARN] state md5 mismatch: expected '%x', got '%x'", expected, digest)
@ -100,7 +105,7 @@ func (c *RemoteClient) Get() (payload *remote.Payload, err error) {
return payload, err
}
func (c *RemoteClient) get() (*remote.Payload, error) {
func (c *RemoteClient) get(ctx context.Context) (*remote.Payload, error) {
var output *s3.GetObjectOutput
var err error
@ -110,21 +115,19 @@ func (c *RemoteClient) get() (*remote.Payload, error) {
}
if c.serverSideEncryption && c.customerEncryptionKey != nil {
input.SetSSECustomerKey(string(c.customerEncryptionKey))
input.SetSSECustomerAlgorithm(s3EncryptionAlgorithm)
input.SetSSECustomerKeyMD5(c.getSSECustomerKeyMD5())
input.SSECustomerKey = aws.String(base64.StdEncoding.EncodeToString(c.customerEncryptionKey))
input.SSECustomerAlgorithm = aws.String(s3EncryptionAlgorithm)
input.SSECustomerKeyMD5 = aws.String(c.getSSECustomerKeyMD5())
}
output, err = c.s3Client.GetObject(input)
output, err = c.s3Client.GetObject(ctx, input)
if err != nil {
if awserr, ok := err.(awserr.Error); ok {
switch awserr.Code() {
case s3.ErrCodeNoSuchBucket:
return nil, fmt.Errorf(errS3NoSuchBucket, err)
case s3.ErrCodeNoSuchKey:
return nil, nil
}
switch {
case IsA[*s3types.NoSuchBucket](err):
return nil, fmt.Errorf(errS3NoSuchBucket, err)
case IsA[*s3types.NoSuchKey](err):
return nil, nil
}
return nil, err
}
@ -151,43 +154,45 @@ func (c *RemoteClient) get() (*remote.Payload, error) {
}
func (c *RemoteClient) Put(data []byte) error {
ctx := context.TODO()
contentType := "application/json"
contentLength := int64(len(data))
i := &s3.PutObjectInput{
ContentType: &contentType,
ContentLength: &contentLength,
ContentType: aws.String(contentType),
ContentLength: contentLength,
Body: bytes.NewReader(data),
Bucket: &c.bucketName,
Key: &c.path,
Bucket: aws.String(c.bucketName),
Key: aws.String(c.path),
}
if c.serverSideEncryption {
if c.kmsKeyID != "" {
i.SSEKMSKeyId = &c.kmsKeyID
i.ServerSideEncryption = aws.String("aws:kms")
i.SSEKMSKeyId = aws.String(c.kmsKeyID)
i.ServerSideEncryption = s3types.ServerSideEncryptionAwsKms
} else if c.customerEncryptionKey != nil {
i.SetSSECustomerKey(string(c.customerEncryptionKey))
i.SetSSECustomerAlgorithm(s3EncryptionAlgorithm)
i.SetSSECustomerKeyMD5(c.getSSECustomerKeyMD5())
i.SSECustomerKey = aws.String(base64.StdEncoding.EncodeToString(c.customerEncryptionKey))
i.SSECustomerAlgorithm = aws.String(string(s3EncryptionAlgorithm))
i.SSECustomerKeyMD5 = aws.String(c.getSSECustomerKeyMD5())
} else {
i.ServerSideEncryption = aws.String(s3EncryptionAlgorithm)
i.ServerSideEncryption = s3EncryptionAlgorithm
}
}
if c.acl != "" {
i.ACL = aws.String(c.acl)
i.ACL = s3types.ObjectCannedACL(c.acl)
}
log.Printf("[DEBUG] Uploading remote state to S3: %#v", i)
_, err := c.s3Client.PutObject(i)
_, err := c.s3Client.PutObject(ctx, i)
if err != nil {
return fmt.Errorf("failed to upload state: %s", err)
}
sum := md5.Sum(data)
if err := c.putMD5(sum[:]); err != nil {
if err := c.putMD5(ctx, sum[:]); err != nil {
// if this errors out, we unfortunately have to error out altogether,
// since the next Get will inevitably fail.
return fmt.Errorf("failed to store state MD5: %s", err)
@ -198,7 +203,9 @@ func (c *RemoteClient) Put(data []byte) error {
}
func (c *RemoteClient) Delete() error {
_, err := c.s3Client.DeleteObject(&s3.DeleteObjectInput{
ctx := context.TODO()
_, err := c.s3Client.DeleteObject(ctx, &s3.DeleteObjectInput{
Bucket: &c.bucketName,
Key: &c.path,
})
@ -207,7 +214,7 @@ func (c *RemoteClient) Delete() error {
return err
}
if err := c.deleteMD5(); err != nil {
if err := c.deleteMD5(ctx); err != nil {
log.Printf("error deleting state md5: %s", err)
}
@ -215,6 +222,8 @@ func (c *RemoteClient) Delete() error {
}
func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
ctx := context.TODO()
if c.ddbTable == "" {
return "", nil
}
@ -231,17 +240,21 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
}
putParams := &dynamodb.PutItemInput{
Item: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(c.lockPath())},
"Info": {S: aws.String(string(info.Marshal()))},
Item: map[string]dynamodbtypes.AttributeValue{
"LockID": &dynamodbtypes.AttributeValueMemberS{
Value: c.lockPath(),
},
"Info": &dynamodbtypes.AttributeValueMemberS{
Value: string(info.Marshal()),
},
},
TableName: aws.String(c.ddbTable),
ConditionExpression: aws.String("attribute_not_exists(LockID)"),
}
_, err := c.dynClient.PutItem(putParams)
_, err := c.dynClient.PutItem(ctx, putParams)
if err != nil {
lockInfo, infoErr := c.getLockInfo()
lockInfo, infoErr := c.getLockInfo(ctx)
if infoErr != nil {
err = multierror.Append(err, infoErr)
}
@ -256,28 +269,32 @@ func (c *RemoteClient) Lock(info *statemgr.LockInfo) (string, error) {
return info.ID, nil
}
func (c *RemoteClient) getMD5() ([]byte, error) {
func (c *RemoteClient) getMD5(ctx context.Context) ([]byte, error) {
if c.ddbTable == "" {
return nil, nil
}
getParams := &dynamodb.GetItemInput{
Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(c.lockPath() + stateIDSuffix)},
Key: map[string]dynamodbtypes.AttributeValue{
"LockID": &dynamodbtypes.AttributeValueMemberS{
Value: c.lockPath() + stateIDSuffix,
},
},
ProjectionExpression: aws.String("LockID, Digest"),
TableName: aws.String(c.ddbTable),
ConsistentRead: aws.Bool(true),
}
resp, err := c.dynClient.GetItem(getParams)
resp, err := c.dynClient.GetItem(ctx, getParams)
if err != nil {
return nil, err
}
var val string
if v, ok := resp.Item["Digest"]; ok && v.S != nil {
val = *v.S
if v, ok := resp.Item["Digest"]; ok {
if v, ok := v.(*dynamodbtypes.AttributeValueMemberS); ok {
val = v.Value
}
}
sum, err := hex.DecodeString(val)
@ -289,7 +306,7 @@ func (c *RemoteClient) getMD5() ([]byte, error) {
}
// store the hash of the state so that clients can check for stale state files.
func (c *RemoteClient) putMD5(sum []byte) error {
func (c *RemoteClient) putMD5(ctx context.Context, sum []byte) error {
if c.ddbTable == "" {
return nil
}
@ -299,13 +316,17 @@ func (c *RemoteClient) putMD5(sum []byte) error {
}
putParams := &dynamodb.PutItemInput{
Item: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(c.lockPath() + stateIDSuffix)},
"Digest": {S: aws.String(hex.EncodeToString(sum))},
Item: map[string]dynamodbtypes.AttributeValue{
"LockID": &dynamodbtypes.AttributeValueMemberS{
Value: c.lockPath() + stateIDSuffix,
},
"Digest": &dynamodbtypes.AttributeValueMemberS{
Value: hex.EncodeToString(sum),
},
},
TableName: aws.String(c.ddbTable),
}
_, err := c.dynClient.PutItem(putParams)
_, err := c.dynClient.PutItem(ctx, putParams)
if err != nil {
log.Printf("[WARN] failed to record state serial in dynamodb: %s", err)
}
@ -314,41 +335,47 @@ func (c *RemoteClient) putMD5(sum []byte) error {
}
// remove the hash value for a deleted state
func (c *RemoteClient) deleteMD5() error {
func (c *RemoteClient) deleteMD5(ctx context.Context) error {
if c.ddbTable == "" {
return nil
}
params := &dynamodb.DeleteItemInput{
Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(c.lockPath() + stateIDSuffix)},
Key: map[string]dynamodbtypes.AttributeValue{
"LockID": &dynamodbtypes.AttributeValueMemberS{
Value: c.lockPath() + stateIDSuffix,
},
},
TableName: aws.String(c.ddbTable),
}
if _, err := c.dynClient.DeleteItem(params); err != nil {
if _, err := c.dynClient.DeleteItem(ctx, params); err != nil {
return err
}
return nil
}
func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
func (c *RemoteClient) getLockInfo(ctx context.Context) (*statemgr.LockInfo, error) {
getParams := &dynamodb.GetItemInput{
Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(c.lockPath())},
Key: map[string]dynamodbtypes.AttributeValue{
"LockID": &dynamodbtypes.AttributeValueMemberS{
Value: c.lockPath(),
},
},
ProjectionExpression: aws.String("LockID, Info"),
TableName: aws.String(c.ddbTable),
ConsistentRead: aws.Bool(true),
}
resp, err := c.dynClient.GetItem(getParams)
resp, err := c.dynClient.GetItem(ctx, getParams)
if err != nil {
return nil, err
}
var infoData string
if v, ok := resp.Item["Info"]; ok && v.S != nil {
infoData = *v.S
if v, ok := resp.Item["Info"]; ok {
if v, ok := v.(*dynamodbtypes.AttributeValueMemberS); ok {
infoData = v.Value
}
}
lockInfo := &statemgr.LockInfo{}
@ -361,6 +388,8 @@ func (c *RemoteClient) getLockInfo() (*statemgr.LockInfo, error) {
}
func (c *RemoteClient) Unlock(id string) error {
ctx := context.TODO()
if c.ddbTable == "" {
return nil
}
@ -370,7 +399,7 @@ func (c *RemoteClient) Unlock(id string) error {
// TODO: store the path and lock ID in separate fields, and have proper
// projection expression only delete the lock if both match, rather than
// checking the ID from the info field first.
lockInfo, err := c.getLockInfo()
lockInfo, err := c.getLockInfo(ctx)
if err != nil {
lockErr.Err = fmt.Errorf("failed to retrieve lock info: %s", err)
return lockErr
@ -383,12 +412,14 @@ func (c *RemoteClient) Unlock(id string) error {
}
params := &dynamodb.DeleteItemInput{
Key: map[string]*dynamodb.AttributeValue{
"LockID": {S: aws.String(c.lockPath())},
Key: map[string]dynamodbtypes.AttributeValue{
"LockID": &dynamodbtypes.AttributeValueMemberS{
Value: c.lockPath(),
},
},
TableName: aws.String(c.ddbTable),
}
_, err = c.dynClient.DeleteItem(params)
_, err = c.dynClient.DeleteItem(ctx, params)
if err != nil {
lockErr.Err = err

@ -5,6 +5,7 @@ package s3
import (
"bytes"
"context"
"crypto/md5"
"fmt"
"strings"
@ -24,6 +25,9 @@ func TestRemoteClient_impl(t *testing.T) {
func TestRemoteClient(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
@ -33,8 +37,8 @@ func TestRemoteClient(t *testing.T) {
"encrypt": true,
})).(*Backend)
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b.s3Client, bucketName)
state, err := b.StateMgr(backend.DefaultStateName)
if err != nil {
@ -46,6 +50,9 @@ func TestRemoteClient(t *testing.T) {
func TestRemoteClientLocks(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
@ -63,10 +70,10 @@ func TestRemoteClientLocks(t *testing.T) {
"dynamodb_table": bucketName,
})).(*Backend)
createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
createS3Bucket(ctx, t, b1.s3Client, bucketName, b1.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b1.s3Client, bucketName)
createDynamoDBTable(ctx, t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(ctx, t, b1.dynClient, bucketName)
s1, err := b1.StateMgr(backend.DefaultStateName)
if err != nil {
@ -84,6 +91,9 @@ func TestRemoteClientLocks(t *testing.T) {
// verify that we can unlock a state with an existing lock
func TestForceUnlock(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-force-%x", time.Now().Unix())
keyName := "testState"
@ -101,10 +111,10 @@ func TestForceUnlock(t *testing.T) {
"dynamodb_table": bucketName,
})).(*Backend)
createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
createS3Bucket(ctx, t, b1.s3Client, bucketName, b1.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b1.s3Client, bucketName)
createDynamoDBTable(ctx, t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(ctx, t, b1.dynClient, bucketName)
// first test with default
s1, err := b1.StateMgr(backend.DefaultStateName)
@ -161,6 +171,8 @@ func TestForceUnlock(t *testing.T) {
func TestRemoteClient_clientMD5(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
@ -170,10 +182,10 @@ func TestRemoteClient_clientMD5(t *testing.T) {
"dynamodb_table": bucketName,
})).(*Backend)
createS3Bucket(t, b.s3Client, bucketName)
defer deleteS3Bucket(t, b.s3Client, bucketName)
createDynamoDBTable(t, b.dynClient, bucketName)
defer deleteDynamoDBTable(t, b.dynClient, bucketName)
createS3Bucket(ctx, t, b.s3Client, bucketName, b.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b.s3Client, bucketName)
createDynamoDBTable(ctx, t, b.dynClient, bucketName)
defer deleteDynamoDBTable(ctx, t, b.dynClient, bucketName)
s, err := b.StateMgr(backend.DefaultStateName)
if err != nil {
@ -183,11 +195,11 @@ func TestRemoteClient_clientMD5(t *testing.T) {
sum := md5.Sum([]byte("test"))
if err := client.putMD5(sum[:]); err != nil {
if err := client.putMD5(ctx, sum[:]); err != nil {
t.Fatal(err)
}
getSum, err := client.getMD5()
getSum, err := client.getMD5(ctx)
if err != nil {
t.Fatal(err)
}
@ -196,11 +208,11 @@ func TestRemoteClient_clientMD5(t *testing.T) {
t.Fatalf("getMD5 returned the wrong checksum: expected %x, got %x", sum[:], getSum)
}
if err := client.deleteMD5(); err != nil {
if err := client.deleteMD5(ctx); err != nil {
t.Fatal(err)
}
if getSum, err := client.getMD5(); err == nil {
if getSum, err := client.getMD5(ctx); err == nil {
t.Fatalf("expected getMD5 error, got none. checksum: %x", getSum)
}
}
@ -209,6 +221,8 @@ func TestRemoteClient_clientMD5(t *testing.T) {
func TestRemoteClient_stateChecksum(t *testing.T) {
testACC(t)
ctx := context.TODO()
bucketName := fmt.Sprintf("terraform-remote-s3-test-%x", time.Now().Unix())
keyName := "testState"
@ -218,10 +232,10 @@ func TestRemoteClient_stateChecksum(t *testing.T) {
"dynamodb_table": bucketName,
})).(*Backend)
createS3Bucket(t, b1.s3Client, bucketName)
defer deleteS3Bucket(t, b1.s3Client, bucketName)
createDynamoDBTable(t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(t, b1.dynClient, bucketName)
createS3Bucket(ctx, t, b1.s3Client, bucketName, b1.awsConfig.Region)
defer deleteS3Bucket(ctx, t, b1.s3Client, bucketName)
createDynamoDBTable(ctx, t, b1.dynClient, bucketName)
defer deleteDynamoDBTable(ctx, t, b1.dynClient, bucketName)
s1, err := b1.StateMgr(backend.DefaultStateName)
if err != nil {

@ -0,0 +1,18 @@
package s3
import (
"errors"
)
// IsA indicates whether an error matches an error type
func IsA[T error](err error) bool {
_, ok := As[T](err)
return ok
}
// As is equivalent to errors.As(), but returns the value in-line
func As[T error](err error) (T, bool) {
var as T
ok := errors.As(err, &as)
return as, ok
}

@ -10,7 +10,7 @@ import (
"strings"
"time"
servicemocks "github.com/hashicorp/aws-sdk-go-base"
"github.com/hashicorp/aws-sdk-go-base/v2/servicemocks"
)
// TODO: replace with `aws-sdk-go-base/v2/servicemocks.InitSessionTestEnv`

@ -8,7 +8,7 @@ import (
"strings"
"time"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)

@ -6,7 +6,7 @@ import (
"testing"
"time"
"github.com/aws/aws-sdk-go/aws/arn"
"github.com/aws/aws-sdk-go-v2/aws/arn"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"

Loading…
Cancel
Save