diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da05827..e6401d9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: 1.17 + go-version: 1.18 - name: Import GPG key id: import_gpg diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3c823dc..2b36375 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,12 +13,12 @@ jobs: build: name: Build runs-on: ubuntu-latest - timeout-minutes: 5 + timeout-minutes: 15 steps: - name: Set up Go uses: actions/setup-go@v2.1.3 with: - go-version: "1.15" + go-version: 1.18 id: go - name: Check out code into the Go module directory @@ -47,6 +47,7 @@ jobs: - "0.14.5" - "0.15.1" - "1.0.0" + - "1.1.0" steps: - name: Set up Go uses: actions/setup-go@v2.1.3 diff --git a/go.mod b/go.mod index af8a56f..7aa5226 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,44 @@ require ( github.com/google/uuid v1.3.0 github.com/hashicorp/aws-sdk-go-base v1.1.0 github.com/hashicorp/terraform-plugin-docs v0.4.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 github.com/jen20/awspolicyequivalence v1.1.0 github.com/mitchellh/go-testing-interface v1.14.1 gopkg.in/yaml.v2 v2.4.0 ) +require ( + github.com/apparentlymart/go-cidr v1.1.0 // indirect + github.com/aws/aws-sdk-go-v2 v1.16.7 // indirect + github.com/aws/aws-sdk-go-v2/config v1.15.4 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.12.0 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 // indirect + github.com/aws/aws-sdk-go-v2/service/fis v1.12.8 // indirect + github.com/aws/aws-sdk-go-v2/service/iam v1.18.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 // indirect + github.com/aws/aws-sdk-go-v2/service/kendra v1.31.0 // indirect + github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.0.0 // indirect + github.com/aws/aws-sdk-go-v2/service/route53domains v1.12.8 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 // indirect + github.com/aws/aws-sdk-go-v2/service/transcribe v1.21.0 // indirect + github.com/aws/smithy-go v1.12.0 // indirect + github.com/google/go-cmp v0.5.8 // indirect + github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.16 // indirect + github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2 v2.0.0-beta.17 // indirect + github.com/hashicorp/hc-install v0.4.0 // indirect + github.com/hashicorp/terraform-plugin-framework v0.10.0 // indirect + github.com/hashicorp/terraform-plugin-log v0.7.0 // indirect + github.com/hashicorp/terraform-plugin-mux v0.7.0 // indirect + github.com/hashicorp/terraform-registry-address v0.0.0-20220623143253-7d51757b572c // indirect + github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect + github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect + github.com/vmihailenco/tagparser v0.1.1 // indirect +) + require ( cloud.google.com/go v0.65.0 // indirect cloud.google.com/go/storage v1.10.0 // indirect @@ -26,26 +58,26 @@ require ( github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.9.0 // indirect + github.com/fatih/color v1.13.0 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/golang/protobuf v1.4.2 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/googleapis/gax-go/v2 v2.0.5 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 // indirect github.com/hashicorp/go-getter v1.5.3 // indirect - github.com/hashicorp/go-hclog v0.15.0 // indirect + github.com/hashicorp/go-hclog v1.2.1 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.1 // indirect + github.com/hashicorp/go-plugin v1.4.4 // indirect github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-uuid v1.0.1 // indirect - github.com/hashicorp/go-version v1.3.0 // indirect - github.com/hashicorp/hcl/v2 v2.8.2 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hcl/v2 v2.13.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.14.0 // indirect - github.com/hashicorp/terraform-json v0.12.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.4.0 // indirect + github.com/hashicorp/terraform-exec v0.17.2 // indirect + github.com/hashicorp/terraform-json v0.14.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.12.0 // indirect github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect github.com/huandu/xstrings v1.3.2 // indirect github.com/imdario/mergo v0.3.12 // indirect @@ -53,33 +85,33 @@ require ( github.com/jstemmer/go-junit-report v0.9.1 // indirect github.com/keybase/go-crypto v0.0.0-20200123153347-de78d2cb44f4 github.com/klauspost/compress v1.11.2 // indirect - github.com/mattn/go-colorable v0.1.8 // indirect - github.com/mattn/go-isatty v0.0.12 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/cli v1.1.2 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.1.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect github.com/posener/complete v1.2.1 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/ulikunitz/xz v0.5.8 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect - github.com/zclconf/go-cty v1.8.4 // indirect + github.com/zclconf/go-cty v1.10.0 // indirect go.opencensus.io v0.22.4 // indirect - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect + golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 // indirect golang.org/x/lint v0.0.0-20200302205851-738671d3881b // indirect golang.org/x/mod v0.3.0 // indirect golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect - golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e // indirect + golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect golang.org/x/text v0.3.7 // indirect golang.org/x/tools v0.0.0-20201028111035-eafbe7b904eb // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/api v0.34.0 // indirect google.golang.org/appengine v1.6.6 // indirect google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d // indirect - google.golang.org/grpc v1.32.0 // indirect - google.golang.org/protobuf v1.25.0 // indirect + google.golang.org/grpc v1.48.0 // indirect + google.golang.org/protobuf v1.28.0 // indirect ) diff --git a/go.sum b/go.sum index 099c241..b783f28 100644 --- a/go.sum +++ b/go.sum @@ -55,7 +55,10 @@ github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= github.com/andybalholm/crlf v0.0.0-20171020200849-670099aa064f/go.mod h1:k8feO4+kXDxro6ErPXBRTJ/ro2mf0SsFG8s7doP9kJE= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= +github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= @@ -74,17 +77,65 @@ github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.44.45 h1:E2i73X4QdVS0XrfX/aVPt/M0Su2IuJ7AFvAMtF0id1Q= github.com/aws/aws-sdk-go v1.44.45/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= +github.com/aws/aws-sdk-go-v2 v1.16.3 h1:0W1TSJ7O6OzwuEvIXAtJGvOeQ0SGAhcpxPN2/NK5EhM= +github.com/aws/aws-sdk-go-v2 v1.16.3/go.mod h1:ytwTPBG6fXTZLxxeeCCWj2/EMYp/xDUgX+OET6TLNNU= +github.com/aws/aws-sdk-go-v2 v1.16.7 h1:zfBwXus3u14OszRxGcqCDS4MfMCv10e8SMJ2r8Xm0Ns= +github.com/aws/aws-sdk-go-v2 v1.16.7/go.mod h1:6CpKuLXg2w7If3ABZCl/qZ6rEgwtjZTn4eAf4RcEyuw= +github.com/aws/aws-sdk-go-v2/config v1.15.4 h1:P4mesY1hYUxru4f9SU0XxNKXmzfxsD0FtMIPRBjkH7Q= +github.com/aws/aws-sdk-go-v2/config v1.15.4/go.mod h1:ZijHHh0xd/A+ZY53az0qzC5tT46kt4JVCePf2NX9Lk4= +github.com/aws/aws-sdk-go-v2/credentials v1.12.0 h1:4R/NqlcRFSkR0wxOhgHi+agGpbEr5qMCjn7VqUIJY+E= +github.com/aws/aws-sdk-go-v2/credentials v1.12.0/go.mod h1:9YWk7VW+eyKsoIL6/CljkTrNVWBSK9pkqOPUuijid4A= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4 h1:FP8gquGeGHHdfY6G5llaMQDF+HAf20VKc8opRwmjf04= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.4/go.mod h1:u/s5/Z+ohUQOPXl00m2yJVyioWDECsbpXTQlaqSlufc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10 h1:uFWgo6mGJI1n17nbcvSc6fxVuR3xLNqvXt12JCnEcT8= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.10/go.mod h1:F+EZtuIwjlv35kRJPyBGcsA4f7bnSoz15zOQ2lJq1Z4= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14 h1:2C0pYHcUBmdzPj+EKNC4qj97oK6yjrUhc1KoSodglvk= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.14/go.mod h1:kdjrMwHwrC3+FsKhNcCMJ7tUVj/8uSD5CZXeQ4wV6fM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4 h1:cnsvEKSoHN4oAN7spMMr0zhEW2MHnhAVpmqQg8E6UcM= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.4/go.mod h1:8glyUqVIM4AmeenIsPo0oVh3+NUwnsQml2OFupfQW+0= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8 h1:2J+jdlBJWEmTyAwC82Ym68xCykIvnSnIN18b8xHGlcc= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.8/go.mod h1:ZIV8GYoC6WLBW5KGs+o4rsc65/ozd+eQ0L31XF5VDwk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11 h1:6cZRymlLEIlDTEB0+5+An6Zj1CKt6rSE69tOmFeu1nk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.3.11/go.mod h1:0MR+sS1b/yxsfAPvAESrw8NfwUoxMinDyw6EYR9BS2U= +github.com/aws/aws-sdk-go-v2/service/fis v1.12.8 h1:LLVOXMdXspsXhTjGERgXvSYUhhC2zAPLAUBdn9eM7dA= +github.com/aws/aws-sdk-go-v2/service/fis v1.12.8/go.mod h1:Xhb/njxqFw3jJ+N57DtKhlTDX+UOjGSntS47YyC1Xes= +github.com/aws/aws-sdk-go-v2/service/iam v1.18.4 h1:E41guA79mjEbwJdh0zXz1d8+Zt4zxRr+b1ipiVbKXzs= +github.com/aws/aws-sdk-go-v2/service/iam v1.18.4/go.mod h1:FpNvAfCZyIQ3qeNJUOw4CShKvdizHblXqAvSk0qmyL4= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4 h1:b16QW0XWl0jWjLABFc1A+uh145Oqv+xDcObNk0iQgUk= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.4/go.mod h1:uKkN7qmSIsNJVyMtxNQoCEYMvFEXbOg9fwCJPdfp2u8= +github.com/aws/aws-sdk-go-v2/service/kendra v1.31.0 h1:xEyl64MguV9mhPhtIqxNX5+mp/w3Wo1bXTEYYip6jFU= +github.com/aws/aws-sdk-go-v2/service/kendra v1.31.0/go.mod h1:iK7BBBCIHFkJ/fOK/fGT2l5AGmpizYTjHYh2RyDnXBE= +github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.0.0 h1:SaRx3zt7kpjUvJuRMyTN+y6CX1jTKqDBZMIcgNGv2Xs= +github.com/aws/aws-sdk-go-v2/service/rolesanywhere v1.0.0/go.mod h1:narEYLWaUCxp7FZkVgviBykKKaovHAf/Qd06z4xTLk0= +github.com/aws/aws-sdk-go-v2/service/route53domains v1.12.8 h1:a73eN9Y9wpdpbAwWABujx/nhlji0kxUSjmWeJWUnj4o= +github.com/aws/aws-sdk-go-v2/service/route53domains v1.12.8/go.mod h1:ilsoUWPEuWAUYKyTT3OWfQ3QZHDKhAtZE7xmSiTsdBs= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 h1:Uw5wBybFQ1UeA9ts0Y07gbv0ncZnIAyw858tDW0NP2o= +github.com/aws/aws-sdk-go-v2/service/sso v1.11.4/go.mod h1:cPDwJwsP4Kff9mldCXAmddjJL6JGQqtA3Mzer2zyr88= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 h1:+xtV90n3abQmgzk1pS++FdxZTrPEDgQng6e4/56WR2A= +github.com/aws/aws-sdk-go-v2/service/sts v1.16.4/go.mod h1:lfSYenAXtavyX2A1LsViglqlG9eEFYxNryTZS5rn3QE= +github.com/aws/aws-sdk-go-v2/service/transcribe v1.21.0 h1:X586tXSlRXPE/G1rnAJan82lR5meZd/frQAANFWbQbs= +github.com/aws/aws-sdk-go-v2/service/transcribe v1.21.0/go.mod h1:ojx+dJuqQFFX0EApdKHRPLvQvhmGKj+b0evbL7Uu19U= +github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= +github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= +github.com/aws/smithy-go v1.12.0 h1:gXpeZel/jPoWQ7OEmLIgCUnhkFftqNfwWUwAHSlp1v0= +github.com/aws/smithy-go v1.12.0/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= +github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -94,11 +145,16 @@ github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= 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/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= @@ -144,6 +200,10 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= @@ -154,8 +214,12 @@ github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs= @@ -174,14 +238,20 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/aws-sdk-go-base v1.1.0 h1:27urM3JAp6v+Oj/Ea5ULZwuFPK9cO1RUdEpV+rNdSAc= github.com/hashicorp/aws-sdk-go-base v1.1.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= +github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.16 h1:Ac/qv9kXBBeyIAGkZdy+Idmw4Avb3mdxPVdT0rI/+aY= +github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.16/go.mod h1:xySJRdcDB8hVSmboo3X+evGhZPzBYwPmZbQxLgfGfBw= +github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2 v2.0.0-beta.17 h1:hUG69tIA35oNI2hoeURCnShSViDNCmcQdLSngP89124= +github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2 v2.0.0-beta.17/go.mod h1:MS242y2IOhTXoT0nhbJt/Z1p/6hlisZunHwvrWAcJyY= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUKaCaGKZ/dR2roBXv0vKbSCnssIldfQdI= @@ -193,40 +263,74 @@ github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9 github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.15.0 h1:qMuK0wxsoW4D0ddCCYwPSTm4KQv1X1ke3WmPWZ0Mvsk= github.com/hashicorp/go-hclog v0.15.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v1.2.1 h1:YQsLlGDJgwhXFpucSPyVbCBviQtjlHv3jLTlp8YmtEw= +github.com/hashicorp/go-hclog v1.2.1/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= github.com/hashicorp/go-plugin v1.4.1 h1:6UltRQlLN9iZO513VveELp5xyaFxVD2+1OVylE+2E+w= github.com/hashicorp/go-plugin v1.4.1/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= +github.com/hashicorp/go-plugin v1.4.4 h1:NVdrSdFRt3SkZtNckJ6tog7gbpRrcbOjQi/rgF7JYWQ= +github.com/hashicorp/go-plugin v1.4.4/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04x7iNw= github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hc-install v0.4.0 h1:cZkRFr1WVa0Ty6x5fTvL1TuO1flul231rWkGH92oYYk= +github.com/hashicorp/hc-install v0.4.0/go.mod h1:5d155H8EC5ewegao9A4PUTMNPZaq+TbOzkJJZ4vrXeI= github.com/hashicorp/hcl/v2 v2.3.0/go.mod h1:d+FwDBbOLvpAM3Z6J7gPj/VoAGkNe/gm352ZhjJ/Zv8= github.com/hashicorp/hcl/v2 v2.8.2 h1:wmFle3D1vu0okesm8BTLVDyJ6/OL9DCLUwn0b2OptiY= github.com/hashicorp/hcl/v2 v2.8.2/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= +github.com/hashicorp/hcl/v2 v2.13.0 h1:0Apadu1w6M11dyGFxWnmhhcMjkbAiKCv7G1r/2QgCNc= +github.com/hashicorp/hcl/v2 v2.13.0/go.mod h1:e4z5nxYlWNPdDSNYX+ph14EvWYMFm3eP0zIUqPc2jr0= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.12.0/go.mod h1:SGhto91bVRlgXQWcJ5znSz+29UZIa8kpBbkGwQ+g9E8= github.com/hashicorp/terraform-exec v0.14.0 h1:UQoUcxKTZZXhyyK68Cwn4mApT4mnFPmEXPiqaHL9r+w= github.com/hashicorp/terraform-exec v0.14.0/go.mod h1:qrAASDq28KZiMPDnQ02sFS9udcqEkRly002EA2izXTA= +github.com/hashicorp/terraform-exec v0.17.2 h1:EU7i3Fh7vDUI9nNRdMATCEfnm9axzTnad8zszYZ73Go= +github.com/hashicorp/terraform-exec v0.17.2/go.mod h1:tuIbsL2l4MlwwIZx9HPM+LOV9vVyEfBYu2GsO1uH3/8= github.com/hashicorp/terraform-json v0.8.0/go.mod h1:3defM4kkMfttwiE7VakJDwCd4R+umhSQnvJwORXbprE= github.com/hashicorp/terraform-json v0.12.0 h1:8czPgEEWWPROStjkWPUnTQDXmpmZPlkQAwYYLETaTvw= github.com/hashicorp/terraform-json v0.12.0/go.mod h1:pmbq9o4EuL43db5+0ogX10Yofv1nozM+wskr/bGFJpI= +github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= +github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/terraform-plugin-docs v0.4.0 h1:xJIXsMzBFwBvC1zcjoNz743GL2tNEfYFFU9+Hjp4Uek= github.com/hashicorp/terraform-plugin-docs v0.4.0/go.mod h1:fKj/V3t45tiXpSlUms/0G4OrBayyWpbUJ4WtLjBkINU= +github.com/hashicorp/terraform-plugin-framework v0.10.0 h1:LGYcnvNdVaZA1ZHe53BHLVjaaGs7HTiq6+9Js29stL4= +github.com/hashicorp/terraform-plugin-framework v0.10.0/go.mod h1:CK7Opzukfu/2CPJs+HzUdfHrFlp+ZIQeSxjF0x8k464= github.com/hashicorp/terraform-plugin-go v0.4.0 h1:LFbXNeLDo0J/wR0kUzSPq0RpdmFh2gNedzU0n/gzPAo= github.com/hashicorp/terraform-plugin-go v0.4.0/go.mod h1:7u/6nt6vaiwcWE2GuJKbJwNlDFnf5n95xKw4hqIVr58= +github.com/hashicorp/terraform-plugin-go v0.12.0 h1:6wW9mT1dSs0Xq4LR6HXj1heQ5ovr5GxXNJwkErZzpJw= +github.com/hashicorp/terraform-plugin-go v0.12.0/go.mod h1:kwhmaWHNDvT1B3QiSJdAtrB/D4RaKSY/v3r2BuoWK4M= +github.com/hashicorp/terraform-plugin-log v0.6.0 h1:/Vq78uSIdUSZ3iqDc9PESKtwt8YqNKN6u+khD+lLjuw= +github.com/hashicorp/terraform-plugin-log v0.6.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= +github.com/hashicorp/terraform-plugin-log v0.7.0 h1:SDxJUyT8TwN4l5b5/VkiTIaQgY6R+Y2BQ0sRZftGKQs= +github.com/hashicorp/terraform-plugin-log v0.7.0/go.mod h1:p4R1jWBXRTvL4odmEkFfDdhUjHf9zcs/BCoNHAc7IK4= +github.com/hashicorp/terraform-plugin-mux v0.7.0 h1:wRbSYzg+v2sn5Mdee0UKm4YTt4wJG0LfSwtgNuBkglY= +github.com/hashicorp/terraform-plugin-mux v0.7.0/go.mod h1:Ae30Mc5lz4d1awtiCbHP0YyvgBeiQ00Q1nAq0U3lb+I= github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0 h1:GSumgrL6GGcRYU37YuF1CC59hRPR7Yzy6tpoFlo8wr4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.8.0/go.mod h1:6KbP09YzlB++S6XSUKYl83WyoHVN4MgeoCbPRsdfCtA= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0 h1:+KxZULPsbjpAVoP0WNj/8aVW6EqpcX5JcUcQ5wl7Da4= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.20.0/go.mod h1:DwGJG3KNxIPluVk6hexvDfYR/MS/eKGpiztJoT3Bbbw= +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 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= +github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -264,6 +368,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/klauspost/compress v1.11.2 h1:MiK62aErc3gIiVEtyzKfeOHgW7atJb5g/KNX5m3c2nQ= github.com/klauspost/compress v1.11.2/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -279,6 +384,9 @@ github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaO github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= @@ -286,6 +394,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mitchellh/cli v1.1.1/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/cli v1.1.2 h1:PvH+lL2B7IQ101xQL63Of8yFS2y+aDlsFcsqNc+u/Kw= @@ -307,6 +417,8 @@ github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUb github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= @@ -323,6 +435,7 @@ github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndr github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6DI= github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= @@ -342,13 +455,17 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/ulikunitz/xz v0.5.5/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= github.com/ulikunitz/xz v0.5.8 h1:ERv8V6GKqVi23rgu5cj9pVfVzJbOqAY2Ntl88O6c2nQ= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4= github.com/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= @@ -357,11 +474,14 @@ github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.2.1/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= github.com/zclconf/go-cty v1.7.1/go.mod h1:VDR4+I79ubFBGm1uJac1226K5yANQFHeauxPBoP54+o= github.com/zclconf/go-cty v1.8.4 h1:pwhhz5P+Fjxse7S7UriBrMu6AUJSZM5pKqGem1PjGAs= github.com/zclconf/go-cty v1.8.4/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= @@ -369,6 +489,7 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -381,6 +502,9 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167 h1:O8uGbHCqlTp2P6QJSLmCojM4mN6UemYv8K+dCnmHmu0= +golang.org/x/crypto v0.0.0-20220517005047-85d78b3ac167/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -427,6 +551,7 @@ golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -442,6 +567,7 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= @@ -499,12 +625,17 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -616,6 +747,7 @@ google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfG google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= @@ -641,6 +773,10 @@ google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0= google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.48.0 h1:rQOsyJ/8+ufEDJd/Gdsz7HG220Mh9HAhFHRGnIjda0w= +google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -652,9 +788,15 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= @@ -663,6 +805,7 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= @@ -670,6 +813,8 @@ gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go new file mode 100644 index 0000000..f0cc606 --- /dev/null +++ b/internal/acctest/acctest.go @@ -0,0 +1,1965 @@ +package acctest + +import ( + "context" + "fmt" + "log" + "os" + "regexp" + "strconv" + "strings" + "sync" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go/service/acmpca" + "github.com/aws/aws-sdk-go/service/directoryservice" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go/service/outposts" + "github.com/aws/aws-sdk-go/service/ssoadmin" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/cloudposse/terraform-provider-awsutils/internal/provider" + tfec2 "github.com/cloudposse/terraform-provider-awsutils/internal/service/ec2" + tforganizations "github.com/cloudposse/terraform-provider-awsutils/internal/service/organizations" + tfsts "github.com/cloudposse/terraform-provider-awsutils/internal/service/sts" + "github.com/cloudposse/terraform-provider-awsutils/internal/tfresource" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +const ( + // Provider name for single configuration testing + ProviderName = "aws" + + // Provider name for alternate configuration testing + ProviderNameAlternate = "awsalternate" + + // Provider name for alternate account and alternate region configuration testing + ProviderNameAlternateAccountAlternateRegion = "awsalternateaccountalternateregion" + + // Provider name for alternate account and same region configuration testing + ProviderNameAlternateAccountSameRegion = "awsalternateaccountsameregion" + + // Provider name for same account and alternate region configuration testing + ProviderNameSameAccountAlternateRegion = "awssameaccountalternateregion" + + // Provider name for third configuration testing + ProviderNameThird = "awsthird" + + ResourcePrefix = "tf-acc-test" +) + +const RFC3339RegexPattern = `^[0-9]{4}-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?([Zz]|([+-]([01][0-9]|2[0-3]):[0-5][0-9]))$` +const regionRegexp = `[a-z]{2}(-[a-z]+)+-\d` +const accountIDRegexp = `(aws|aws-managed|\d{12})` + +// Skip implements a wrapper for (*testing.T).Skip() to prevent unused linting reports +// +// Reference: https://github.com/dominikh/go-tools/issues/633#issuecomment-606560616 +func Skip(t *testing.T, message string) { + t.Skip(message) +} + +// ProtoV5ProviderFactories is a static map containing only the main provider instance +// +// Use other ProviderFactories functions, such as FactoriesAlternate, +// for tests requiring special provider configurations. +var ProtoV5ProviderFactories map[string]func() (tfprotov5.ProviderServer, error) + +// Provider is the "main" provider instance +// +// This Provider can be used in testing code for API calls without requiring +// the use of saving and referencing specific ProviderFactories instances. +// +// PreCheck(t) must be called before using this provider instance. +var Provider *schema.Provider + +// testAccProviderConfigure ensures Provider is only configured once +// +// The PreCheck(t) function is invoked for every test and this prevents +// extraneous reconfiguration to the same values each time. However, this does +// not prevent reconfiguration that may happen should the address of +// Provider be errantly reused in ProviderFactories. +var testAccProviderConfigure sync.Once + +func init() { + var err error + Provider, err = provider.New(context.Background()) + + if err != nil { + panic(err) + } + + // Always allocate a new provider instance each invocation, otherwise gRPC + // ProviderConfigure() can overwrite configuration during concurrent testing. + ProtoV5ProviderFactories = protoV5ProviderFactoriesInit(ProviderName) +} + +func protoV5ProviderFactoriesInit(providerNames ...string) map[string]func() (tfprotov5.ProviderServer, error) { + factories := make(map[string]func() (tfprotov5.ProviderServer, error), len(providerNames)) + + for _, name := range providerNames { + factories[name] = func() (tfprotov5.ProviderServer, error) { + providerServerFactory, err := provider.ProtoV5ProviderServerFactory(context.Background()) + + if err != nil { + return nil, err + } + + return providerServerFactory(), nil + } + } + + return factories +} + +func factoriesInit(t *testing.T, providers *[]*schema.Provider, providerNames []string) map[string]func() (*schema.Provider, error) { + ctx := context.Background() + var factories = make(map[string]func() (*schema.Provider, error), len(providerNames)) + + for _, name := range providerNames { + p, err := provider.New(ctx) + + if err != nil { + t.Fatal(err) + } + + factories[name] = func() (*schema.Provider, error) { //nolint:unparam + return p, nil + } + + if providers != nil { + *providers = append(*providers, p) + } + } + + return factories +} + +// FactoriesAlternate creates ProviderFactories for cross-account and cross-region configurations +// +// For cross-region testing: Typically paired with PreCheckMultipleRegion and ConfigAlternateRegionProvider. +// +// For cross-account testing: Typically paired with PreCheckAlternateAccount and ConfigAlternateAccountProvider. +func FactoriesAlternate(t *testing.T, providers *[]*schema.Provider) map[string]func() (*schema.Provider, error) { + return factoriesInit(t, providers, []string{ + ProviderName, + ProviderNameAlternate, + }) +} + +func ProtoV5FactoriesAlternate(t *testing.T) map[string]func() (tfprotov5.ProviderServer, error) { + return protoV5ProviderFactoriesInit(ProviderName, ProviderNameAlternate) +} + +// ProtoV5FactoriesAlternateAccountAndAlternateRegion creates ProtoV5ProviderFactories for cross-account and cross-region configurations +// +// Usage typically paired with PreCheckMultipleRegion, PreCheckAlternateAccount, +// and ConfigAlternateAccountAndAlternateRegionProvider. +func ProtoV5FactoriesAlternateAccountAndAlternateRegion(t *testing.T) map[string]func() (tfprotov5.ProviderServer, error) { + return protoV5ProviderFactoriesInit( + ProviderName, + ProviderNameAlternateAccountAlternateRegion, + ProviderNameAlternateAccountSameRegion, + ProviderNameSameAccountAlternateRegion, + ) +} + +// ProtoV5FactoriesMultipleRegions creates ProtoV5ProviderFactories for the specified number of region configurations +// +// Usage typically paired with PreCheckMultipleRegion and ConfigMultipleRegionProvider. +func ProtoV5FactoriesMultipleRegions(t *testing.T, n int) map[string]func() (tfprotov5.ProviderServer, error) { + switch n { + case 2: + return protoV5ProviderFactoriesInit(ProviderName, ProviderNameAlternate) + case 3: + return protoV5ProviderFactoriesInit(ProviderName, ProviderNameAlternate, ProviderNameThird) + default: + t.Fatalf("invalid number of Region configurations: %d", n) + } + + return nil +} + +// PreCheck verifies and sets required provider testing configuration +// +// This PreCheck function should be present in every acceptance test. It allows +// test configurations to omit a provider configuration with region and ensures +// testing functions that attempt to call AWS APIs are previously configured. +// +// These verifications and configuration are preferred at this level to prevent +// provider developers from experiencing less clear errors for every test. +func PreCheck(t *testing.T) { + // Since we are outside the scope of the Terraform configuration we must + // call Configure() to properly initialize the provider configuration. + testAccProviderConfigure.Do(func() { + conns.FailIfAllEnvVarEmpty(t, []string{conns.EnvVarProfile, conns.EnvVarAccessKeyId, conns.EnvVarContainerCredentialsFullURI}, "credentials for running acceptance testing") + + if os.Getenv(conns.EnvVarAccessKeyId) != "" { + conns.FailIfEnvVarEmpty(t, conns.EnvVarSecretAccessKey, "static credentials value when using "+conns.EnvVarAccessKeyId) + } + + // Setting the AWS_DEFAULT_REGION environment variable here allows all tests to omit + // a provider configuration with a region. This defaults to us-west-2 for provider + // developer simplicity and has been in the codebase for a very long time. + // + // This handling must be preserved until either: + // * AWS_DEFAULT_REGION is required and checked above (should mention us-west-2 default) + // * Region is automatically handled via shared AWS configuration file and still verified + region := Region() + os.Setenv(conns.EnvVarDefaultRegion, region) + + err := Provider.Configure(context.Background(), terraform.NewResourceConfigRaw(nil)) + if err != nil { + t.Fatal(err) + } + }) +} + +// providerAccountID returns the account ID of an AWS provider +func providerAccountID(provo *schema.Provider) string { + if provo == nil { + log.Print("[DEBUG] Unable to read account ID from test provider: empty provider") + return "" + } + if provo.Meta() == nil { + log.Print("[DEBUG] Unable to read account ID from test provider: unconfigured provider") + return "" + } + client, ok := provo.Meta().(*conns.AWSClient) + if !ok { + log.Print("[DEBUG] Unable to read account ID from test provider: non-AWS or unconfigured AWS provider") + return "" + } + return client.AccountID +} + +// CheckDestroyNoop is a TestCheckFunc to be used as a TestCase's CheckDestroy when no such check can be made. +func CheckDestroyNoop(_ *terraform.State) error { + return nil +} + +// CheckResourceAttrAccountID ensures the Terraform state exactly matches the account ID +func CheckResourceAttrAccountID(resourceName, attributeName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + return resource.TestCheckResourceAttr(resourceName, attributeName, AccountID())(s) + } +} + +// CheckResourceAttrRegionalARN ensures the Terraform state exactly matches a formatted ARN with region +func CheckResourceAttrRegionalARN(resourceName, attributeName, arnService, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributeValue := arn.ARN{ + AccountID: AccountID(), + Partition: Partition(), + Region: Region(), + Resource: arnResource, + Service: arnService, + }.String() + return resource.TestCheckResourceAttr(resourceName, attributeName, attributeValue)(s) + } +} + +// CheckResourceAttrRegionalARNNoAccount ensures the Terraform state exactly matches a formatted ARN with region but without account ID +func CheckResourceAttrRegionalARNNoAccount(resourceName, attributeName, arnService, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributeValue := arn.ARN{ + Partition: Partition(), + Region: Region(), + Resource: arnResource, + Service: arnService, + }.String() + return resource.TestCheckResourceAttr(resourceName, attributeName, attributeValue)(s) + } +} + +// CheckResourceAttrRegionalARNAccountID ensures the Terraform state exactly matches a formatted ARN with region and specific account ID +func CheckResourceAttrRegionalARNAccountID(resourceName, attributeName, arnService, accountID, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributeValue := arn.ARN{ + AccountID: accountID, + Partition: Partition(), + Region: Region(), + Resource: arnResource, + Service: arnService, + }.String() + return resource.TestCheckResourceAttr(resourceName, attributeName, attributeValue)(s) + } +} + +// CheckResourceAttrRegionalHostnameService ensures the Terraform state exactly matches a service DNS hostname with region and partition DNS suffix +// +// For example: ec2.us-west-2.amazonaws.com +func CheckResourceAttrRegionalHostnameService(resourceName, attributeName, serviceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + hostname := fmt.Sprintf("%s.%s.%s", serviceName, Region(), PartitionDNSSuffix()) + + return resource.TestCheckResourceAttr(resourceName, attributeName, hostname)(s) + } +} + +// MatchResourceAttrAccountID ensures the Terraform state regexp matches an account ID +func MatchResourceAttrAccountID(resourceName, attributeName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + return resource.TestMatchResourceAttr(resourceName, attributeName, regexp.MustCompile(`^\d{12}$`))(s) + } +} + +// MatchResourceAttrRegionalARN ensures the Terraform state regexp matches a formatted ARN with region +func MatchResourceAttrRegionalARN(resourceName, attributeName, arnService string, arnResourceRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + arnRegexp := arn.ARN{ + AccountID: AccountID(), + Partition: Partition(), + Region: Region(), + Resource: arnResourceRegexp.String(), + Service: arnService, + }.String() + + attributeMatch, err := regexp.Compile(arnRegexp) + + if err != nil { + return fmt.Errorf("unable to compile ARN regexp (%s): %w", arnRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, attributeMatch)(s) + } +} + +// MatchResourceAttrRegionalARNNoAccount ensures the Terraform state regexp matches a formatted ARN with region but without account ID +func MatchResourceAttrRegionalARNNoAccount(resourceName, attributeName, arnService string, arnResourceRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + arnRegexp := arn.ARN{ + Partition: Partition(), + Region: Region(), + Resource: arnResourceRegexp.String(), + Service: arnService, + }.String() + + attributeMatch, err := regexp.Compile(arnRegexp) + + if err != nil { + return fmt.Errorf("unable to compile ARN regexp (%s): %s", arnRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, attributeMatch)(s) + } +} + +// MatchResourceAttrRegionalARNAccountID ensures the Terraform state regexp matches a formatted ARN with region and specific account ID +func MatchResourceAttrRegionalARNAccountID(resourceName, attributeName, arnService, accountID string, arnResourceRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + arnRegexp := arn.ARN{ + AccountID: accountID, + Partition: Partition(), + Region: Region(), + Resource: arnResourceRegexp.String(), + Service: arnService, + }.String() + + attributeMatch, err := regexp.Compile(arnRegexp) + + if err != nil { + return fmt.Errorf("unable to compile ARN regexp (%s): %w", arnRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, attributeMatch)(s) + } +} + +// MatchResourceAttrRegionalHostname ensures the Terraform state regexp matches a formatted DNS hostname with region and partition DNS suffix +func MatchResourceAttrRegionalHostname(resourceName, attributeName, serviceName string, hostnamePrefixRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + hostnameRegexpPattern := fmt.Sprintf("%s\\.%s\\.%s\\.%s$", hostnamePrefixRegexp.String(), serviceName, Region(), PartitionDNSSuffix()) + + hostnameRegexp, err := regexp.Compile(hostnameRegexpPattern) + + if err != nil { + return fmt.Errorf("unable to compile hostname regexp (%s): %w", hostnameRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, hostnameRegexp)(s) + } +} + +// MatchResourceAttrGlobalHostname ensures the Terraform state regexp matches a formatted DNS hostname with partition DNS suffix and without region +func MatchResourceAttrGlobalHostname(resourceName, attributeName, serviceName string, hostnamePrefixRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + hostnameRegexpPattern := fmt.Sprintf("%s\\.%s\\.%s$", hostnamePrefixRegexp.String(), serviceName, PartitionDNSSuffix()) + + hostnameRegexp, err := regexp.Compile(hostnameRegexpPattern) + + if err != nil { + return fmt.Errorf("unable to compile hostname regexp (%s): %w", hostnameRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, hostnameRegexp)(s) + } +} + +// CheckResourceAttrGlobalARN ensures the Terraform state exactly matches a formatted ARN without region +func CheckResourceAttrGlobalARN(resourceName, attributeName, arnService, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributeValue := arn.ARN{ + AccountID: AccountID(), + Partition: Partition(), + Resource: arnResource, + Service: arnService, + }.String() + return resource.TestCheckResourceAttr(resourceName, attributeName, attributeValue)(s) + } +} + +// CheckResourceAttrGlobalARNNoAccount ensures the Terraform state exactly matches a formatted ARN without region or account ID +func CheckResourceAttrGlobalARNNoAccount(resourceName, attributeName, arnService, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributeValue := arn.ARN{ + Partition: Partition(), + Resource: arnResource, + Service: arnService, + }.String() + return resource.TestCheckResourceAttr(resourceName, attributeName, attributeValue)(s) + } +} + +// CheckResourceAttrGlobalARNAccountID ensures the Terraform state exactly matches a formatted ARN without region and with specific account ID +func CheckResourceAttrGlobalARNAccountID(resourceName, attributeName, accountID, arnService, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributeValue := arn.ARN{ + AccountID: accountID, + Partition: Partition(), + Resource: arnResource, + Service: arnService, + }.String() + return resource.TestCheckResourceAttr(resourceName, attributeName, attributeValue)(s) + } +} + +// MatchResourceAttrGlobalARN ensures the Terraform state regexp matches a formatted ARN without region +func MatchResourceAttrGlobalARN(resourceName, attributeName, arnService string, arnResourceRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + arnRegexp := arn.ARN{ + AccountID: AccountID(), + Partition: Partition(), + Resource: arnResourceRegexp.String(), + Service: arnService, + }.String() + + attributeMatch, err := regexp.Compile(arnRegexp) + + if err != nil { + return fmt.Errorf("unable to compile ARN regexp (%s): %w", arnRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, attributeMatch)(s) + } +} + +// CheckResourceAttrRegionalARNIgnoreRegionAndAccount ensures the Terraform state exactly matches a formatted ARN with region without specifying the region or account +func CheckResourceAttrRegionalARNIgnoreRegionAndAccount(resourceName, attributeName, arnService, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + arnRegexp := arn.ARN{ + AccountID: accountIDRegexp, + Partition: Partition(), + Region: regionRegexp, + Resource: arnResource, + Service: arnService, + }.String() + + attributeMatch, err := regexp.Compile(arnRegexp) + + if err != nil { + return fmt.Errorf("unable to compile ARN regexp (%s): %w", arnRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, attributeMatch)(s) + } +} + +// MatchResourceAttrGlobalARNNoAccount ensures the Terraform state regexp matches a formatted ARN without region or account ID +func MatchResourceAttrGlobalARNNoAccount(resourceName, attributeName, arnService string, arnResourceRegexp *regexp.Regexp) resource.TestCheckFunc { + return func(s *terraform.State) error { + arnRegexp := arn.ARN{ + Partition: Partition(), + Resource: arnResourceRegexp.String(), + Service: arnService, + }.String() + + attributeMatch, err := regexp.Compile(arnRegexp) + + if err != nil { + return fmt.Errorf("unable to compile ARN regexp (%s): %s", arnRegexp, err) + } + + return resource.TestMatchResourceAttr(resourceName, attributeName, attributeMatch)(s) + } +} + +// CheckResourceAttrRFC3339 ensures the Terraform state matches a RFC3339 value +// This TestCheckFunc will likely be moved to the Terraform Plugin SDK in the future. +func CheckResourceAttrRFC3339(resourceName, attributeName string) resource.TestCheckFunc { + return resource.TestMatchResourceAttr(resourceName, attributeName, regexp.MustCompile(RFC3339RegexPattern)) +} + +// CheckResourceAttrEquivalentJSON is a TestCheckFunc that compares a JSON value with an expected value. Both JSON +// values are normalized before being compared. +func CheckResourceAttrEquivalentJSON(resourceName, attributeName, expectedJSON string) resource.TestCheckFunc { + return func(s *terraform.State) error { + is, err := PrimaryInstanceState(s, resourceName) + if err != nil { + return err + } + + v, ok := is.Attributes[attributeName] + if !ok { + return fmt.Errorf("%s: No attribute %q found", resourceName, attributeName) + } + + vNormal, err := structure.NormalizeJsonString(v) + if err != nil { + return fmt.Errorf("%s: Error normalizing JSON in %q: %w", resourceName, attributeName, err) + } + + expectedNormal, err := structure.NormalizeJsonString(expectedJSON) + if err != nil { + return fmt.Errorf("normalizing expected JSON: %w", err) + } + + if vNormal != expectedNormal { + return fmt.Errorf("%s: Attribute %q expected\n%s\ngot\n%s", resourceName, attributeName, expectedJSON, v) + } + return nil + } +} + +// Copied and inlined from the SDK testing code +func PrimaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { + rs, ok := s.RootModule().Resources[name] + if !ok { + return nil, fmt.Errorf("not found: %s", name) + } + + is := rs.Primary + if is == nil { + return nil, fmt.Errorf("no primary instance: %s", name) + } + + return is, nil +} + +// AccountID returns the account ID of Provider +// Must be used within a resource.TestCheckFunc +func AccountID() string { + return providerAccountID(Provider) +} + +func Region() string { + return conns.GetEnvVarWithDefault(conns.EnvVarDefaultRegion, endpoints.UsWest2RegionID) +} + +func AlternateRegion() string { + return conns.GetEnvVarWithDefault(conns.EnvVarAlternateRegion, endpoints.UsEast1RegionID) +} + +func ThirdRegion() string { + return conns.GetEnvVarWithDefault(conns.EnvVarThirdRegion, endpoints.UsEast2RegionID) +} + +func Partition() string { + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), Region()); ok { + return partition.ID() + } + return "aws" +} + +func PartitionDNSSuffix() string { + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), Region()); ok { + return partition.DNSSuffix() + } + return "amazonaws.com" +} + +func PartitionReverseDNSPrefix() string { + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), Region()); ok { + return conns.ReverseDNS(partition.DNSSuffix()) + } + + return "com.amazonaws" +} + +func alternateRegionPartition() string { + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), AlternateRegion()); ok { + return partition.ID() + } + return "aws" +} + +func thirdRegionPartition() string { + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), ThirdRegion()); ok { + return partition.ID() + } + return "aws" +} + +func PreCheckAlternateAccount(t *testing.T) { + conns.SkipIfAllEnvVarEmpty(t, []string{conns.EnvVarAlternateProfile, conns.EnvVarAlternateAccessKeyId}, "credentials for running acceptance testing in alternate AWS account") + + if os.Getenv(conns.EnvVarAlternateAccessKeyId) != "" { + conns.SkipIfEnvVarEmpty(t, conns.EnvVarAlternateSecretAccessKey, "static credentials value when using "+conns.EnvVarAlternateAccessKeyId) + } +} + +func PreCheckPartitionHasService(serviceId string, t *testing.T) { + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), Region()); ok { + if _, ok := partition.Services()[serviceId]; !ok { + t.Skipf("skipping tests; partition %s does not support %s service", partition.ID(), serviceId) + } + } +} + +func PreCheckMultipleRegion(t *testing.T, regions int) { + if Region() == AlternateRegion() { + t.Fatalf("%s and %s must be set to different values for acceptance tests", conns.EnvVarDefaultRegion, conns.EnvVarAlternateRegion) + } + + if Partition() != alternateRegionPartition() { + t.Fatalf("%s partition (%s) does not match %s partition (%s)", conns.EnvVarAlternateRegion, alternateRegionPartition(), conns.EnvVarDefaultRegion, Partition()) + } + + if regions >= 3 { + if thirdRegionPartition() == "aws-us-gov" || Partition() == "aws-us-gov" { + t.Skipf("wanted %d regions, partition (%s) only has 2 regions", regions, Partition()) + } + + if Region() == ThirdRegion() { + t.Fatalf("%s and %s must be set to different values for acceptance tests", conns.EnvVarDefaultRegion, conns.EnvVarThirdRegion) + } + + if AlternateRegion() == ThirdRegion() { + t.Fatalf("%s and %s must be set to different values for acceptance tests", conns.EnvVarAlternateRegion, conns.EnvVarThirdRegion) + } + + if Partition() != thirdRegionPartition() { + t.Fatalf("%s partition (%s) does not match %s partition (%s)", conns.EnvVarThirdRegion, thirdRegionPartition(), conns.EnvVarDefaultRegion, Partition()) + } + } + + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), Region()); ok { + if len(partition.Regions()) < regions { + t.Skipf("skipping tests; partition includes %d regions, %d expected", len(partition.Regions()), regions) + } + } +} + +// PreCheckRegion checks that the test region is the specified region. +func PreCheckRegion(t *testing.T, region string) { + if curr := Region(); curr != region { + t.Skipf("skipping tests; %s (%s) does not equal %s", conns.EnvVarDefaultRegion, curr, region) + } +} + +// PreCheckRegionNot checks that the test region is not one of the specified regions. +func PreCheckRegionNot(t *testing.T, regions ...string) { + for _, region := range regions { + if curr := Region(); curr == region { + t.Skipf("skipping tests; %s (%s) not supported", conns.EnvVarDefaultRegion, curr) + } + } +} + +// PreCheckPartition checks that the test partition is the specified partition. +func PreCheckPartition(t *testing.T, partition string) { + if curr := Partition(); curr != partition { + t.Skipf("skipping tests; current partition (%s) does not equal %s", curr, partition) + } +} + +// PreCheckPartitionNot checks that the test partition is not one of the specified partitions. +func PreCheckPartitionNot(t *testing.T, partitions ...string) { + for _, partition := range partitions { + if curr := Partition(); curr == partition { + t.Skipf("skipping tests; current partition (%s) not supported", curr) + } + } +} + +func PreCheckOrganizationsAccount(t *testing.T) { + _, err := tforganizations.FindOrganization(Provider.Meta().(*conns.AWSClient).OrganizationsConn) + + if tfresource.NotFound(err) { + return + } + + if err != nil { + t.Fatalf("error describing AWS Organization: %s", err) + } + + t.Skip("skipping tests; this AWS account must not be an existing member of an AWS Organization") +} + +func PreCheckOrganizationsEnabled(t *testing.T) { + _, err := tforganizations.FindOrganization(Provider.Meta().(*conns.AWSClient).OrganizationsConn) + + if tfresource.NotFound(err) { + t.Skip("this AWS account must be an existing member of an AWS Organization") + } + + if err != nil { + t.Fatalf("error describing AWS Organization: %s", err) + } +} + +func PreCheckOrganizationManagementAccount(t *testing.T) { + organization, err := tforganizations.FindOrganization(Provider.Meta().(*conns.AWSClient).OrganizationsConn) + + if err != nil { + t.Fatalf("error describing AWS Organization: %s", err) + } + + callerIdentity, err := tfsts.FindCallerIdentity(Provider.Meta().(*conns.AWSClient).STSConn) + + if err != nil { + t.Fatalf("error getting current identity: %s", err) + } + + if aws.StringValue(organization.MasterAccountId) != aws.StringValue(callerIdentity.Account) { + t.Skip("this AWS account must be the management account of an AWS Organization") + } +} + +func PreCheckSSOAdminInstances(t *testing.T) { + conn := Provider.Meta().(*conns.AWSClient).SSOAdminConn + input := &ssoadmin.ListInstancesInput{} + var instances []*ssoadmin.InstanceMetadata + + err := conn.ListInstancesPages(input, func(page *ssoadmin.ListInstancesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + instances = append(instances, page.Instances...) + + return !lastPage + }) + + if PreCheckSkipError(err) { + t.Skipf("skipping tests: %s", err) + } + + if len(instances) == 0 { + t.Skip("skipping tests; no SSO Instances found.") + } + + if err != nil { + t.Fatalf("error listing SSO Instances: %s", err) + } +} + +func PreCheckHasIAMRole(t *testing.T, roleName string) { + conn := Provider.Meta().(*conns.AWSClient).IAMConn + + input := &iam.GetRoleInput{ + RoleName: aws.String(roleName), + } + _, err := conn.GetRole(input) + + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + t.Skipf("skipping acceptance test: required IAM role \"%s\" is not present", roleName) + } + if PreCheckSkipError(err) { + t.Skipf("skipping acceptance test: %s", err) + } + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func PreCheckIAMServiceLinkedRole(t *testing.T, pathPrefix string) { + conn := Provider.Meta().(*conns.AWSClient).IAMConn + + input := &iam.ListRolesInput{ + PathPrefix: aws.String(pathPrefix), + } + + var role *iam.Role + err := conn.ListRolesPages(input, func(page *iam.ListRolesOutput, lastPage bool) bool { + for _, r := range page.Roles { + role = r + break + } + + return !lastPage + }) + + if PreCheckSkipError(err) { + t.Skipf("skipping tests: %s", err) + } + + if err != nil { + t.Fatalf("error listing IAM roles: %s", err) + } + + if role == nil { + t.Skipf("skipping tests; missing IAM service-linked role %s. Please create the role and retry", pathPrefix) + } +} + +func ConfigAlternateAccountProvider() string { + //lintignore:AT004 + return fmt.Sprintf(` +provider "awsalternate" { + access_key = %[1]q + profile = %[2]q + secret_key = %[3]q +} +`, os.Getenv(conns.EnvVarAlternateAccessKeyId), os.Getenv(conns.EnvVarAlternateProfile), os.Getenv(conns.EnvVarAlternateSecretAccessKey)) +} + +// Deprecated: Use ConfigMultipleRegionProvider instead +func ConfigAlternateRegionProvider() string { + return ConfigNamedRegionalProvider(ProviderNameAlternate, AlternateRegion()) +} + +func ConfigMultipleRegionProvider(regions int) string { + var config strings.Builder + + config.WriteString(ConfigNamedRegionalProvider(ProviderNameAlternate, AlternateRegion())) + + if regions >= 3 { + config.WriteString(ConfigNamedRegionalProvider(ProviderNameThird, ThirdRegion())) + } + + return config.String() +} + +func ConfigDefaultAndIgnoreTagsKeyPrefixes1(key1, value1, keyPrefix1 string) string { + //lintignore:AT004 + return fmt.Sprintf(` +provider "aws" { + default_tags { + tags = { + %q = %q + } + } + ignore_tags { + key_prefixes = [%q] + } +} +`, key1, value1, keyPrefix1) +} + +func ConfigDefaultAndIgnoreTagsKeys1(key1, value1 string) string { + //lintignore:AT004 + return fmt.Sprintf(` +provider "aws" { + default_tags { + tags = { + %[1]q = %q + } + } + ignore_tags { + keys = [%[1]q] + } +} +`, key1, value1) +} + +func ConfigIgnoreTagsKeyPrefixes1(keyPrefix1 string) string { + //lintignore:AT004 + return fmt.Sprintf(` +provider "aws" { + ignore_tags { + key_prefixes = [%[1]q] + } +} +`, keyPrefix1) +} + +func ConfigIgnoreTagsKeys(key1 string) string { + //lintignore:AT004 + return fmt.Sprintf(` +provider "aws" { + ignore_tags { + keys = [%[1]q] + } +} +`, key1) +} + +// ConfigNamedRegionalProvider creates a new provider named configuration with a region. +// +// This can be used to build multiple provider configuration testing. +func ConfigNamedRegionalProvider(providerName string, region string) string { + //lintignore:AT004 + return fmt.Sprintf(` +provider %[1]q { + region = %[2]q +} +`, providerName, region) +} + +// ConfigRegionalProvider creates a new provider configuration with a region. +// +// This can only be used for single provider configuration testing as it +// overwrites the "aws" provider configuration. +func ConfigRegionalProvider(region string) string { + //lintignore:AT004 + return fmt.Sprintf(` +provider "aws" { + region = %[1]q +} +`, region) +} + +func RegionProviderFunc(region string, providers *[]*schema.Provider) func() *schema.Provider { + return func() *schema.Provider { + if region == "" { + log.Println("[DEBUG] No region given") + return nil + } + if providers == nil { + log.Println("[DEBUG] No providers given") + return nil + } + + log.Printf("[DEBUG] Checking providers for AWS region: %s", region) + for _, provo := range *providers { + // Ignore if Meta is empty, this can happen for validation providers + if provo == nil || provo.Meta() == nil { + log.Printf("[DEBUG] Skipping empty provider") + continue + } + + // Ignore if Meta is not conns.AWSClient, this will happen for other providers + client, ok := provo.Meta().(*conns.AWSClient) + if !ok { + log.Printf("[DEBUG] Skipping non-AWS provider") + continue + } + + clientRegion := client.Region + log.Printf("[DEBUG] Checking AWS provider region %q against %q", clientRegion, region) + if clientRegion == region { + log.Printf("[DEBUG] Found AWS provider with region: %s", region) + return provo + } + } + + log.Printf("[DEBUG] No suitable provider found for %q in %d providers", region, len(*providers)) + return nil + } +} + +func DeleteResource(resource *schema.Resource, d *schema.ResourceData, meta interface{}) error { + if resource.DeleteContext != nil || resource.DeleteWithoutTimeout != nil { + var diags diag.Diagnostics + + if resource.DeleteContext != nil { + diags = resource.DeleteContext(context.Background(), d, meta) + } else { + diags = resource.DeleteWithoutTimeout(context.Background(), d, meta) + } + + for i := range diags { + if diags[i].Severity == diag.Error { + return fmt.Errorf("error deleting resource: %s", diags[i].Summary) + } + } + + return nil + } + + return resource.Delete(d, meta) +} + +func CheckResourceDisappears(provo *schema.Provider, resource *schema.Resource, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + resourceState, ok := s.RootModule().Resources[resourceName] + + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + + if resourceState.Primary.ID == "" { + return fmt.Errorf("resource ID missing: %s", resourceName) + } + + return DeleteResource(resource, resource.Data(resourceState.Primary), provo.Meta()) + } +} + +func CheckWithProviders(f func(*terraform.State, *schema.Provider) error, providers *[]*schema.Provider) resource.TestCheckFunc { + return func(s *terraform.State) error { + numberOfProviders := len(*providers) + for i, provo := range *providers { + if provo.Meta() == nil { + log.Printf("[DEBUG] Skipping empty provider %d (total: %d)", i, numberOfProviders) + continue + } + log.Printf("[DEBUG] Calling check with provider %d (total: %d)", i, numberOfProviders) + if err := f(s, provo); err != nil { + return err + } + } + return nil + } +} + +// ErrorCheckSkipMessagesContaining skips tests based on error messages that indicate unsupported features +func ErrorCheckSkipMessagesContaining(t *testing.T, messages ...string) resource.ErrorCheckFunc { + return func(err error) error { + if err == nil { + return nil + } + + for _, message := range messages { + errorMessage := err.Error() + if strings.Contains(errorMessage, message) { + t.Skipf("skipping test for %s/%s: %s", Partition(), Region(), errorMessage) + } + } + + return err + } +} + +type ServiceErrorCheckFunc func(*testing.T) resource.ErrorCheckFunc + +var serviceErrorCheckFuncs map[string]ServiceErrorCheckFunc + +func RegisterServiceErrorCheckFunc(endpointID string, f ServiceErrorCheckFunc) { + if serviceErrorCheckFuncs == nil { + serviceErrorCheckFuncs = make(map[string]ServiceErrorCheckFunc) + } + + if _, ok := serviceErrorCheckFuncs[endpointID]; ok { + // already registered + panic(fmt.Sprintf("Cannot re-register a service! ServiceErrorCheckFunc exists for %s", endpointID)) //lintignore:R009 + } + + serviceErrorCheckFuncs[endpointID] = f +} + +func ErrorCheck(t *testing.T, endpointIDs ...string) resource.ErrorCheckFunc { + return func(err error) error { + if err == nil { + return nil + } + + for _, endpointID := range endpointIDs { + if f, ok := serviceErrorCheckFuncs[endpointID]; ok { + ef := f(t) + err = ef(err) + } + + if err == nil { + break + } + } + + if errorCheckCommon(err) { + t.Skipf("skipping test for %s/%s: %s", Partition(), Region(), err.Error()) + } + + return err + } +} + +// NOTE: This function cannot use the standard tfawserr helpers +// as it is receiving error strings from the SDK testing framework, +// not actual error types from the resource logic. +func errorCheckCommon(err error) bool { + if strings.Contains(err.Error(), "is not supported in this") { + return true + } + + if strings.Contains(err.Error(), "is currently not supported") { + return true + } + + if strings.Contains(err.Error(), "InvalidAction") { + return true + } + + if strings.Contains(err.Error(), "Unknown operation") { + return true + } + + if strings.Contains(err.Error(), "UnknownOperationException") { + return true + } + + if strings.Contains(err.Error(), "UnsupportedOperation") { + return true + } + + return false +} + +// Check service API call error for reasons to skip acceptance testing +// These include missing API endpoints and unsupported API calls +func PreCheckSkipError(err error) bool { + // GovCloud has endpoints that respond with (no message provided after the error code): + // AccessDeniedException: + // Ignore these API endpoints that exist but are not officially enabled + if tfawserr.ErrCodeEquals(err, "AccessDeniedException") { + return true + } + // Ignore missing API endpoints + if tfawserr.ErrMessageContains(err, "RequestError", "send request failed") { + return true + } + // Ignore unsupported API calls + if tfawserr.ErrCodeEquals(err, "UnknownOperationException") { + return true + } + if tfawserr.ErrCodeEquals(err, "UnsupportedOperation") { + return true + } + if tfawserr.ErrMessageContains(err, "InvalidInputException", "Unknown operation") { + return true + } + if tfawserr.ErrMessageContains(err, "InvalidAction", "is not valid") { + return true + } + if tfawserr.ErrMessageContains(err, "InvalidAction", "Unavailable Operation") { + return true + } + return false +} + +func ConfigDefaultTags_Tags0() string { + //lintignore:AT004 + return ConfigCompose( + testAccProviderConfigBase, + ` +provider "aws" { + skip_credentials_validation = true + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_requesting_account_id = true +} +`) +} + +func ConfigDefaultTags_Tags1(tag1, value1 string) string { + //lintignore:AT004 + return ConfigCompose( + testAccProviderConfigBase, + fmt.Sprintf(` +provider "aws" { + default_tags { + tags = { + %q = %q + } + } + + skip_credentials_validation = true + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_requesting_account_id = true +} +`, tag1, value1)) +} + +func ConfigDefaultTags_Tags2(tag1, value1, tag2, value2 string) string { + //lintignore:AT004 + return ConfigCompose( + testAccProviderConfigBase, + fmt.Sprintf(` +provider "aws" { + default_tags { + tags = { + %q = %q + %q = %q + } + } + + skip_credentials_validation = true + skip_get_ec2_platforms = true + skip_metadata_api_check = true + skip_requesting_account_id = true +} +`, tag1, value1, tag2, value2)) +} + +func PreCheckAssumeRoleARN(t *testing.T) { + conns.SkipIfEnvVarEmpty(t, conns.EnvVarAccAssumeRoleARN, "Amazon Resource Name (ARN) of existing IAM Role to assume for testing restricted permissions") +} + +func ConfigAssumeRolePolicy(policy string) string { + //lintignore:AT004 + return fmt.Sprintf(` +provider "aws" { + assume_role { + role_arn = %q + policy = %q + } +} +`, os.Getenv(conns.EnvVarAccAssumeRoleARN), policy) +} + +const testAccProviderConfigBase = ` +data "aws_partition" "provider_test" {} + +# Required to initialize the provider +data "aws_arn" "test" { + arn = "arn:${data.aws_partition.provider_test.partition}:s3:::test" +} +` + +// ConfigCompose can be called to concatenate multiple strings to build test configurations +func ConfigCompose(config ...string) string { + var str strings.Builder + + for _, conf := range config { + str.WriteString(conf) + } + + return str.String() +} + +type domainName string + +// The top level domain ".test" is reserved by IANA for testing purposes: +// https://datatracker.ietf.org/doc/html/rfc6761 +const domainNameTestTopLevelDomain domainName = "test" + +// RandomSubdomain creates a random three-level domain name in the form +// "..test" +// The top level domain ".test" is reserved by IANA for testing purposes: +// https://datatracker.ietf.org/doc/html/rfc6761 +func RandomSubdomain() string { + return string(RandomDomain().RandomSubdomain()) +} + +// RandomDomainName creates a random two-level domain name in the form +// ".test" +// The top level domain ".test" is reserved by IANA for testing purposes: +// https://datatracker.ietf.org/doc/html/rfc6761 +func RandomDomainName() string { + return string(RandomDomain()) +} + +// RandomFQDomainName creates a random fully-qualified two-level domain name in the form +// ".test." +// The top level domain ".test" is reserved by IANA for testing purposes: +// https://datatracker.ietf.org/doc/html/rfc6761 +func RandomFQDomainName() string { + return string(RandomDomain().FQDN()) +} + +func (d domainName) Subdomain(name string) domainName { + return domainName(fmt.Sprintf("%s.%s", name, d)) +} + +func (d domainName) RandomSubdomain() domainName { + return d.Subdomain(sdkacctest.RandString(8)) //nolint:gomnd +} + +func (d domainName) FQDN() domainName { + return domainName(fmt.Sprintf("%s.", d)) +} + +func (d domainName) String() string { + return string(d) +} + +func RandomDomain() domainName { + return domainNameTestTopLevelDomain.RandomSubdomain() +} + +// DefaultEmailAddress is the default email address to set as a +// resource or data source parameter for acceptance tests. +const DefaultEmailAddress = "no-reply@hashicorp.com" + +// RandomEmailAddress generates a random email address in the form +// "tf-acc-test-@" +func RandomEmailAddress(domainName string) string { + return fmt.Sprintf("%s@%s", sdkacctest.RandomWithPrefix(ResourcePrefix), domainName) +} + +func PreCheckOutpostsOutposts(t *testing.T) { + conn := Provider.Meta().(*conns.AWSClient).OutpostsConn + + input := &outposts.ListOutpostsInput{} + + output, err := conn.ListOutposts(input) + + if PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } + + // Ensure there is at least one Outpost + if output == nil || len(output.Outposts) == 0 { + t.Skip("skipping since no Outposts found") + } +} + +const ( + // ACM domain names cannot be longer than 64 characters + // Other resources, e.g. Cognito User Pool Domains, limit this to 63 + acmCertificateDomainMaxLen = 63 + + acmRandomSubDomainPrefix = "tf-acc-" + acmRandomSubDomainPrefixLen = len(acmRandomSubDomainPrefix) + + // Max length (63) + // Subtract "tf-acc-" prefix (7) + // Subtract "." between prefix and root domain (1) + acmRandomSubDomainRemainderLen = acmCertificateDomainMaxLen - acmRandomSubDomainPrefixLen - 1 +) + +func ACMCertificateDomainFromEnv(t *testing.T) string { + rootDomain := os.Getenv("ACM_CERTIFICATE_ROOT_DOMAIN") + + if rootDomain == "" { + t.Skip( + "Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is not set. " + + "For DNS validation requests, this domain must be publicly " + + "accessible and configurable via Route53 during the testing. " + + "For email validation requests, you must have access to one of " + + "the five standard email addresses used (admin|administrator|" + + "hostmaster|postmaster|webmaster)@domain or one of the WHOIS " + + "contact addresses.") + } + + if len(rootDomain) > acmRandomSubDomainRemainderLen { + t.Skipf( + "Environment variable ACM_CERTIFICATE_ROOT_DOMAIN is too long. "+ + "The domain must be %d characters or shorter to allow for "+ + "subdomain randomization in the testing.", acmRandomSubDomainRemainderLen) + } + + return rootDomain +} + +// ACM domain names cannot be longer than 64 characters +// Other resources, e.g. Cognito User Pool Domains, limit this to 63 +func ACMCertificateRandomSubDomain(rootDomain string) string { + return fmt.Sprintf( + acmRandomSubDomainPrefix+"%s.%s", + sdkacctest.RandString(acmRandomSubDomainRemainderLen-len(rootDomain)), + rootDomain) +} + +func CheckACMPCACertificateAuthorityActivateRootCA(certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn + + if v := aws.StringValue(certificateAuthority.Type); v != acmpca.CertificateAuthorityTypeRoot { + return fmt.Errorf("attempting to activate ACM PCA %s Certificate Authority", v) + } + + arn := aws.StringValue(certificateAuthority.Arn) + + getCsrOutput, err := conn.GetCertificateAuthorityCsr(&acmpca.GetCertificateAuthorityCsrInput{ + CertificateAuthorityArn: aws.String(arn), + }) + + if err != nil { + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) CSR: %w", arn, err) + } + + issueCertOutput, err := conn.IssueCertificate(&acmpca.IssueCertificateInput{ + CertificateAuthorityArn: aws.String(arn), + Csr: []byte(aws.StringValue(getCsrOutput.Csr)), + IdempotencyToken: aws.String(resource.UniqueId()), + SigningAlgorithm: certificateAuthority.CertificateAuthorityConfiguration.SigningAlgorithm, + TemplateArn: aws.String(fmt.Sprintf("arn:%s:acm-pca:::template/RootCACertificate/V1", Partition())), + Validity: &acmpca.Validity{ + Type: aws.String(acmpca.ValidityPeriodTypeYears), + Value: aws.Int64(10), + }, + }) + + if err != nil { + return fmt.Errorf("error issuing ACM PCA Certificate Authority (%s) Root CA certificate from CSR: %w", arn, err) + } + + // Wait for certificate status to become ISSUED. + err = conn.WaitUntilCertificateIssued(&acmpca.GetCertificateInput{ + CertificateAuthorityArn: aws.String(arn), + CertificateArn: issueCertOutput.CertificateArn, + }) + + if err != nil { + return fmt.Errorf("error waiting for ACM PCA Certificate Authority (%s) Root CA certificate to become ISSUED: %w", arn, err) + } + + getCertOutput, err := conn.GetCertificate(&acmpca.GetCertificateInput{ + CertificateAuthorityArn: aws.String(arn), + CertificateArn: issueCertOutput.CertificateArn, + }) + + if err != nil { + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) issued Root CA certificate: %w", arn, err) + } + + _, err = conn.ImportCertificateAuthorityCertificate(&acmpca.ImportCertificateAuthorityCertificateInput{ + CertificateAuthorityArn: aws.String(arn), + Certificate: []byte(aws.StringValue(getCertOutput.Certificate)), + }) + + if err != nil { + return fmt.Errorf("error importing ACM PCA Certificate Authority (%s) Root CA certificate: %w", arn, err) + } + + return err + } +} + +func CheckACMPCACertificateAuthorityActivateSubordinateCA(rootCertificateAuthority, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn + + if v := aws.StringValue(certificateAuthority.Type); v != acmpca.CertificateAuthorityTypeSubordinate { + return fmt.Errorf("attempting to activate ACM PCA %s Certificate Authority", v) + } + + arn := aws.StringValue(certificateAuthority.Arn) + + getCsrOutput, err := conn.GetCertificateAuthorityCsr(&acmpca.GetCertificateAuthorityCsrInput{ + CertificateAuthorityArn: aws.String(arn), + }) + + if err != nil { + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) CSR: %w", arn, err) + } + + rootCertificateAuthorityArn := aws.StringValue(rootCertificateAuthority.Arn) + + issueCertOutput, err := conn.IssueCertificate(&acmpca.IssueCertificateInput{ + CertificateAuthorityArn: aws.String(rootCertificateAuthorityArn), + Csr: []byte(aws.StringValue(getCsrOutput.Csr)), + IdempotencyToken: aws.String(resource.UniqueId()), + SigningAlgorithm: certificateAuthority.CertificateAuthorityConfiguration.SigningAlgorithm, + TemplateArn: aws.String(fmt.Sprintf("arn:%s:acm-pca:::template/SubordinateCACertificate_PathLen0/V1", Partition())), + Validity: &acmpca.Validity{ + Type: aws.String(acmpca.ValidityPeriodTypeYears), + Value: aws.Int64(3), + }, + }) + + if err != nil { + return fmt.Errorf("error issuing ACM PCA Certificate Authority (%s) Subordinate CA certificate from CSR: %w", arn, err) + } + + // Wait for certificate status to become ISSUED. + err = conn.WaitUntilCertificateIssued(&acmpca.GetCertificateInput{ + CertificateAuthorityArn: aws.String(rootCertificateAuthorityArn), + CertificateArn: issueCertOutput.CertificateArn, + }) + + if err != nil { + return fmt.Errorf("error waiting for ACM PCA Certificate Authority (%s) Subordinate CA certificate to become ISSUED: %w", arn, err) + } + + getCertOutput, err := conn.GetCertificate(&acmpca.GetCertificateInput{ + CertificateAuthorityArn: aws.String(rootCertificateAuthorityArn), + CertificateArn: issueCertOutput.CertificateArn, + }) + + if err != nil { + return fmt.Errorf("error getting ACM PCA Certificate Authority (%s) issued Subordinate CA certificate: %w", arn, err) + } + + _, err = conn.ImportCertificateAuthorityCertificate(&acmpca.ImportCertificateAuthorityCertificateInput{ + CertificateAuthorityArn: aws.String(arn), + Certificate: []byte(aws.StringValue(getCertOutput.Certificate)), + CertificateChain: []byte(aws.StringValue(getCertOutput.CertificateChain)), + }) + + if err != nil { + return fmt.Errorf("error importing ACM PCA Certificate Authority (%s) Subordinate CA certificate: %w", arn, err) + } + + return err + } +} + +func CheckACMPCACertificateAuthorityDisableCA(certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn + + _, err := conn.UpdateCertificateAuthority(&acmpca.UpdateCertificateAuthorityInput{ + CertificateAuthorityArn: certificateAuthority.Arn, + Status: aws.String(acmpca.CertificateAuthorityStatusDisabled), + }) + + return err + } +} + +func CheckACMPCACertificateAuthorityExists(n string, certificateAuthority *acmpca.CertificateAuthority) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("no ACM PCA Certificate Authority ID is set") + } + + conn := Provider.Meta().(*conns.AWSClient).ACMPCAConn + + input := &acmpca.DescribeCertificateAuthorityInput{ + CertificateAuthorityArn: aws.String(rs.Primary.ID), + } + + output, err := conn.DescribeCertificateAuthority(input) + + if err != nil { + return err + } + + if output == nil || output.CertificateAuthority == nil { + return fmt.Errorf("empty ACM PCA Certificate Authority (%s)", rs.Primary.ID) + } + + *certificateAuthority = *output.CertificateAuthority + + return nil + } +} + +// PreCheckAPIGatewayTypeEDGE checks if endpoint config type EDGE can be used in a test and skips test if not (i.e., not in standard partition). +func PreCheckAPIGatewayTypeEDGE(t *testing.T) { + if Partition() != endpoints.AwsPartitionID { + t.Skipf("skipping test; Endpoint Configuration type EDGE is not supported in this partition (%s)", Partition()) + } +} + +func PreCheckDirectoryService(t *testing.T) { + conn := Provider.Meta().(*conns.AWSClient).DSConn + + input := &directoryservice.DescribeDirectoriesInput{} + + _, err := conn.DescribeDirectories(input) + + if PreCheckSkipError(err) { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +// Certain regions such as AWS GovCloud (US) do not support Simple AD directories +// and we do not have a good read-only way to determine this situation. Here we +// opt to perform a creation that will fail so we can determine Simple AD support. +func PreCheckDirectoryServiceSimpleDirectory(t *testing.T) { + conn := Provider.Meta().(*conns.AWSClient).DSConn + + input := &directoryservice.CreateDirectoryInput{ + Name: aws.String("corp.example.com"), + Password: aws.String("PreCheck123"), + Size: aws.String(directoryservice.DirectorySizeSmall), + } + + _, err := conn.CreateDirectory(input) + + if tfawserr.ErrMessageContains(err, directoryservice.ErrCodeClientException, "Simple AD directory creation is currently not supported in this region") { + t.Skipf("skipping acceptance testing: %s", err) + } + + if err != nil && !tfawserr.ErrMessageContains(err, directoryservice.ErrCodeInvalidParameterException, "VpcSettings must be specified") { + t.Fatalf("unexpected PreCheck error: %s", err) + } +} + +func ConfigAvailableAZsNoOptIn() string { + return ` +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} +` +} + +func ConfigAvailableAZsNoOptInDefaultExclude() string { + // Exclude usw2-az4 (us-west-2d) as it has limited instance types. + return ConfigAvailableAZsNoOptInExclude("usw2-az4", "usgw1-az2") +} + +func ConfigAvailableAZsNoOptInExclude(excludeZoneIds ...string) string { + return fmt.Sprintf(` +data "aws_availability_zones" "available" { + exclude_zone_ids = ["%[1]s"] + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} +`, strings.Join(excludeZoneIds, "\", \"")) +} + +// AvailableEC2InstanceTypeForAvailabilityZone returns the configuration for a data source that describes +// the first available EC2 instance type offering in the specified availability zone from a list of preferred instance types. +// The first argument is either an Availability Zone name or Terraform configuration reference to one, e.g. +// * data.aws_availability_zones.available.names[0] +// * aws_subnet.test.availability_zone +// * us-west-2a +// The data source is named 'available'. +func AvailableEC2InstanceTypeForAvailabilityZone(availabilityZoneName string, preferredInstanceTypes ...string) string { + if !strings.Contains(availabilityZoneName, ".") { + availabilityZoneName = strconv.Quote(availabilityZoneName) + } + + return fmt.Sprintf(` +data "aws_ec2_instance_type_offering" "available" { + filter { + name = "instance-type" + values = ["%[2]s"] + } + + filter { + name = "location" + values = [%[1]s] + } + + location_type = "availability-zone" + preferred_instance_types = ["%[2]s"] +} +`, availabilityZoneName, strings.Join(preferredInstanceTypes, "\", \"")) +} + +// AvailableEC2InstanceTypeForRegion returns the configuration for a data source that describes +// the first available EC2 instance type offering in the current region from a list of preferred instance types. +// The data source is named 'available'. +func AvailableEC2InstanceTypeForRegion(preferredInstanceTypes ...string) string { + return AvailableEC2InstanceTypeForRegionNamed("available", preferredInstanceTypes...) +} + +// AvailableEC2InstanceTypeForRegionNamed returns the configuration for a data source that describes +// the first available EC2 instance type offering in the current region from a list of preferred instance types. +// The data source name is configurable. +func AvailableEC2InstanceTypeForRegionNamed(name string, preferredInstanceTypes ...string) string { + return fmt.Sprintf(` +data "aws_ec2_instance_type_offering" "%[1]s" { + filter { + name = "instance-type" + values = ["%[2]s"] + } + + preferred_instance_types = ["%[2]s"] +} +`, name, strings.Join(preferredInstanceTypes, "\", \"")) +} + +// ConfigLatestAmazonLinuxHVMEBSAMI returns the configuration for a data source that +// describes the latest Amazon Linux AMI using HVM virtualization and an EBS root device. +// The data source is named 'amzn-ami-minimal-hvm-ebs'. +func ConfigLatestAmazonLinuxHVMEBSAMI() string { + return ` +data "aws_ami" "amzn-ami-minimal-hvm-ebs" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn-ami-minimal-hvm-*"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } +} +` +} + +func configLatestAmazonLinux2HVMEBSAMI(architecture string) string { + return fmt.Sprintf(` +data "aws_ami" "amzn2-ami-minimal-hvm-ebs-%[1]s" { + most_recent = true + owners = ["amazon"] + + filter { + name = "name" + values = ["amzn2-ami-minimal-hvm-*"] + } + + filter { + name = "root-device-type" + values = ["ebs"] + } + + filter { + name = "architecture" + values = [%[1]q] + } +} +`, architecture) +} + +// ConfigLatestAmazonLinux2HVMEBSX8664AMI returns the configuration for a data source that +// describes the latest Amazon Linux 2 x86_64 AMI using HVM virtualization and an EBS root device. +// The data source is named 'amzn2-ami-minimal-hvm-ebs-x86_64'. +func ConfigLatestAmazonLinux2HVMEBSX8664AMI() string { + return configLatestAmazonLinux2HVMEBSAMI(ec2.ArchitectureValuesX8664) +} + +// ConfigLatestAmazonLinux2HVMEBSARM64AMI returns the configuration for a data source that +// describes the latest Amazon Linux 2 arm64 AMI using HVM virtualization and an EBS root device. +// The data source is named 'amzn2-ami-minimal-hvm-ebs-arm64'. +func ConfigLatestAmazonLinux2HVMEBSARM64AMI() string { + return configLatestAmazonLinux2HVMEBSAMI(ec2.ArchitectureValuesArm64) +} + +func ConfigLambdaBase(policyName, roleName, sgName string) string { + return fmt.Sprintf(` +data "aws_partition" "current" {} + +data "aws_availability_zones" "available" { + state = "available" + + filter { + name = "opt-in-status" + values = ["opt-in-not-required"] + } +} + +resource "aws_iam_role_policy" "iam_policy_for_lambda" { + name = "%s" + role = aws_iam_role.iam_for_lambda.id + + policy = < value) { + if !ok { + return fmt.Errorf("%s: Attribute %q not found", n, key) + } + + return fmt.Errorf("%s: Attribute %q is not greater than %q, got %q", n, key, value, v) + } + + return nil + + } +} diff --git a/internal/acctest/crypto.go b/internal/acctest/crypto.go new file mode 100644 index 0000000..02cd810 --- /dev/null +++ b/internal/acctest/crypto.go @@ -0,0 +1,346 @@ +package acctest + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha1" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "strings" + "time" +) + +const ( + pemBlockTypeCertificate = `CERTIFICATE` + pemBlockTypeRsaPrivateKey = `RSA PRIVATE KEY` + pemBlockTypePublicKey = `PUBLIC KEY` + pemBlockTypeCertificateRequest = `CERTIFICATE REQUEST` +) + +var tlsX509CertificateSerialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) //nolint:gomnd + +// TLSRSAPrivateKeyPEM generates a RSA private key PEM string. +// Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() +// configurations such as: private_key_pem = "%[1]s" +func TLSRSAPrivateKeyPEM(bits int) string { + key, err := rsa.GenerateKey(rand.Reader, bits) + + if err != nil { + //lintignore:R009 + panic(err) + } + + block := &pem.Block{ + Bytes: x509.MarshalPKCS1PrivateKey(key), + Type: pemBlockTypeRsaPrivateKey, + } + + return string(pem.EncodeToMemory(block)) +} + +// TLSRSAPublicKeyPEM generates a RSA public key PEM string. +// Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() +// configurations such as: public_key_pem = "%[1]s" +func TLSRSAPublicKeyPEM(keyPem string) string { + keyBlock, _ := pem.Decode([]byte(keyPem)) + + key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + + if err != nil { + //lintignore:R009 + panic(err) + } + + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + + if err != nil { + //lintignore:R009 + panic(err) + } + + block := &pem.Block{ + Bytes: publicKeyBytes, + Type: pemBlockTypePublicKey, + } + + return string(pem.EncodeToMemory(block)) +} + +// TLSRSAX509LocallySignedCertificatePEM generates a local CA x509 certificate PEM string. +// Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() +// configurations such as: certificate_pem = "%[1]s" +func TLSRSAX509LocallySignedCertificatePEM(caKeyPem, caCertificatePem, keyPem, commonName string) string { + caCertificateBlock, _ := pem.Decode([]byte(caCertificatePem)) + + caCertificate, err := x509.ParseCertificate(caCertificateBlock.Bytes) + + if err != nil { + //lintignore:R009 + panic(err) + } + + caKeyBlock, _ := pem.Decode([]byte(caKeyPem)) + + caKey, err := x509.ParsePKCS1PrivateKey(caKeyBlock.Bytes) + + if err != nil { + //lintignore:R009 + panic(err) + } + + keyBlock, _ := pem.Decode([]byte(keyPem)) + + key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + + if err != nil { + //lintignore:R009 + panic(err) + } + + serialNumber, err := rand.Int(rand.Reader, tlsX509CertificateSerialNumberLimit) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificate := &x509.Certificate{ + BasicConstraintsValid: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + NotAfter: time.Now().Add(24 * time.Hour), //nolint:gomnd + NotBefore: time.Now(), + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: commonName, + Organization: []string{"ACME Examples, Inc"}, + }, + } + + certificateBytes, err := x509.CreateCertificate(rand.Reader, certificate, caCertificate, &key.PublicKey, caKey) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificateBlock := &pem.Block{ + Bytes: certificateBytes, + Type: pemBlockTypeCertificate, + } + + return string(pem.EncodeToMemory(certificateBlock)) +} + +// TLSRSAX509SelfSignedCACertificatePEM generates a x509 CA certificate PEM string. +// Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() +// configurations such as: root_certificate_pem = "%[1]s" +func TLSRSAX509SelfSignedCACertificatePEM(keyPem string) string { + keyBlock, _ := pem.Decode([]byte(keyPem)) + + key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + + if err != nil { + //lintignore:R009 + panic(err) + } + + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + + if err != nil { + //lintignore:R009 + panic(err) + } + + publicKeyBytesSha1 := sha1.Sum(publicKeyBytes) + + serialNumber, err := rand.Int(rand.Reader, tlsX509CertificateSerialNumberLimit) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificate := &x509.Certificate{ + BasicConstraintsValid: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IsCA: true, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + NotAfter: time.Now().Add(24 * time.Hour), //nolint:gomnd + NotBefore: time.Now(), + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: "ACME Root CA", + Organization: []string{"ACME Examples, Inc"}, + }, + SubjectKeyId: publicKeyBytesSha1[:], + } + + certificateBytes, err := x509.CreateCertificate(rand.Reader, certificate, certificate, &key.PublicKey, key) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificateBlock := &pem.Block{ + Bytes: certificateBytes, + Type: pemBlockTypeCertificate, + } + + return string(pem.EncodeToMemory(certificateBlock)) +} + +// TLSRSAX509SelfSignedCACertificateForRolesAnywhereTrustAnchorPEM generates a x509 CA certificate PEM string. +// The CA certificate is suitable for use as an IAM RolesAnywhere Trust Anchor. +// See https://docs.aws.amazon.com/rolesanywhere/latest/userguide/trust-model.html#signature-verification. +// Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() +// configurations such as: root_certificate_pem = "%[1]s" +func TLSRSAX509SelfSignedCACertificateForRolesAnywhereTrustAnchorPEM(keyPem string) string { + keyBlock, _ := pem.Decode([]byte(keyPem)) + + key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + + if err != nil { + //lintignore:R009 + panic(err) + } + + publicKeyBytes, err := x509.MarshalPKIXPublicKey(&key.PublicKey) + + if err != nil { + //lintignore:R009 + panic(err) + } + + publicKeyBytesSha1 := sha1.Sum(publicKeyBytes) + + serialNumber, err := rand.Int(rand.Reader, tlsX509CertificateSerialNumberLimit) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificate := &x509.Certificate{ + BasicConstraintsValid: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + IsCA: true, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + NotAfter: time.Now().Add(24 * time.Hour), //nolint:gomnd + NotBefore: time.Now(), + SerialNumber: serialNumber, + SignatureAlgorithm: x509.SHA256WithRSA, + Subject: pkix.Name{ + CommonName: "ACME Root CA", + Organization: []string{"ACME Examples, Inc"}, + }, + SubjectKeyId: publicKeyBytesSha1[:], + } + + certificateBytes, err := x509.CreateCertificate(rand.Reader, certificate, certificate, &key.PublicKey, key) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificateBlock := &pem.Block{ + Bytes: certificateBytes, + Type: pemBlockTypeCertificate, + } + + return string(pem.EncodeToMemory(certificateBlock)) +} + +// TLSRSAX509SelfSignedCertificatePEM generates a x509 certificate PEM string. +// Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() +// configurations such as: private_key_pem = "%[1]s" +func TLSRSAX509SelfSignedCertificatePEM(keyPem, commonName string) string { + keyBlock, _ := pem.Decode([]byte(keyPem)) + + key, err := x509.ParsePKCS1PrivateKey(keyBlock.Bytes) + + if err != nil { + //lintignore:R009 + panic(err) + } + + serialNumber, err := rand.Int(rand.Reader, tlsX509CertificateSerialNumberLimit) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificate := &x509.Certificate{ + BasicConstraintsValid: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + NotAfter: time.Now().Add(24 * time.Hour), //nolint:gomnd + NotBefore: time.Now(), + SerialNumber: serialNumber, + Subject: pkix.Name{ + CommonName: commonName, + Organization: []string{"ACME Examples, Inc"}, + }, + } + + certificateBytes, err := x509.CreateCertificate(rand.Reader, certificate, certificate, &key.PublicKey, key) + + if err != nil { + //lintignore:R009 + panic(err) + } + + certificateBlock := &pem.Block{ + Bytes: certificateBytes, + Type: pemBlockTypeCertificate, + } + + return string(pem.EncodeToMemory(certificateBlock)) +} + +// TLSRSAX509CertificateRequestPEM generates a x509 certificate request PEM string +// and a RSA private key PEM string. +// Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() +// configurations such as: certificate_signing_request_pem = "%[1]s" private_key_pem = "%[2]s" +func TLSRSAX509CertificateRequestPEM(keyBits int, commonName string) (string, string) { + keyBytes, err := rsa.GenerateKey(rand.Reader, keyBits) + if err != nil { + //lintignore:R009 + panic(err) + } + + csr := x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: commonName, + Organization: []string{"ACME Examples, Inc"}, + }, + SignatureAlgorithm: x509.SHA256WithRSA, + } + + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &csr, keyBytes) + if err != nil { + //lintignore:R009 + panic(err) + } + + csrBlock := &pem.Block{ + Bytes: csrBytes, + Type: pemBlockTypeCertificateRequest, + } + + keyBlock := &pem.Block{ + Bytes: x509.MarshalPKCS1PrivateKey(keyBytes), + Type: pemBlockTypeRsaPrivateKey, + } + + return string(pem.EncodeToMemory(csrBlock)), string(pem.EncodeToMemory(keyBlock)) +} + +func TLSPEMEscapeNewlines(pem string) string { + return strings.ReplaceAll(pem, "\n", "\\n") +} diff --git a/internal/acctest/ec2_classic.go b/internal/acctest/ec2_classic.go new file mode 100644 index 0000000..1719c2c --- /dev/null +++ b/internal/acctest/ec2_classic.go @@ -0,0 +1,101 @@ +package acctest + +import ( + "context" + "os" + "sync" + "testing" + + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/cloudposse/terraform-provider-awsutils/internal/provider" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +const ( + // EC2-Classic region testing environment variable name + ec2ClassicRegionEnvVar = "AWS_EC2_CLASSIC_REGION" +) + +// ProviderEC2Classic is the EC2-Classic provider instance +// +// This Provider can be used in testing code for API calls without requiring +// the use of saving and referencing specific ProviderFactories instances. +// +// PreCheckEC2Classic(t) must be called before using this provider instance. +var ProviderEC2Classic *schema.Provider + +// testAccProviderEc2ClassicConfigure ensures the provider is only configured once +var testAccProviderEc2ClassicConfigure sync.Once + +// PreCheckEC2Classic verifies AWS credentials and that EC2-Classic is supported +func PreCheckEC2Classic(t *testing.T) { + // Since we are outside the scope of the Terraform configuration we must + // call Configure() to properly initialize the provider configuration. + testAccProviderEc2ClassicConfigure.Do(func() { + ctx := context.Background() + var err error + ProviderEC2Classic, err = provider.New(ctx) + + if err != nil { + t.Fatal(err) + } + + config := map[string]interface{}{ + "region": EC2ClassicRegion(), + } + + diags := ProviderEC2Classic.Configure(ctx, terraform.NewResourceConfigRaw(config)) + + if diags.HasError() { + t.Fatal(diags) + } + }) + + client := ProviderEC2Classic.Meta().(*conns.AWSClient) + platforms := client.SupportedPlatforms + region := client.Region + if !conns.HasEC2Classic(platforms) { + t.Skipf("this test can only run in EC2-Classic, platforms available in %s: %q", region, platforms) + } +} + +// ConfigEC2ClassicRegionProvider is the Terraform provider configuration for EC2-Classic region testing +// +// Testing EC2-Classic assumes no other provider configurations are necessary +// and overwrites the "aws" provider configuration. +func ConfigEC2ClassicRegionProvider() string { + return ConfigRegionalProvider(EC2ClassicRegion()) +} + +// EC2ClassicRegion returns the EC2-Classic region for testing +func EC2ClassicRegion() string { + v := os.Getenv(ec2ClassicRegionEnvVar) + + if v != "" { + return v + } + + if Partition() == endpoints.AwsPartitionID { + return endpoints.UsEast1RegionID + } + + return Region() +} + +// CheckResourceAttrRegionalARNEC2Classic ensures the Terraform state exactly matches a formatted ARN with EC2-Classic region +func CheckResourceAttrRegionalARNEC2Classic(resourceName, attributeName, arnService, arnResource string) resource.TestCheckFunc { + return func(s *terraform.State) error { + attributeValue := arn.ARN{ + AccountID: AccountID(), + Partition: Partition(), + Region: EC2ClassicRegion(), + Resource: arnResource, + Service: arnService, + }.String() + return resource.TestCheckResourceAttr(resourceName, attributeName, attributeValue)(s) + } +} diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go new file mode 100644 index 0000000..4943241 --- /dev/null +++ b/internal/conns/awsclient_gen.go @@ -0,0 +1,643 @@ +// Code generated by internal/generate/awsclient/main.go; DO NOT EDIT. +package conns + +import ( + "fmt" + + "github.com/aws/aws-sdk-go-v2/service/fis" + "github.com/aws/aws-sdk-go-v2/service/kendra" + "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" + "github.com/aws/aws-sdk-go-v2/service/route53domains" + "github.com/aws/aws-sdk-go-v2/service/transcribe" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/accessanalyzer" + "github.com/aws/aws-sdk-go/service/account" + "github.com/aws/aws-sdk-go/service/acm" + "github.com/aws/aws-sdk-go/service/acmpca" + "github.com/aws/aws-sdk-go/service/alexaforbusiness" + "github.com/aws/aws-sdk-go/service/amplify" + "github.com/aws/aws-sdk-go/service/amplifybackend" + "github.com/aws/aws-sdk-go/service/amplifyuibuilder" + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/aws/aws-sdk-go/service/apigatewaymanagementapi" + "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/aws/aws-sdk-go/service/appconfigdata" + "github.com/aws/aws-sdk-go/service/appflow" + "github.com/aws/aws-sdk-go/service/appintegrationsservice" + "github.com/aws/aws-sdk-go/service/applicationautoscaling" + "github.com/aws/aws-sdk-go/service/applicationcostprofiler" + "github.com/aws/aws-sdk-go/service/applicationdiscoveryservice" + "github.com/aws/aws-sdk-go/service/applicationinsights" + "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/aws/aws-sdk-go/service/appregistry" + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/aws/aws-sdk-go/service/appstream" + "github.com/aws/aws-sdk-go/service/appsync" + "github.com/aws/aws-sdk-go/service/athena" + "github.com/aws/aws-sdk-go/service/auditmanager" + "github.com/aws/aws-sdk-go/service/augmentedairuntime" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/autoscalingplans" + "github.com/aws/aws-sdk-go/service/backup" + "github.com/aws/aws-sdk-go/service/backupgateway" + "github.com/aws/aws-sdk-go/service/batch" + "github.com/aws/aws-sdk-go/service/billingconductor" + "github.com/aws/aws-sdk-go/service/braket" + "github.com/aws/aws-sdk-go/service/budgets" + "github.com/aws/aws-sdk-go/service/chime" + "github.com/aws/aws-sdk-go/service/chimesdkidentity" + "github.com/aws/aws-sdk-go/service/chimesdkmeetings" + "github.com/aws/aws-sdk-go/service/chimesdkmessaging" + "github.com/aws/aws-sdk-go/service/cloud9" + "github.com/aws/aws-sdk-go/service/cloudcontrolapi" + "github.com/aws/aws-sdk-go/service/clouddirectory" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/service/cloudfront" + "github.com/aws/aws-sdk-go/service/cloudhsmv2" + "github.com/aws/aws-sdk-go/service/cloudsearch" + "github.com/aws/aws-sdk-go/service/cloudsearchdomain" + "github.com/aws/aws-sdk-go/service/cloudtrail" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go/service/cloudwatchevidently" + "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + "github.com/aws/aws-sdk-go/service/cloudwatchrum" + "github.com/aws/aws-sdk-go/service/codeartifact" + "github.com/aws/aws-sdk-go/service/codebuild" + "github.com/aws/aws-sdk-go/service/codecommit" + "github.com/aws/aws-sdk-go/service/codedeploy" + "github.com/aws/aws-sdk-go/service/codeguruprofiler" + "github.com/aws/aws-sdk-go/service/codegurureviewer" + "github.com/aws/aws-sdk-go/service/codepipeline" + "github.com/aws/aws-sdk-go/service/codestar" + "github.com/aws/aws-sdk-go/service/codestarconnections" + "github.com/aws/aws-sdk-go/service/codestarnotifications" + "github.com/aws/aws-sdk-go/service/cognitoidentity" + "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" + "github.com/aws/aws-sdk-go/service/cognitosync" + "github.com/aws/aws-sdk-go/service/comprehend" + "github.com/aws/aws-sdk-go/service/comprehendmedical" + "github.com/aws/aws-sdk-go/service/computeoptimizer" + "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go/service/connect" + "github.com/aws/aws-sdk-go/service/connectcontactlens" + "github.com/aws/aws-sdk-go/service/connectparticipant" + "github.com/aws/aws-sdk-go/service/connectwisdomservice" + "github.com/aws/aws-sdk-go/service/costandusagereportservice" + "github.com/aws/aws-sdk-go/service/costexplorer" + "github.com/aws/aws-sdk-go/service/customerprofiles" + "github.com/aws/aws-sdk-go/service/databasemigrationservice" + "github.com/aws/aws-sdk-go/service/dataexchange" + "github.com/aws/aws-sdk-go/service/datapipeline" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/aws/aws-sdk-go/service/dax" + "github.com/aws/aws-sdk-go/service/detective" + "github.com/aws/aws-sdk-go/service/devicefarm" + "github.com/aws/aws-sdk-go/service/devopsguru" + "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/aws/aws-sdk-go/service/directoryservice" + "github.com/aws/aws-sdk-go/service/dlm" + "github.com/aws/aws-sdk-go/service/docdb" + "github.com/aws/aws-sdk-go/service/drs" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodbstreams" + "github.com/aws/aws-sdk-go/service/ebs" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2instanceconnect" + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/aws/aws-sdk-go/service/ecrpublic" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/efs" + "github.com/aws/aws-sdk-go/service/eks" + "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go/service/elasticbeanstalk" + "github.com/aws/aws-sdk-go/service/elasticinference" + "github.com/aws/aws-sdk-go/service/elasticsearchservice" + "github.com/aws/aws-sdk-go/service/elastictranscoder" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/aws/aws-sdk-go/service/emr" + "github.com/aws/aws-sdk-go/service/emrcontainers" + "github.com/aws/aws-sdk-go/service/emrserverless" + "github.com/aws/aws-sdk-go/service/eventbridge" + "github.com/aws/aws-sdk-go/service/finspace" + "github.com/aws/aws-sdk-go/service/finspacedata" + "github.com/aws/aws-sdk-go/service/firehose" + "github.com/aws/aws-sdk-go/service/fms" + "github.com/aws/aws-sdk-go/service/forecastqueryservice" + "github.com/aws/aws-sdk-go/service/forecastservice" + "github.com/aws/aws-sdk-go/service/frauddetector" + "github.com/aws/aws-sdk-go/service/fsx" + "github.com/aws/aws-sdk-go/service/gamelift" + "github.com/aws/aws-sdk-go/service/glacier" + "github.com/aws/aws-sdk-go/service/globalaccelerator" + "github.com/aws/aws-sdk-go/service/glue" + "github.com/aws/aws-sdk-go/service/gluedatabrew" + "github.com/aws/aws-sdk-go/service/greengrass" + "github.com/aws/aws-sdk-go/service/greengrassv2" + "github.com/aws/aws-sdk-go/service/groundstation" + "github.com/aws/aws-sdk-go/service/guardduty" + "github.com/aws/aws-sdk-go/service/health" + "github.com/aws/aws-sdk-go/service/healthlake" + "github.com/aws/aws-sdk-go/service/honeycode" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go/service/identitystore" + "github.com/aws/aws-sdk-go/service/imagebuilder" + "github.com/aws/aws-sdk-go/service/inspector" + "github.com/aws/aws-sdk-go/service/inspector2" + "github.com/aws/aws-sdk-go/service/iot" + "github.com/aws/aws-sdk-go/service/iot1clickdevicesservice" + "github.com/aws/aws-sdk-go/service/iot1clickprojects" + "github.com/aws/aws-sdk-go/service/iotanalytics" + "github.com/aws/aws-sdk-go/service/iotdataplane" + "github.com/aws/aws-sdk-go/service/iotdeviceadvisor" + "github.com/aws/aws-sdk-go/service/iotevents" + "github.com/aws/aws-sdk-go/service/ioteventsdata" + "github.com/aws/aws-sdk-go/service/iotfleethub" + "github.com/aws/aws-sdk-go/service/iotjobsdataplane" + "github.com/aws/aws-sdk-go/service/iotsecuretunneling" + "github.com/aws/aws-sdk-go/service/iotsitewise" + "github.com/aws/aws-sdk-go/service/iotthingsgraph" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + "github.com/aws/aws-sdk-go/service/iotwireless" + "github.com/aws/aws-sdk-go/service/ivs" + "github.com/aws/aws-sdk-go/service/kafka" + "github.com/aws/aws-sdk-go/service/kafkaconnect" + "github.com/aws/aws-sdk-go/service/keyspaces" + "github.com/aws/aws-sdk-go/service/kinesis" + "github.com/aws/aws-sdk-go/service/kinesisanalytics" + "github.com/aws/aws-sdk-go/service/kinesisanalyticsv2" + "github.com/aws/aws-sdk-go/service/kinesisvideo" + "github.com/aws/aws-sdk-go/service/kinesisvideoarchivedmedia" + "github.com/aws/aws-sdk-go/service/kinesisvideomedia" + "github.com/aws/aws-sdk-go/service/kinesisvideosignalingchannels" + "github.com/aws/aws-sdk-go/service/kms" + "github.com/aws/aws-sdk-go/service/lakeformation" + "github.com/aws/aws-sdk-go/service/lambda" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" + "github.com/aws/aws-sdk-go/service/lexmodelsv2" + "github.com/aws/aws-sdk-go/service/lexruntimeservice" + "github.com/aws/aws-sdk-go/service/lexruntimev2" + "github.com/aws/aws-sdk-go/service/licensemanager" + "github.com/aws/aws-sdk-go/service/lightsail" + "github.com/aws/aws-sdk-go/service/locationservice" + "github.com/aws/aws-sdk-go/service/lookoutequipment" + "github.com/aws/aws-sdk-go/service/lookoutforvision" + "github.com/aws/aws-sdk-go/service/lookoutmetrics" + "github.com/aws/aws-sdk-go/service/machinelearning" + "github.com/aws/aws-sdk-go/service/macie" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/aws/aws-sdk-go/service/managedblockchain" + "github.com/aws/aws-sdk-go/service/managedgrafana" + "github.com/aws/aws-sdk-go/service/marketplacecatalog" + "github.com/aws/aws-sdk-go/service/marketplacecommerceanalytics" + "github.com/aws/aws-sdk-go/service/marketplaceentitlementservice" + "github.com/aws/aws-sdk-go/service/marketplacemetering" + "github.com/aws/aws-sdk-go/service/mediaconnect" + "github.com/aws/aws-sdk-go/service/mediaconvert" + "github.com/aws/aws-sdk-go/service/medialive" + "github.com/aws/aws-sdk-go/service/mediapackage" + "github.com/aws/aws-sdk-go/service/mediapackagevod" + "github.com/aws/aws-sdk-go/service/mediastore" + "github.com/aws/aws-sdk-go/service/mediastoredata" + "github.com/aws/aws-sdk-go/service/mediatailor" + "github.com/aws/aws-sdk-go/service/memorydb" + "github.com/aws/aws-sdk-go/service/mgn" + "github.com/aws/aws-sdk-go/service/migrationhub" + "github.com/aws/aws-sdk-go/service/migrationhubconfig" + "github.com/aws/aws-sdk-go/service/migrationhubrefactorspaces" + "github.com/aws/aws-sdk-go/service/migrationhubstrategyrecommendations" + "github.com/aws/aws-sdk-go/service/mobile" + "github.com/aws/aws-sdk-go/service/mq" + "github.com/aws/aws-sdk-go/service/mturk" + "github.com/aws/aws-sdk-go/service/mwaa" + "github.com/aws/aws-sdk-go/service/neptune" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/aws/aws-sdk-go/service/nimblestudio" + "github.com/aws/aws-sdk-go/service/opensearchservice" + "github.com/aws/aws-sdk-go/service/opsworks" + "github.com/aws/aws-sdk-go/service/opsworkscm" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/aws/aws-sdk-go/service/outposts" + "github.com/aws/aws-sdk-go/service/panorama" + "github.com/aws/aws-sdk-go/service/personalize" + "github.com/aws/aws-sdk-go/service/personalizeevents" + "github.com/aws/aws-sdk-go/service/personalizeruntime" + "github.com/aws/aws-sdk-go/service/pi" + "github.com/aws/aws-sdk-go/service/pinpoint" + "github.com/aws/aws-sdk-go/service/pinpointemail" + "github.com/aws/aws-sdk-go/service/pinpointsmsvoice" + "github.com/aws/aws-sdk-go/service/polly" + "github.com/aws/aws-sdk-go/service/pricing" + "github.com/aws/aws-sdk-go/service/prometheusservice" + "github.com/aws/aws-sdk-go/service/proton" + "github.com/aws/aws-sdk-go/service/qldb" + "github.com/aws/aws-sdk-go/service/qldbsession" + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/aws/aws-sdk-go/service/ram" + "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go/service/rdsdataservice" + "github.com/aws/aws-sdk-go/service/recyclebin" + "github.com/aws/aws-sdk-go/service/redshift" + "github.com/aws/aws-sdk-go/service/redshiftdataapiservice" + "github.com/aws/aws-sdk-go/service/redshiftserverless" + "github.com/aws/aws-sdk-go/service/rekognition" + "github.com/aws/aws-sdk-go/service/resiliencehub" + "github.com/aws/aws-sdk-go/service/resourcegroups" + "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" + "github.com/aws/aws-sdk-go/service/robomaker" + "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go/service/route53recoverycluster" + "github.com/aws/aws-sdk-go/service/route53recoverycontrolconfig" + "github.com/aws/aws-sdk-go/service/route53recoveryreadiness" + "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3control" + "github.com/aws/aws-sdk-go/service/s3outposts" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/aws/aws-sdk-go/service/sagemakeredgemanager" + "github.com/aws/aws-sdk-go/service/sagemakerfeaturestoreruntime" + "github.com/aws/aws-sdk-go/service/sagemakerruntime" + "github.com/aws/aws-sdk-go/service/savingsplans" + "github.com/aws/aws-sdk-go/service/schemas" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/aws/aws-sdk-go/service/securityhub" + "github.com/aws/aws-sdk-go/service/serverlessapplicationrepository" + "github.com/aws/aws-sdk-go/service/servicecatalog" + "github.com/aws/aws-sdk-go/service/servicediscovery" + "github.com/aws/aws-sdk-go/service/servicequotas" + "github.com/aws/aws-sdk-go/service/ses" + "github.com/aws/aws-sdk-go/service/sesv2" + "github.com/aws/aws-sdk-go/service/sfn" + "github.com/aws/aws-sdk-go/service/shield" + "github.com/aws/aws-sdk-go/service/signer" + "github.com/aws/aws-sdk-go/service/simpledb" + "github.com/aws/aws-sdk-go/service/sms" + "github.com/aws/aws-sdk-go/service/snowball" + "github.com/aws/aws-sdk-go/service/snowdevicemanagement" + "github.com/aws/aws-sdk-go/service/sns" + "github.com/aws/aws-sdk-go/service/sqs" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go/service/ssmcontacts" + "github.com/aws/aws-sdk-go/service/ssmincidents" + "github.com/aws/aws-sdk-go/service/sso" + "github.com/aws/aws-sdk-go/service/ssoadmin" + "github.com/aws/aws-sdk-go/service/ssooidc" + "github.com/aws/aws-sdk-go/service/storagegateway" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/aws/aws-sdk-go/service/support" + "github.com/aws/aws-sdk-go/service/swf" + "github.com/aws/aws-sdk-go/service/synthetics" + "github.com/aws/aws-sdk-go/service/textract" + "github.com/aws/aws-sdk-go/service/timestreamquery" + "github.com/aws/aws-sdk-go/service/timestreamwrite" + "github.com/aws/aws-sdk-go/service/transcribestreamingservice" + "github.com/aws/aws-sdk-go/service/transfer" + "github.com/aws/aws-sdk-go/service/translate" + "github.com/aws/aws-sdk-go/service/voiceid" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/aws/aws-sdk-go/service/wafregional" + "github.com/aws/aws-sdk-go/service/wafv2" + "github.com/aws/aws-sdk-go/service/wellarchitected" + "github.com/aws/aws-sdk-go/service/workdocs" + "github.com/aws/aws-sdk-go/service/worklink" + "github.com/aws/aws-sdk-go/service/workmail" + "github.com/aws/aws-sdk-go/service/workmailmessageflow" + "github.com/aws/aws-sdk-go/service/workspaces" + "github.com/aws/aws-sdk-go/service/workspacesweb" + "github.com/aws/aws-sdk-go/service/xray" + tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" +) + +type AWSClient struct { + AccountID string + DefaultTagsConfig *tftags.DefaultConfig + DNSSuffix string + IgnoreTagsConfig *tftags.IgnoreConfig + MediaConvertAccountConn *mediaconvert.MediaConvert + Partition string + Region string + ReverseDNSPrefix string + S3ConnURICleaningDisabled *s3.S3 + Session *session.Session + SupportedPlatforms []string + TerraformVersion string + + ACMConn *acm.ACM + ACMPCAConn *acmpca.ACMPCA + AMPConn *prometheusservice.PrometheusService + APIGatewayConn *apigateway.APIGateway + APIGatewayManagementAPIConn *apigatewaymanagementapi.ApiGatewayManagementApi + APIGatewayV2Conn *apigatewayv2.ApiGatewayV2 + AccessAnalyzerConn *accessanalyzer.AccessAnalyzer + AccountConn *account.Account + AlexaForBusinessConn *alexaforbusiness.AlexaForBusiness + AmplifyConn *amplify.Amplify + AmplifyBackendConn *amplifybackend.AmplifyBackend + AmplifyUIBuilderConn *amplifyuibuilder.AmplifyUIBuilder + AppAutoScalingConn *applicationautoscaling.ApplicationAutoScaling + AppConfigConn *appconfig.AppConfig + AppConfigDataConn *appconfigdata.AppConfigData + AppFlowConn *appflow.Appflow + AppIntegrationsConn *appintegrationsservice.AppIntegrationsService + AppMeshConn *appmesh.AppMesh + AppRunnerConn *apprunner.AppRunner + AppStreamConn *appstream.AppStream + AppSyncConn *appsync.AppSync + ApplicationCostProfilerConn *applicationcostprofiler.ApplicationCostProfiler + ApplicationInsightsConn *applicationinsights.ApplicationInsights + AthenaConn *athena.Athena + AuditManagerConn *auditmanager.AuditManager + AutoScalingConn *autoscaling.AutoScaling + AutoScalingPlansConn *autoscalingplans.AutoScalingPlans + BackupConn *backup.Backup + BackupGatewayConn *backupgateway.BackupGateway + BatchConn *batch.Batch + BillingConductorConn *billingconductor.BillingConductor + BraketConn *braket.Braket + BudgetsConn *budgets.Budgets + CEConn *costexplorer.CostExplorer + CURConn *costandusagereportservice.CostandUsageReportService + ChimeConn *chime.Chime + ChimeSDKIdentityConn *chimesdkidentity.ChimeSDKIdentity + ChimeSDKMeetingsConn *chimesdkmeetings.ChimeSDKMeetings + ChimeSDKMessagingConn *chimesdkmessaging.ChimeSDKMessaging + Cloud9Conn *cloud9.Cloud9 + CloudControlConn *cloudcontrolapi.CloudControlApi + CloudDirectoryConn *clouddirectory.CloudDirectory + CloudFormationConn *cloudformation.CloudFormation + CloudFrontConn *cloudfront.CloudFront + CloudHSMV2Conn *cloudhsmv2.CloudHSMV2 + CloudSearchConn *cloudsearch.CloudSearch + CloudSearchDomainConn *cloudsearchdomain.CloudSearchDomain + CloudTrailConn *cloudtrail.CloudTrail + CloudWatchConn *cloudwatch.CloudWatch + CodeArtifactConn *codeartifact.CodeArtifact + CodeBuildConn *codebuild.CodeBuild + CodeCommitConn *codecommit.CodeCommit + CodeGuruProfilerConn *codeguruprofiler.CodeGuruProfiler + CodeGuruReviewerConn *codegurureviewer.CodeGuruReviewer + CodePipelineConn *codepipeline.CodePipeline + CodeStarConn *codestar.CodeStar + CodeStarConnectionsConn *codestarconnections.CodeStarConnections + CodeStarNotificationsConn *codestarnotifications.CodeStarNotifications + CognitoIDPConn *cognitoidentityprovider.CognitoIdentityProvider + CognitoIdentityConn *cognitoidentity.CognitoIdentity + CognitoSyncConn *cognitosync.CognitoSync + ComprehendConn *comprehend.Comprehend + ComprehendMedicalConn *comprehendmedical.ComprehendMedical + ComputeOptimizerConn *computeoptimizer.ComputeOptimizer + ConfigServiceConn *configservice.ConfigService + ConnectConn *connect.Connect + ConnectContactLensConn *connectcontactlens.ConnectContactLens + ConnectParticipantConn *connectparticipant.ConnectParticipant + CustomerProfilesConn *customerprofiles.CustomerProfiles + DAXConn *dax.DAX + DLMConn *dlm.DLM + DMSConn *databasemigrationservice.DatabaseMigrationService + DRSConn *drs.Drs + DSConn *directoryservice.DirectoryService + DataBrewConn *gluedatabrew.GlueDataBrew + DataExchangeConn *dataexchange.DataExchange + DataPipelineConn *datapipeline.DataPipeline + DataSyncConn *datasync.DataSync + DeployConn *codedeploy.CodeDeploy + DetectiveConn *detective.Detective + DevOpsGuruConn *devopsguru.DevOpsGuru + DeviceFarmConn *devicefarm.DeviceFarm + DirectConnectConn *directconnect.DirectConnect + DiscoveryConn *applicationdiscoveryservice.ApplicationDiscoveryService + DocDBConn *docdb.DocDB + DynamoDBConn *dynamodb.DynamoDB + DynamoDBStreamsConn *dynamodbstreams.DynamoDBStreams + EBSConn *ebs.EBS + EC2Conn *ec2.EC2 + EC2InstanceConnectConn *ec2instanceconnect.EC2InstanceConnect + ECRConn *ecr.ECR + ECRPublicConn *ecrpublic.ECRPublic + ECSConn *ecs.ECS + EFSConn *efs.EFS + EKSConn *eks.EKS + ELBConn *elb.ELB + ELBV2Conn *elbv2.ELBV2 + EMRConn *emr.EMR + EMRContainersConn *emrcontainers.EMRContainers + EMRServerlessConn *emrserverless.EMRServerless + ElastiCacheConn *elasticache.ElastiCache + ElasticBeanstalkConn *elasticbeanstalk.ElasticBeanstalk + ElasticInferenceConn *elasticinference.ElasticInference + ElasticTranscoderConn *elastictranscoder.ElasticTranscoder + ElasticsearchConn *elasticsearchservice.ElasticsearchService + EventsConn *eventbridge.EventBridge + EvidentlyConn *cloudwatchevidently.CloudWatchEvidently + FISConn *fis.Client + FMSConn *fms.FMS + FSxConn *fsx.FSx + FinSpaceConn *finspace.Finspace + FinSpaceDataConn *finspacedata.FinSpaceData + FirehoseConn *firehose.Firehose + ForecastConn *forecastservice.ForecastService + ForecastQueryConn *forecastqueryservice.ForecastQueryService + FraudDetectorConn *frauddetector.FraudDetector + GameLiftConn *gamelift.GameLift + GlacierConn *glacier.Glacier + GlobalAcceleratorConn *globalaccelerator.GlobalAccelerator + GlueConn *glue.Glue + GrafanaConn *managedgrafana.ManagedGrafana + GreengrassConn *greengrass.Greengrass + GreengrassV2Conn *greengrassv2.GreengrassV2 + GroundStationConn *groundstation.GroundStation + GuardDutyConn *guardduty.GuardDuty + HealthConn *health.Health + HealthLakeConn *healthlake.HealthLake + HoneycodeConn *honeycode.Honeycode + IAMConn *iam.IAM + IVSConn *ivs.IVS + IdentityStoreConn *identitystore.IdentityStore + ImageBuilderConn *imagebuilder.Imagebuilder + InspectorConn *inspector.Inspector + Inspector2Conn *inspector2.Inspector2 + IoTConn *iot.IoT + IoT1ClickDevicesConn *iot1clickdevicesservice.IoT1ClickDevicesService + IoT1ClickProjectsConn *iot1clickprojects.IoT1ClickProjects + IoTAnalyticsConn *iotanalytics.IoTAnalytics + IoTDataConn *iotdataplane.IoTDataPlane + IoTDeviceAdvisorConn *iotdeviceadvisor.IoTDeviceAdvisor + IoTEventsConn *iotevents.IoTEvents + IoTEventsDataConn *ioteventsdata.IoTEventsData + IoTFleetHubConn *iotfleethub.IoTFleetHub + IoTJobsDataConn *iotjobsdataplane.IoTJobsDataPlane + IoTSecureTunnelingConn *iotsecuretunneling.IoTSecureTunneling + IoTSiteWiseConn *iotsitewise.IoTSiteWise + IoTThingsGraphConn *iotthingsgraph.IoTThingsGraph + IoTTwinMakerConn *iottwinmaker.IoTTwinMaker + IoTWirelessConn *iotwireless.IoTWireless + KMSConn *kms.KMS + KafkaConn *kafka.Kafka + KafkaConnectConn *kafkaconnect.KafkaConnect + KendraConn *kendra.Client + KeyspacesConn *keyspaces.Keyspaces + KinesisConn *kinesis.Kinesis + KinesisAnalyticsConn *kinesisanalytics.KinesisAnalytics + KinesisAnalyticsV2Conn *kinesisanalyticsv2.KinesisAnalyticsV2 + KinesisVideoConn *kinesisvideo.KinesisVideo + KinesisVideoArchivedMediaConn *kinesisvideoarchivedmedia.KinesisVideoArchivedMedia + KinesisVideoMediaConn *kinesisvideomedia.KinesisVideoMedia + KinesisVideoSignalingConn *kinesisvideosignalingchannels.KinesisVideoSignalingChannels + LakeFormationConn *lakeformation.LakeFormation + LambdaConn *lambda.Lambda + LexModelsConn *lexmodelbuildingservice.LexModelBuildingService + LexModelsV2Conn *lexmodelsv2.LexModelsV2 + LexRuntimeConn *lexruntimeservice.LexRuntimeService + LexRuntimeV2Conn *lexruntimev2.LexRuntimeV2 + LicenseManagerConn *licensemanager.LicenseManager + LightsailConn *lightsail.Lightsail + LocationConn *locationservice.LocationService + LogsConn *cloudwatchlogs.CloudWatchLogs + LookoutEquipmentConn *lookoutequipment.LookoutEquipment + LookoutMetricsConn *lookoutmetrics.LookoutMetrics + LookoutVisionConn *lookoutforvision.LookoutForVision + MQConn *mq.MQ + MTurkConn *mturk.MTurk + MWAAConn *mwaa.MWAA + MachineLearningConn *machinelearning.MachineLearning + MacieConn *macie.Macie + Macie2Conn *macie2.Macie2 + ManagedBlockchainConn *managedblockchain.ManagedBlockchain + MarketplaceCatalogConn *marketplacecatalog.MarketplaceCatalog + MarketplaceCommerceAnalyticsConn *marketplacecommerceanalytics.MarketplaceCommerceAnalytics + MarketplaceEntitlementConn *marketplaceentitlementservice.MarketplaceEntitlementService + MarketplaceMeteringConn *marketplacemetering.MarketplaceMetering + MediaConnectConn *mediaconnect.MediaConnect + MediaConvertConn *mediaconvert.MediaConvert + MediaLiveConn *medialive.MediaLive + MediaPackageConn *mediapackage.MediaPackage + MediaPackageVODConn *mediapackagevod.MediaPackageVod + MediaStoreConn *mediastore.MediaStore + MediaStoreDataConn *mediastoredata.MediaStoreData + MediaTailorConn *mediatailor.MediaTailor + MemoryDBConn *memorydb.MemoryDB + MgHConn *migrationhub.MigrationHub + MgnConn *mgn.Mgn + MigrationHubConfigConn *migrationhubconfig.MigrationHubConfig + MigrationHubRefactorSpacesConn *migrationhubrefactorspaces.MigrationHubRefactorSpaces + MigrationHubStrategyConn *migrationhubstrategyrecommendations.MigrationHubStrategyRecommendations + MobileConn *mobile.Mobile + NeptuneConn *neptune.Neptune + NetworkFirewallConn *networkfirewall.NetworkFirewall + NetworkManagerConn *networkmanager.NetworkManager + NimbleConn *nimblestudio.NimbleStudio + OpenSearchConn *opensearchservice.OpenSearchService + OpsWorksConn *opsworks.OpsWorks + OpsWorksCMConn *opsworkscm.OpsWorksCM + OrganizationsConn *organizations.Organizations + OutpostsConn *outposts.Outposts + PIConn *pi.PI + PanoramaConn *panorama.Panorama + PersonalizeConn *personalize.Personalize + PersonalizeEventsConn *personalizeevents.PersonalizeEvents + PersonalizeRuntimeConn *personalizeruntime.PersonalizeRuntime + PinpointConn *pinpoint.Pinpoint + PinpointEmailConn *pinpointemail.PinpointEmail + PinpointSMSVoiceConn *pinpointsmsvoice.PinpointSMSVoice + PollyConn *polly.Polly + PricingConn *pricing.Pricing + ProtonConn *proton.Proton + QLDBConn *qldb.QLDB + QLDBSessionConn *qldbsession.QLDBSession + QuickSightConn *quicksight.QuickSight + RAMConn *ram.RAM + RBinConn *recyclebin.RecycleBin + RDSConn *rds.RDS + RDSDataConn *rdsdataservice.RDSDataService + RUMConn *cloudwatchrum.CloudWatchRUM + RedshiftConn *redshift.Redshift + RedshiftDataConn *redshiftdataapiservice.RedshiftDataAPIService + RedshiftServerlessConn *redshiftserverless.RedshiftServerless + RekognitionConn *rekognition.Rekognition + ResilienceHubConn *resiliencehub.ResilienceHub + ResourceGroupsConn *resourcegroups.ResourceGroups + ResourceGroupsTaggingAPIConn *resourcegroupstaggingapi.ResourceGroupsTaggingAPI + RoboMakerConn *robomaker.RoboMaker + RolesAnywhereConn *rolesanywhere.Client + Route53Conn *route53.Route53 + Route53DomainsConn *route53domains.Client + Route53RecoveryClusterConn *route53recoverycluster.Route53RecoveryCluster + Route53RecoveryControlConfigConn *route53recoverycontrolconfig.Route53RecoveryControlConfig + Route53RecoveryReadinessConn *route53recoveryreadiness.Route53RecoveryReadiness + Route53ResolverConn *route53resolver.Route53Resolver + S3Conn *s3.S3 + S3ControlConn *s3control.S3Control + S3OutpostsConn *s3outposts.S3Outposts + SESConn *ses.SES + SESV2Conn *sesv2.SESV2 + SFNConn *sfn.SFN + SMSConn *sms.SMS + SNSConn *sns.SNS + SQSConn *sqs.SQS + SSMConn *ssm.SSM + SSMContactsConn *ssmcontacts.SSMContacts + SSMIncidentsConn *ssmincidents.SSMIncidents + SSOConn *sso.SSO + SSOAdminConn *ssoadmin.SSOAdmin + SSOOIDCConn *ssooidc.SSOOIDC + STSConn *sts.STS + SWFConn *swf.SWF + SageMakerConn *sagemaker.SageMaker + SageMakerA2IRuntimeConn *augmentedairuntime.AugmentedAIRuntime + SageMakerEdgeConn *sagemakeredgemanager.SagemakerEdgeManager + SageMakerFeatureStoreRuntimeConn *sagemakerfeaturestoreruntime.SageMakerFeatureStoreRuntime + SageMakerRuntimeConn *sagemakerruntime.SageMakerRuntime + SavingsPlansConn *savingsplans.SavingsPlans + SchemasConn *schemas.Schemas + SecretsManagerConn *secretsmanager.SecretsManager + SecurityHubConn *securityhub.SecurityHub + ServerlessRepoConn *serverlessapplicationrepository.ServerlessApplicationRepository + ServiceCatalogConn *servicecatalog.ServiceCatalog + ServiceCatalogAppRegistryConn *appregistry.AppRegistry + ServiceDiscoveryConn *servicediscovery.ServiceDiscovery + ServiceQuotasConn *servicequotas.ServiceQuotas + ShieldConn *shield.Shield + SignerConn *signer.Signer + SimpleDBConn *simpledb.SimpleDB + SnowDeviceManagementConn *snowdevicemanagement.SnowDeviceManagement + SnowballConn *snowball.Snowball + StorageGatewayConn *storagegateway.StorageGateway + SupportConn *support.Support + SyntheticsConn *synthetics.Synthetics + TextractConn *textract.Textract + TimestreamQueryConn *timestreamquery.TimestreamQuery + TimestreamWriteConn *timestreamwrite.TimestreamWrite + TranscribeConn *transcribe.Client + TranscribeStreamingConn *transcribestreamingservice.TranscribeStreamingService + TransferConn *transfer.Transfer + TranslateConn *translate.Translate + VoiceIDConn *voiceid.VoiceID + WAFConn *waf.WAF + WAFRegionalConn *wafregional.WAFRegional + WAFV2Conn *wafv2.WAFV2 + WellArchitectedConn *wellarchitected.WellArchitected + WisdomConn *connectwisdomservice.ConnectWisdomService + WorkDocsConn *workdocs.WorkDocs + WorkLinkConn *worklink.WorkLink + WorkMailConn *workmail.WorkMail + WorkMailMessageFlowConn *workmailmessageflow.WorkMailMessageFlow + WorkSpacesConn *workspaces.WorkSpaces + WorkSpacesWebConn *workspacesweb.WorkSpacesWeb + XRayConn *xray.XRay +} + +// PartitionHostname returns a hostname with the provider domain suffix for the partition +// e.g. PREFIX.amazonaws.com +// The prefix should not contain a trailing period. +func (client *AWSClient) PartitionHostname(prefix string) string { + return fmt.Sprintf("%s.%s", prefix, client.DNSSuffix) +} + +// RegionalHostname returns a hostname with the provider domain suffix for the region and partition +// e.g. PREFIX.us-west-2.amazonaws.com +// The prefix should not contain a trailing period. +func (client *AWSClient) RegionalHostname(prefix string) string { + return fmt.Sprintf("%s.%s.%s", prefix, client.Region, client.DNSSuffix) +} diff --git a/internal/conns/config.go b/internal/conns/config.go new file mode 100644 index 0000000..be6cd4a --- /dev/null +++ b/internal/conns/config.go @@ -0,0 +1,563 @@ +package conns + +import ( + "context" + "log" + "strings" + + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" + "github.com/aws/aws-sdk-go-v2/service/fis" + "github.com/aws/aws-sdk-go-v2/service/kendra" + "github.com/aws/aws-sdk-go-v2/service/rolesanywhere" + "github.com/aws/aws-sdk-go-v2/service/route53domains" + "github.com/aws/aws-sdk-go-v2/service/transcribe" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go/aws/request" + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/aws/aws-sdk-go/service/applicationautoscaling" + "github.com/aws/aws-sdk-go/service/appsync" + "github.com/aws/aws-sdk-go/service/chime" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/service/cloudhsmv2" + "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/fms" + "github.com/aws/aws-sdk-go/service/globalaccelerator" + "github.com/aws/aws-sdk-go/service/kafka" + "github.com/aws/aws-sdk-go/service/kinesis" + "github.com/aws/aws-sdk-go/service/lightsail" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/aws/aws-sdk-go/service/route53" + "github.com/aws/aws-sdk-go/service/route53recoverycontrolconfig" + "github.com/aws/aws-sdk-go/service/route53recoveryreadiness" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/securityhub" + "github.com/aws/aws-sdk-go/service/shield" + "github.com/aws/aws-sdk-go/service/ssoadmin" + "github.com/aws/aws-sdk-go/service/storagegateway" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/aws/aws-sdk-go/service/wafv2" + tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" + "github.com/cloudposse/terraform-provider-awsutils/names" + awsbase "github.com/hashicorp/aws-sdk-go-base/v2" + awsbasev1 "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +) + +type Config struct { + AccessKey string + AllowedAccountIds []string + AssumeRole *awsbase.AssumeRole + AssumeRoleWithWebIdentity *awsbase.AssumeRoleWithWebIdentity + CustomCABundle string + DefaultTagsConfig *tftags.DefaultConfig + EC2MetadataServiceEnableState imds.ClientEnableState + EC2MetadataServiceEndpoint string + EC2MetadataServiceEndpointMode string + Endpoints map[string]string + ForbiddenAccountIds []string + HTTPProxy string + IgnoreTagsConfig *tftags.IgnoreConfig + Insecure bool + MaxRetries int + Profile string + Region string + S3UsePathStyle bool + SecretKey string + SharedConfigFiles []string + SharedCredentialsFiles []string + SkipCredsValidation bool + SkipGetEC2Platforms bool + SkipRegionValidation bool + SkipRequestingAccountId bool + STSRegion string + SuppressDebugLog bool + TerraformVersion string + Token string + UseDualStackEndpoint bool + UseFIPSEndpoint bool +} + +// Client configures and returns a fully initialized AWSClient +func (c *Config) Client(ctx context.Context) (interface{}, diag.Diagnostics) { + awsbaseConfig := awsbase.Config{ + AccessKey: c.AccessKey, + APNInfo: StdUserAgentProducts(c.TerraformVersion), + AssumeRoleWithWebIdentity: c.AssumeRoleWithWebIdentity, + CallerDocumentationURL: "https://registry.terraform.io/providers/hashicorp/aws", + CallerName: "Terraform AWS Provider", + EC2MetadataServiceEnableState: c.EC2MetadataServiceEnableState, + IamEndpoint: c.Endpoints[names.IAM], + Insecure: c.Insecure, + HTTPProxy: c.HTTPProxy, + MaxRetries: c.MaxRetries, + Profile: c.Profile, + Region: c.Region, + SecretKey: c.SecretKey, + SkipCredsValidation: c.SkipCredsValidation, + SkipRequestingAccountId: c.SkipRequestingAccountId, + StsEndpoint: c.Endpoints[names.STS], + SuppressDebugLog: c.SuppressDebugLog, + Token: c.Token, + UseDualStackEndpoint: c.UseDualStackEndpoint, + UseFIPSEndpoint: c.UseFIPSEndpoint, + } + + if c.AssumeRole != nil && c.AssumeRole.RoleARN != "" { + awsbaseConfig.AssumeRole = c.AssumeRole + } + + if c.CustomCABundle != "" { + awsbaseConfig.CustomCABundle = c.CustomCABundle + } + + if c.EC2MetadataServiceEndpoint != "" { + awsbaseConfig.EC2MetadataServiceEndpoint = c.EC2MetadataServiceEndpoint + awsbaseConfig.EC2MetadataServiceEndpointMode = c.EC2MetadataServiceEndpointMode + } + + if len(c.SharedConfigFiles) != 0 { + awsbaseConfig.SharedConfigFiles = c.SharedConfigFiles + } + + if len(c.SharedCredentialsFiles) != 0 { + awsbaseConfig.SharedCredentialsFiles = c.SharedCredentialsFiles + } + + if c.STSRegion != "" { + awsbaseConfig.StsRegion = c.STSRegion + } + + cfg, err := awsbase.GetAwsConfig(ctx, &awsbaseConfig) + if err != nil { + return nil, diag.Errorf("error configuring Terraform AWS Provider: %s", err) + } + + if !c.SkipRegionValidation { + if err := awsbase.ValidateRegion(cfg.Region); err != nil { + return nil, diag.FromErr(err) + } + } + c.Region = cfg.Region + + sess, err := awsbasev1.GetSession(&cfg, &awsbaseConfig) + if err != nil { + return nil, diag.Errorf("error creating AWS SDK v1 session: %s", err) + } + + accountID, partition, err := awsbase.GetAwsAccountIDAndPartition(ctx, cfg, &awsbaseConfig) + if err != nil { + return nil, diag.Errorf("error retrieving account details: %s", err) + } + + if accountID == "" { + log.Println("[WARN] AWS account ID not found for provider. See https://www.terraform.io/docs/providers/aws/index.html#skip_requesting_account_id for implications.") + } + + if len(c.ForbiddenAccountIds) > 0 { + for _, forbiddenAccountID := range c.AllowedAccountIds { + if accountID == forbiddenAccountID { + return nil, diag.Errorf("AWS Account ID not allowed: %s", accountID) + } + } + } + if len(c.AllowedAccountIds) > 0 { + found := false + for _, allowedAccountID := range c.AllowedAccountIds { + if accountID == allowedAccountID { + found = true + break + } + } + if !found { + return nil, diag.Errorf("AWS Account ID not allowed: %s", accountID) + } + } + + DNSSuffix := "amazonaws.com" + if p, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), c.Region); ok { + DNSSuffix = p.DNSSuffix() + } + + client := c.clientConns(sess) + + client.AccountID = accountID + client.DefaultTagsConfig = c.DefaultTagsConfig + client.DNSSuffix = DNSSuffix + client.IgnoreTagsConfig = c.IgnoreTagsConfig + client.Partition = partition + client.Region = c.Region + client.ReverseDNSPrefix = ReverseDNS(DNSSuffix) + client.Session = sess + client.TerraformVersion = c.TerraformVersion + + client.FISConn = fis.NewFromConfig(cfg, func(o *fis.Options) { + if endpoint := c.Endpoints[names.FIS]; endpoint != "" { + o.EndpointResolver = fis.EndpointResolverFromURL(endpoint) + } + }) + + client.KendraConn = kendra.NewFromConfig(cfg, func(o *kendra.Options) { + if endpoint := c.Endpoints[names.Kendra]; endpoint != "" { + o.EndpointResolver = kendra.EndpointResolverFromURL(endpoint) + } + }) + + client.RolesAnywhereConn = rolesanywhere.NewFromConfig(cfg, func(o *rolesanywhere.Options) { + if endpoint := c.Endpoints[names.RolesAnywhere]; endpoint != "" { + o.EndpointResolver = rolesanywhere.EndpointResolverFromURL(endpoint) + } + }) + + client.Route53DomainsConn = route53domains.NewFromConfig(cfg, func(o *route53domains.Options) { + if endpoint := c.Endpoints[names.Route53Domains]; endpoint != "" { + o.EndpointResolver = route53domains.EndpointResolverFromURL(endpoint) + } else if partition == endpoints.AwsPartitionID { + // Route 53 Domains is only available in AWS Commercial us-east-1 Region. + o.Region = endpoints.UsEast1RegionID + } + }) + + client.TranscribeConn = transcribe.NewFromConfig(cfg, func(o *transcribe.Options) { + if endpoint := c.Endpoints[names.Transcribe]; endpoint != "" { + o.EndpointResolver = transcribe.EndpointResolverFromURL(endpoint) + } + }) + + // sts + stsConfig := &aws.Config{ + Endpoint: aws.String(c.Endpoints[names.STS]), + } + + if c.STSRegion != "" { + stsConfig.Region = aws.String(c.STSRegion) + } + + client.STSConn = sts.New(sess.Copy(stsConfig)) + + // "Global" services that require customizations + globalAcceleratorConfig := &aws.Config{ + Endpoint: aws.String(c.Endpoints[names.GlobalAccelerator]), + } + route53Config := &aws.Config{ + Endpoint: aws.String(c.Endpoints[names.Route53]), + } + route53RecoveryControlConfigConfig := &aws.Config{ + Endpoint: aws.String(c.Endpoints[names.Route53RecoveryControlConfig]), + } + route53RecoveryReadinessConfig := &aws.Config{ + Endpoint: aws.String(c.Endpoints[names.Route53RecoveryReadiness]), + } + shieldConfig := &aws.Config{ + Endpoint: aws.String(c.Endpoints[names.Shield]), + } + + // Services that require multiple client configurations + s3Config := &aws.Config{ + Endpoint: aws.String(c.Endpoints[names.S3]), + S3ForcePathStyle: aws.Bool(c.S3UsePathStyle), + } + + client.S3Conn = s3.New(sess.Copy(s3Config)) + + s3Config.DisableRestProtocolURICleaning = aws.Bool(true) + client.S3ConnURICleaningDisabled = s3.New(sess.Copy(s3Config)) + + // Force "global" services to correct regions + switch partition { + case endpoints.AwsPartitionID: + globalAcceleratorConfig.Region = aws.String(endpoints.UsWest2RegionID) + route53Config.Region = aws.String(endpoints.UsEast1RegionID) + route53RecoveryControlConfigConfig.Region = aws.String(endpoints.UsWest2RegionID) + route53RecoveryReadinessConfig.Region = aws.String(endpoints.UsWest2RegionID) + shieldConfig.Region = aws.String(endpoints.UsEast1RegionID) + case endpoints.AwsCnPartitionID: + // The AWS Go SDK is missing endpoint information for Route 53 in the AWS China partition. + // This can likely be removed in the future. + if aws.StringValue(route53Config.Endpoint) == "" { + route53Config.Endpoint = aws.String("https://api.route53.cn") + } + route53Config.Region = aws.String(endpoints.CnNorthwest1RegionID) + case endpoints.AwsUsGovPartitionID: + route53Config.Region = aws.String(endpoints.UsGovWest1RegionID) + } + + client.GlobalAcceleratorConn = globalaccelerator.New(sess.Copy(globalAcceleratorConfig)) + client.Route53Conn = route53.New(sess.Copy(route53Config)) + client.Route53RecoveryControlConfigConn = route53recoverycontrolconfig.New(sess.Copy(route53RecoveryControlConfigConfig)) + client.Route53RecoveryReadinessConn = route53recoveryreadiness.New(sess.Copy(route53RecoveryReadinessConfig)) + client.ShieldConn = shield.New(sess.Copy(shieldConfig)) + + client.APIGatewayConn.Handlers.Retry.PushBack(func(r *request.Request) { + // Many operations can return an error such as: + // ConflictException: Unable to complete operation due to concurrent modification. Please try again later. + // Handle them all globally for the service client. + if tfawserr.ErrMessageContains(r.Error, apigateway.ErrCodeConflictException, "try again later") { + r.Retryable = aws.Bool(true) + } + }) + + // Workaround for https://github.com/aws/aws-sdk-go/issues/1472 + client.AppAutoScalingConn.Handlers.Retry.PushBack(func(r *request.Request) { + if !strings.HasPrefix(r.Operation.Name, "Describe") && !strings.HasPrefix(r.Operation.Name, "List") { + return + } + if tfawserr.ErrCodeEquals(r.Error, applicationautoscaling.ErrCodeFailedResourceAccessException) { + r.Retryable = aws.Bool(true) + } + }) + + // StartDeployment operations can return a ConflictException + // if ongoing deployments are in-progress, thus we handle them + // here for the service client. + client.AppConfigConn.Handlers.Retry.PushBack(func(r *request.Request) { + if r.Operation.Name == "StartDeployment" { + if tfawserr.ErrCodeEquals(r.Error, appconfig.ErrCodeConflictException) { + r.Retryable = aws.Bool(true) + } + } + }) + + client.AppSyncConn.Handlers.Retry.PushBack(func(r *request.Request) { + if r.Operation.Name == "CreateGraphqlApi" { + if tfawserr.ErrMessageContains(r.Error, appsync.ErrCodeConcurrentModificationException, "a GraphQL API creation is already in progress") { + r.Retryable = aws.Bool(true) + } + } + }) + + client.ChimeConn.Handlers.Retry.PushBack(func(r *request.Request) { + // When calling CreateVoiceConnector across multiple resources, + // the API can randomly return a BadRequestException without explanation + if r.Operation.Name == "CreateVoiceConnector" { + if tfawserr.ErrMessageContains(r.Error, chime.ErrCodeBadRequestException, "Service received a bad request") { + r.Retryable = aws.Bool(true) + } + } + }) + + client.CloudHSMV2Conn.Handlers.Retry.PushBack(func(r *request.Request) { + if tfawserr.ErrMessageContains(r.Error, cloudhsmv2.ErrCodeCloudHsmInternalFailureException, "request was rejected because of an AWS CloudHSM internal failure") { + r.Retryable = aws.Bool(true) + } + }) + + client.ConfigServiceConn.Handlers.Retry.PushBack(func(r *request.Request) { + // When calling Config Organization Rules API actions immediately + // after Organization creation, the API can randomly return the + // OrganizationAccessDeniedException error for a few minutes, even + // after succeeding a few requests. + switch r.Operation.Name { + case "DeleteOrganizationConfigRule", "DescribeOrganizationConfigRules", "DescribeOrganizationConfigRuleStatuses", "PutOrganizationConfigRule": + if !tfawserr.ErrMessageContains(r.Error, configservice.ErrCodeOrganizationAccessDeniedException, "This action can be only made by AWS Organization's master account.") { + return + } + + // We only want to retry briefly as the default max retry count would + // excessively retry when the error could be legitimate. + // We currently depend on the DefaultRetryer exponential backoff here. + // ~10 retries gives a fair backoff of a few seconds. + if r.RetryCount < 9 { + r.Retryable = aws.Bool(true) + } else { + r.Retryable = aws.Bool(false) + } + case "DeleteOrganizationConformancePack", "DescribeOrganizationConformancePacks", "DescribeOrganizationConformancePackStatuses", "PutOrganizationConformancePack": + if !tfawserr.ErrCodeEquals(r.Error, configservice.ErrCodeOrganizationAccessDeniedException) { + if r.Operation.Name == "DeleteOrganizationConformancePack" && tfawserr.ErrCodeEquals(err, configservice.ErrCodeResourceInUseException) { + r.Retryable = aws.Bool(true) + } + return + } + + // We only want to retry briefly as the default max retry count would + // excessively retry when the error could be legitimate. + // We currently depend on the DefaultRetryer exponential backoff here. + // ~10 retries gives a fair backoff of a few seconds. + if r.RetryCount < 9 { + r.Retryable = aws.Bool(true) + } else { + r.Retryable = aws.Bool(false) + } + } + }) + + client.CloudFormationConn.Handlers.Retry.PushBack(func(r *request.Request) { + if tfawserr.ErrMessageContains(r.Error, cloudformation.ErrCodeOperationInProgressException, "Another Operation on StackSet") { + r.Retryable = aws.Bool(true) + } + }) + + // See https://github.com/aws/aws-sdk-go/pull/1276 + client.DynamoDBConn.Handlers.Retry.PushBack(func(r *request.Request) { + if r.Operation.Name != "PutItem" && r.Operation.Name != "UpdateItem" && r.Operation.Name != "DeleteItem" { + return + } + if tfawserr.ErrMessageContains(r.Error, dynamodb.ErrCodeLimitExceededException, "Subscriber limit exceeded:") { + r.Retryable = aws.Bool(true) + } + }) + + client.EC2Conn.Handlers.Retry.PushBack(func(r *request.Request) { + switch err := r.Error; r.Operation.Name { + case "AttachVpnGateway", "DetachVpnGateway": + if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "This call cannot be completed because there are pending VPNs or Virtual Interfaces") { + r.Retryable = aws.Bool(true) + } + + case "CreateClientVpnEndpoint": + if tfawserr.ErrMessageContains(err, "OperationNotPermitted", "Endpoint cannot be created while another endpoint is being created") { + r.Retryable = aws.Bool(true) + } + + case "CreateClientVpnRoute", "DeleteClientVpnRoute": + if tfawserr.ErrMessageContains(err, "ConcurrentMutationLimitExceeded", "Cannot initiate another change for this endpoint at this time") { + r.Retryable = aws.Bool(true) + } + + case "CreateVpnConnection": + if tfawserr.ErrMessageContains(err, "VpnConnectionLimitExceeded", "maximum number of mutating objects has been reached") { + r.Retryable = aws.Bool(true) + } + + case "CreateVpnGateway": + if tfawserr.ErrMessageContains(err, "VpnGatewayLimitExceeded", "maximum number of mutating objects has been reached") { + r.Retryable = aws.Bool(true) + } + } + }) + + client.FMSConn.Handlers.Retry.PushBack(func(r *request.Request) { + // Acceptance testing creates and deletes resources in quick succession. + // The FMS onboarding process into Organizations is opaque to consumers. + // Since we cannot reasonably check this status before receiving the error, + // set the operation as retryable. + switch r.Operation.Name { + case "AssociateAdminAccount": + if tfawserr.ErrMessageContains(r.Error, fms.ErrCodeInvalidOperationException, "Your AWS Organization is currently offboarding with AWS Firewall Manager. Please submit onboard request after offboarded.") { + r.Retryable = aws.Bool(true) + } + case "DisassociateAdminAccount": + if tfawserr.ErrMessageContains(r.Error, fms.ErrCodeInvalidOperationException, "Your AWS Organization is currently onboarding with AWS Firewall Manager and cannot be offboarded.") { + r.Retryable = aws.Bool(true) + } + // System problems can arise during FMS policy updates (maybe also creation), + // so we set the following operation as retryable. + // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/23946 + case "PutPolicy": + if tfawserr.ErrCodeEquals(r.Error, fms.ErrCodeInternalErrorException) { + r.Retryable = aws.Bool(true) + } + } + }) + + client.KafkaConn.Handlers.Retry.PushBack(func(r *request.Request) { + if tfawserr.ErrMessageContains(r.Error, kafka.ErrCodeTooManyRequestsException, "Too Many Requests") { + r.Retryable = aws.Bool(true) + } + }) + + client.KinesisConn.Handlers.Retry.PushBack(func(r *request.Request) { + if r.Operation.Name == "CreateStream" { + if tfawserr.ErrMessageContains(r.Error, kinesis.ErrCodeLimitExceededException, "simultaneously be in CREATING or DELETING") { + r.Retryable = aws.Bool(true) + } + } + if r.Operation.Name == "CreateStream" || r.Operation.Name == "DeleteStream" { + if tfawserr.ErrMessageContains(r.Error, kinesis.ErrCodeLimitExceededException, "Rate exceeded for stream") { + r.Retryable = aws.Bool(true) + } + } + }) + + client.LightsailConn.Handlers.Retry.PushBack(func(r *request.Request) { + switch r.Operation.Name { + case "CreateContainerService", "UpdateContainerService", "CreateContainerServiceDeployment": + if tfawserr.ErrMessageContains(r.Error, lightsail.ErrCodeInvalidInputException, "Please try again in a few minutes") { + r.Retryable = aws.Bool(true) + } + case "DeleteContainerService": + if tfawserr.ErrMessageContains(r.Error, lightsail.ErrCodeInvalidInputException, "Please try again in a few minutes") || + tfawserr.ErrMessageContains(r.Error, lightsail.ErrCodeInvalidInputException, "Please wait for it to complete before trying again") { + r.Retryable = aws.Bool(true) + } + } + }) + + client.OrganizationsConn.Handlers.Retry.PushBack(func(r *request.Request) { + // Retry on the following error: + // ConcurrentModificationException: AWS Organizations can't complete your request because it conflicts with another attempt to modify the same entity. Try again later. + if tfawserr.ErrMessageContains(r.Error, organizations.ErrCodeConcurrentModificationException, "Try again later") { + r.Retryable = aws.Bool(true) + } + }) + + client.S3Conn.Handlers.Retry.PushBack(func(r *request.Request) { + if tfawserr.ErrMessageContains(r.Error, "OperationAborted", "A conflicting conditional operation is currently in progress against this resource. Please try again.") { + r.Retryable = aws.Bool(true) + } + }) + + // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/17996 + client.SecurityHubConn.Handlers.Retry.PushBack(func(r *request.Request) { + switch r.Operation.Name { + case "EnableOrganizationAdminAccount": + if tfawserr.ErrCodeEquals(r.Error, securityhub.ErrCodeResourceConflictException) { + r.Retryable = aws.Bool(true) + } + } + }) + + // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/19215 + client.SSOAdminConn.Handlers.Retry.PushBack(func(r *request.Request) { + if r.Operation.Name == "AttachManagedPolicyToPermissionSet" || r.Operation.Name == "DetachManagedPolicyFromPermissionSet" { + if tfawserr.ErrCodeEquals(r.Error, ssoadmin.ErrCodeConflictException) { + r.Retryable = aws.Bool(true) + } + } + }) + + client.StorageGatewayConn.Handlers.Retry.PushBack(func(r *request.Request) { + // InvalidGatewayRequestException: The specified gateway proxy network connection is busy. + if tfawserr.ErrMessageContains(r.Error, storagegateway.ErrCodeInvalidGatewayRequestException, "The specified gateway proxy network connection is busy") { + r.Retryable = aws.Bool(true) + } + }) + + client.WAFV2Conn.Handlers.Retry.PushBack(func(r *request.Request) { + if tfawserr.ErrMessageContains(r.Error, wafv2.ErrCodeWAFInternalErrorException, "Retry your request") { + r.Retryable = aws.Bool(true) + } + + if tfawserr.ErrMessageContains(r.Error, wafv2.ErrCodeWAFServiceLinkedRoleErrorException, "Retry") { + r.Retryable = aws.Bool(true) + } + + if r.Operation.Name == "CreateIPSet" || r.Operation.Name == "CreateRegexPatternSet" || + r.Operation.Name == "CreateRuleGroup" || r.Operation.Name == "CreateWebACL" { + // WAFv2 supports tag on create which can result in the below error codes according to the documentation + if tfawserr.ErrMessageContains(r.Error, wafv2.ErrCodeWAFTagOperationException, "Retry your request") { + r.Retryable = aws.Bool(true) + } + if tfawserr.ErrMessageContains(err, wafv2.ErrCodeWAFTagOperationInternalErrorException, "Retry your request") { + r.Retryable = aws.Bool(true) + } + } + }) + + if !c.SkipGetEC2Platforms { + supportedPlatforms, err := GetSupportedEC2Platforms(client.EC2Conn) + if err != nil { + // We intentionally fail *silently* because there's a chance + // user just doesn't have ec2:DescribeAccountAttributes permissions + log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err) + } else { + client.SupportedPlatforms = supportedPlatforms + } + } + + return client, nil +} diff --git a/internal/conns/config_gen.go b/internal/conns/config_gen.go new file mode 100644 index 0000000..19d09c2 --- /dev/null +++ b/internal/conns/config_gen.go @@ -0,0 +1,593 @@ +// Code generated by internal/generate/clientconfig/main.go; DO NOT EDIT. +package conns + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/accessanalyzer" + "github.com/aws/aws-sdk-go/service/account" + "github.com/aws/aws-sdk-go/service/acm" + "github.com/aws/aws-sdk-go/service/acmpca" + "github.com/aws/aws-sdk-go/service/alexaforbusiness" + "github.com/aws/aws-sdk-go/service/amplify" + "github.com/aws/aws-sdk-go/service/amplifybackend" + "github.com/aws/aws-sdk-go/service/amplifyuibuilder" + "github.com/aws/aws-sdk-go/service/apigateway" + "github.com/aws/aws-sdk-go/service/apigatewaymanagementapi" + "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/aws/aws-sdk-go/service/appconfig" + "github.com/aws/aws-sdk-go/service/appconfigdata" + "github.com/aws/aws-sdk-go/service/appflow" + "github.com/aws/aws-sdk-go/service/appintegrationsservice" + "github.com/aws/aws-sdk-go/service/applicationautoscaling" + "github.com/aws/aws-sdk-go/service/applicationcostprofiler" + "github.com/aws/aws-sdk-go/service/applicationdiscoveryservice" + "github.com/aws/aws-sdk-go/service/applicationinsights" + "github.com/aws/aws-sdk-go/service/appmesh" + "github.com/aws/aws-sdk-go/service/appregistry" + "github.com/aws/aws-sdk-go/service/apprunner" + "github.com/aws/aws-sdk-go/service/appstream" + "github.com/aws/aws-sdk-go/service/appsync" + "github.com/aws/aws-sdk-go/service/athena" + "github.com/aws/aws-sdk-go/service/auditmanager" + "github.com/aws/aws-sdk-go/service/augmentedairuntime" + "github.com/aws/aws-sdk-go/service/autoscaling" + "github.com/aws/aws-sdk-go/service/autoscalingplans" + "github.com/aws/aws-sdk-go/service/backup" + "github.com/aws/aws-sdk-go/service/backupgateway" + "github.com/aws/aws-sdk-go/service/batch" + "github.com/aws/aws-sdk-go/service/billingconductor" + "github.com/aws/aws-sdk-go/service/braket" + "github.com/aws/aws-sdk-go/service/budgets" + "github.com/aws/aws-sdk-go/service/chime" + "github.com/aws/aws-sdk-go/service/chimesdkidentity" + "github.com/aws/aws-sdk-go/service/chimesdkmeetings" + "github.com/aws/aws-sdk-go/service/chimesdkmessaging" + "github.com/aws/aws-sdk-go/service/cloud9" + "github.com/aws/aws-sdk-go/service/cloudcontrolapi" + "github.com/aws/aws-sdk-go/service/clouddirectory" + "github.com/aws/aws-sdk-go/service/cloudformation" + "github.com/aws/aws-sdk-go/service/cloudfront" + "github.com/aws/aws-sdk-go/service/cloudhsmv2" + "github.com/aws/aws-sdk-go/service/cloudsearch" + "github.com/aws/aws-sdk-go/service/cloudsearchdomain" + "github.com/aws/aws-sdk-go/service/cloudtrail" + "github.com/aws/aws-sdk-go/service/cloudwatch" + "github.com/aws/aws-sdk-go/service/cloudwatchevidently" + "github.com/aws/aws-sdk-go/service/cloudwatchlogs" + "github.com/aws/aws-sdk-go/service/cloudwatchrum" + "github.com/aws/aws-sdk-go/service/codeartifact" + "github.com/aws/aws-sdk-go/service/codebuild" + "github.com/aws/aws-sdk-go/service/codecommit" + "github.com/aws/aws-sdk-go/service/codedeploy" + "github.com/aws/aws-sdk-go/service/codeguruprofiler" + "github.com/aws/aws-sdk-go/service/codegurureviewer" + "github.com/aws/aws-sdk-go/service/codepipeline" + "github.com/aws/aws-sdk-go/service/codestar" + "github.com/aws/aws-sdk-go/service/codestarconnections" + "github.com/aws/aws-sdk-go/service/codestarnotifications" + "github.com/aws/aws-sdk-go/service/cognitoidentity" + "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" + "github.com/aws/aws-sdk-go/service/cognitosync" + "github.com/aws/aws-sdk-go/service/comprehend" + "github.com/aws/aws-sdk-go/service/comprehendmedical" + "github.com/aws/aws-sdk-go/service/computeoptimizer" + "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go/service/connect" + "github.com/aws/aws-sdk-go/service/connectcontactlens" + "github.com/aws/aws-sdk-go/service/connectparticipant" + "github.com/aws/aws-sdk-go/service/connectwisdomservice" + "github.com/aws/aws-sdk-go/service/costandusagereportservice" + "github.com/aws/aws-sdk-go/service/costexplorer" + "github.com/aws/aws-sdk-go/service/customerprofiles" + "github.com/aws/aws-sdk-go/service/databasemigrationservice" + "github.com/aws/aws-sdk-go/service/dataexchange" + "github.com/aws/aws-sdk-go/service/datapipeline" + "github.com/aws/aws-sdk-go/service/datasync" + "github.com/aws/aws-sdk-go/service/dax" + "github.com/aws/aws-sdk-go/service/detective" + "github.com/aws/aws-sdk-go/service/devicefarm" + "github.com/aws/aws-sdk-go/service/devopsguru" + "github.com/aws/aws-sdk-go/service/directconnect" + "github.com/aws/aws-sdk-go/service/directoryservice" + "github.com/aws/aws-sdk-go/service/dlm" + "github.com/aws/aws-sdk-go/service/docdb" + "github.com/aws/aws-sdk-go/service/drs" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/dynamodbstreams" + "github.com/aws/aws-sdk-go/service/ebs" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/ec2instanceconnect" + "github.com/aws/aws-sdk-go/service/ecr" + "github.com/aws/aws-sdk-go/service/ecrpublic" + "github.com/aws/aws-sdk-go/service/ecs" + "github.com/aws/aws-sdk-go/service/efs" + "github.com/aws/aws-sdk-go/service/eks" + "github.com/aws/aws-sdk-go/service/elasticache" + "github.com/aws/aws-sdk-go/service/elasticbeanstalk" + "github.com/aws/aws-sdk-go/service/elasticinference" + "github.com/aws/aws-sdk-go/service/elasticsearchservice" + "github.com/aws/aws-sdk-go/service/elastictranscoder" + "github.com/aws/aws-sdk-go/service/elb" + "github.com/aws/aws-sdk-go/service/elbv2" + "github.com/aws/aws-sdk-go/service/emr" + "github.com/aws/aws-sdk-go/service/emrcontainers" + "github.com/aws/aws-sdk-go/service/emrserverless" + "github.com/aws/aws-sdk-go/service/eventbridge" + "github.com/aws/aws-sdk-go/service/finspace" + "github.com/aws/aws-sdk-go/service/finspacedata" + "github.com/aws/aws-sdk-go/service/firehose" + "github.com/aws/aws-sdk-go/service/fms" + "github.com/aws/aws-sdk-go/service/forecastqueryservice" + "github.com/aws/aws-sdk-go/service/forecastservice" + "github.com/aws/aws-sdk-go/service/frauddetector" + "github.com/aws/aws-sdk-go/service/fsx" + "github.com/aws/aws-sdk-go/service/gamelift" + "github.com/aws/aws-sdk-go/service/glacier" + "github.com/aws/aws-sdk-go/service/glue" + "github.com/aws/aws-sdk-go/service/gluedatabrew" + "github.com/aws/aws-sdk-go/service/greengrass" + "github.com/aws/aws-sdk-go/service/greengrassv2" + "github.com/aws/aws-sdk-go/service/groundstation" + "github.com/aws/aws-sdk-go/service/guardduty" + "github.com/aws/aws-sdk-go/service/health" + "github.com/aws/aws-sdk-go/service/healthlake" + "github.com/aws/aws-sdk-go/service/honeycode" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/aws/aws-sdk-go/service/identitystore" + "github.com/aws/aws-sdk-go/service/imagebuilder" + "github.com/aws/aws-sdk-go/service/inspector" + "github.com/aws/aws-sdk-go/service/inspector2" + "github.com/aws/aws-sdk-go/service/iot" + "github.com/aws/aws-sdk-go/service/iot1clickdevicesservice" + "github.com/aws/aws-sdk-go/service/iot1clickprojects" + "github.com/aws/aws-sdk-go/service/iotanalytics" + "github.com/aws/aws-sdk-go/service/iotdataplane" + "github.com/aws/aws-sdk-go/service/iotdeviceadvisor" + "github.com/aws/aws-sdk-go/service/iotevents" + "github.com/aws/aws-sdk-go/service/ioteventsdata" + "github.com/aws/aws-sdk-go/service/iotfleethub" + "github.com/aws/aws-sdk-go/service/iotjobsdataplane" + "github.com/aws/aws-sdk-go/service/iotsecuretunneling" + "github.com/aws/aws-sdk-go/service/iotsitewise" + "github.com/aws/aws-sdk-go/service/iotthingsgraph" + "github.com/aws/aws-sdk-go/service/iottwinmaker" + "github.com/aws/aws-sdk-go/service/iotwireless" + "github.com/aws/aws-sdk-go/service/ivs" + "github.com/aws/aws-sdk-go/service/kafka" + "github.com/aws/aws-sdk-go/service/kafkaconnect" + "github.com/aws/aws-sdk-go/service/keyspaces" + "github.com/aws/aws-sdk-go/service/kinesis" + "github.com/aws/aws-sdk-go/service/kinesisanalytics" + "github.com/aws/aws-sdk-go/service/kinesisanalyticsv2" + "github.com/aws/aws-sdk-go/service/kinesisvideo" + "github.com/aws/aws-sdk-go/service/kinesisvideoarchivedmedia" + "github.com/aws/aws-sdk-go/service/kinesisvideomedia" + "github.com/aws/aws-sdk-go/service/kinesisvideosignalingchannels" + "github.com/aws/aws-sdk-go/service/kms" + "github.com/aws/aws-sdk-go/service/lakeformation" + "github.com/aws/aws-sdk-go/service/lambda" + "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" + "github.com/aws/aws-sdk-go/service/lexmodelsv2" + "github.com/aws/aws-sdk-go/service/lexruntimeservice" + "github.com/aws/aws-sdk-go/service/lexruntimev2" + "github.com/aws/aws-sdk-go/service/licensemanager" + "github.com/aws/aws-sdk-go/service/lightsail" + "github.com/aws/aws-sdk-go/service/locationservice" + "github.com/aws/aws-sdk-go/service/lookoutequipment" + "github.com/aws/aws-sdk-go/service/lookoutforvision" + "github.com/aws/aws-sdk-go/service/lookoutmetrics" + "github.com/aws/aws-sdk-go/service/machinelearning" + "github.com/aws/aws-sdk-go/service/macie" + "github.com/aws/aws-sdk-go/service/macie2" + "github.com/aws/aws-sdk-go/service/managedblockchain" + "github.com/aws/aws-sdk-go/service/managedgrafana" + "github.com/aws/aws-sdk-go/service/marketplacecatalog" + "github.com/aws/aws-sdk-go/service/marketplacecommerceanalytics" + "github.com/aws/aws-sdk-go/service/marketplaceentitlementservice" + "github.com/aws/aws-sdk-go/service/marketplacemetering" + "github.com/aws/aws-sdk-go/service/mediaconnect" + "github.com/aws/aws-sdk-go/service/mediaconvert" + "github.com/aws/aws-sdk-go/service/medialive" + "github.com/aws/aws-sdk-go/service/mediapackage" + "github.com/aws/aws-sdk-go/service/mediapackagevod" + "github.com/aws/aws-sdk-go/service/mediastore" + "github.com/aws/aws-sdk-go/service/mediastoredata" + "github.com/aws/aws-sdk-go/service/mediatailor" + "github.com/aws/aws-sdk-go/service/memorydb" + "github.com/aws/aws-sdk-go/service/mgn" + "github.com/aws/aws-sdk-go/service/migrationhub" + "github.com/aws/aws-sdk-go/service/migrationhubconfig" + "github.com/aws/aws-sdk-go/service/migrationhubrefactorspaces" + "github.com/aws/aws-sdk-go/service/migrationhubstrategyrecommendations" + "github.com/aws/aws-sdk-go/service/mobile" + "github.com/aws/aws-sdk-go/service/mq" + "github.com/aws/aws-sdk-go/service/mturk" + "github.com/aws/aws-sdk-go/service/mwaa" + "github.com/aws/aws-sdk-go/service/neptune" + "github.com/aws/aws-sdk-go/service/networkfirewall" + "github.com/aws/aws-sdk-go/service/networkmanager" + "github.com/aws/aws-sdk-go/service/nimblestudio" + "github.com/aws/aws-sdk-go/service/opensearchservice" + "github.com/aws/aws-sdk-go/service/opsworks" + "github.com/aws/aws-sdk-go/service/opsworkscm" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/aws/aws-sdk-go/service/outposts" + "github.com/aws/aws-sdk-go/service/panorama" + "github.com/aws/aws-sdk-go/service/personalize" + "github.com/aws/aws-sdk-go/service/personalizeevents" + "github.com/aws/aws-sdk-go/service/personalizeruntime" + "github.com/aws/aws-sdk-go/service/pi" + "github.com/aws/aws-sdk-go/service/pinpoint" + "github.com/aws/aws-sdk-go/service/pinpointemail" + "github.com/aws/aws-sdk-go/service/pinpointsmsvoice" + "github.com/aws/aws-sdk-go/service/polly" + "github.com/aws/aws-sdk-go/service/pricing" + "github.com/aws/aws-sdk-go/service/prometheusservice" + "github.com/aws/aws-sdk-go/service/proton" + "github.com/aws/aws-sdk-go/service/qldb" + "github.com/aws/aws-sdk-go/service/qldbsession" + "github.com/aws/aws-sdk-go/service/quicksight" + "github.com/aws/aws-sdk-go/service/ram" + "github.com/aws/aws-sdk-go/service/rds" + "github.com/aws/aws-sdk-go/service/rdsdataservice" + "github.com/aws/aws-sdk-go/service/recyclebin" + "github.com/aws/aws-sdk-go/service/redshift" + "github.com/aws/aws-sdk-go/service/redshiftdataapiservice" + "github.com/aws/aws-sdk-go/service/redshiftserverless" + "github.com/aws/aws-sdk-go/service/rekognition" + "github.com/aws/aws-sdk-go/service/resiliencehub" + "github.com/aws/aws-sdk-go/service/resourcegroups" + "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" + "github.com/aws/aws-sdk-go/service/robomaker" + "github.com/aws/aws-sdk-go/service/route53recoverycluster" + "github.com/aws/aws-sdk-go/service/route53resolver" + "github.com/aws/aws-sdk-go/service/s3control" + "github.com/aws/aws-sdk-go/service/s3outposts" + "github.com/aws/aws-sdk-go/service/sagemaker" + "github.com/aws/aws-sdk-go/service/sagemakeredgemanager" + "github.com/aws/aws-sdk-go/service/sagemakerfeaturestoreruntime" + "github.com/aws/aws-sdk-go/service/sagemakerruntime" + "github.com/aws/aws-sdk-go/service/savingsplans" + "github.com/aws/aws-sdk-go/service/schemas" + "github.com/aws/aws-sdk-go/service/secretsmanager" + "github.com/aws/aws-sdk-go/service/securityhub" + "github.com/aws/aws-sdk-go/service/serverlessapplicationrepository" + "github.com/aws/aws-sdk-go/service/servicecatalog" + "github.com/aws/aws-sdk-go/service/servicediscovery" + "github.com/aws/aws-sdk-go/service/servicequotas" + "github.com/aws/aws-sdk-go/service/ses" + "github.com/aws/aws-sdk-go/service/sesv2" + "github.com/aws/aws-sdk-go/service/sfn" + "github.com/aws/aws-sdk-go/service/signer" + "github.com/aws/aws-sdk-go/service/simpledb" + "github.com/aws/aws-sdk-go/service/sms" + "github.com/aws/aws-sdk-go/service/snowball" + "github.com/aws/aws-sdk-go/service/snowdevicemanagement" + "github.com/aws/aws-sdk-go/service/sns" + "github.com/aws/aws-sdk-go/service/sqs" + "github.com/aws/aws-sdk-go/service/ssm" + "github.com/aws/aws-sdk-go/service/ssmcontacts" + "github.com/aws/aws-sdk-go/service/ssmincidents" + "github.com/aws/aws-sdk-go/service/sso" + "github.com/aws/aws-sdk-go/service/ssoadmin" + "github.com/aws/aws-sdk-go/service/ssooidc" + "github.com/aws/aws-sdk-go/service/storagegateway" + "github.com/aws/aws-sdk-go/service/support" + "github.com/aws/aws-sdk-go/service/swf" + "github.com/aws/aws-sdk-go/service/synthetics" + "github.com/aws/aws-sdk-go/service/textract" + "github.com/aws/aws-sdk-go/service/timestreamquery" + "github.com/aws/aws-sdk-go/service/timestreamwrite" + "github.com/aws/aws-sdk-go/service/transcribestreamingservice" + "github.com/aws/aws-sdk-go/service/transfer" + "github.com/aws/aws-sdk-go/service/translate" + "github.com/aws/aws-sdk-go/service/voiceid" + "github.com/aws/aws-sdk-go/service/waf" + "github.com/aws/aws-sdk-go/service/wafregional" + "github.com/aws/aws-sdk-go/service/wafv2" + "github.com/aws/aws-sdk-go/service/wellarchitected" + "github.com/aws/aws-sdk-go/service/workdocs" + "github.com/aws/aws-sdk-go/service/worklink" + "github.com/aws/aws-sdk-go/service/workmail" + "github.com/aws/aws-sdk-go/service/workmailmessageflow" + "github.com/aws/aws-sdk-go/service/workspaces" + "github.com/aws/aws-sdk-go/service/workspacesweb" + "github.com/aws/aws-sdk-go/service/xray" + "github.com/cloudposse/terraform-provider-awsutils/names" +) + +func (c *Config) clientConns(sess *session.Session) *AWSClient { + return &AWSClient{ + ACMConn: acm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ACM])})), + ACMPCAConn: acmpca.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ACMPCA])})), + AMPConn: prometheusservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AMP])})), + APIGatewayConn: apigateway.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.APIGateway])})), + APIGatewayManagementAPIConn: apigatewaymanagementapi.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.APIGatewayManagementAPI])})), + APIGatewayV2Conn: apigatewayv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.APIGatewayV2])})), + AccessAnalyzerConn: accessanalyzer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AccessAnalyzer])})), + AccountConn: account.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Account])})), + AlexaForBusinessConn: alexaforbusiness.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AlexaForBusiness])})), + AmplifyConn: amplify.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Amplify])})), + AmplifyBackendConn: amplifybackend.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AmplifyBackend])})), + AmplifyUIBuilderConn: amplifyuibuilder.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AmplifyUIBuilder])})), + AppAutoScalingConn: applicationautoscaling.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppAutoScaling])})), + AppConfigConn: appconfig.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppConfig])})), + AppConfigDataConn: appconfigdata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppConfigData])})), + AppFlowConn: appflow.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppFlow])})), + AppIntegrationsConn: appintegrationsservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppIntegrations])})), + AppMeshConn: appmesh.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppMesh])})), + AppRunnerConn: apprunner.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppRunner])})), + AppStreamConn: appstream.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppStream])})), + AppSyncConn: appsync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AppSync])})), + ApplicationCostProfilerConn: applicationcostprofiler.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ApplicationCostProfiler])})), + ApplicationInsightsConn: applicationinsights.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ApplicationInsights])})), + AthenaConn: athena.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Athena])})), + AuditManagerConn: auditmanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AuditManager])})), + AutoScalingConn: autoscaling.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AutoScaling])})), + AutoScalingPlansConn: autoscalingplans.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.AutoScalingPlans])})), + BackupConn: backup.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Backup])})), + BackupGatewayConn: backupgateway.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.BackupGateway])})), + BatchConn: batch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Batch])})), + BillingConductorConn: billingconductor.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.BillingConductor])})), + BraketConn: braket.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Braket])})), + BudgetsConn: budgets.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Budgets])})), + CEConn: costexplorer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CE])})), + CURConn: costandusagereportservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CUR])})), + ChimeConn: chime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Chime])})), + ChimeSDKIdentityConn: chimesdkidentity.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ChimeSDKIdentity])})), + ChimeSDKMeetingsConn: chimesdkmeetings.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ChimeSDKMeetings])})), + ChimeSDKMessagingConn: chimesdkmessaging.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ChimeSDKMessaging])})), + Cloud9Conn: cloud9.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Cloud9])})), + CloudControlConn: cloudcontrolapi.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudControl])})), + CloudDirectoryConn: clouddirectory.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudDirectory])})), + CloudFormationConn: cloudformation.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudFormation])})), + CloudFrontConn: cloudfront.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudFront])})), + CloudHSMV2Conn: cloudhsmv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudHSMV2])})), + CloudSearchConn: cloudsearch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudSearch])})), + CloudSearchDomainConn: cloudsearchdomain.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudSearchDomain])})), + CloudTrailConn: cloudtrail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudTrail])})), + CloudWatchConn: cloudwatch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CloudWatch])})), + CodeArtifactConn: codeartifact.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeArtifact])})), + CodeBuildConn: codebuild.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeBuild])})), + CodeCommitConn: codecommit.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeCommit])})), + CodeGuruProfilerConn: codeguruprofiler.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeGuruProfiler])})), + CodeGuruReviewerConn: codegurureviewer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeGuruReviewer])})), + CodePipelineConn: codepipeline.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodePipeline])})), + CodeStarConn: codestar.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeStar])})), + CodeStarConnectionsConn: codestarconnections.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeStarConnections])})), + CodeStarNotificationsConn: codestarnotifications.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CodeStarNotifications])})), + CognitoIDPConn: cognitoidentityprovider.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CognitoIDP])})), + CognitoIdentityConn: cognitoidentity.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CognitoIdentity])})), + CognitoSyncConn: cognitosync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CognitoSync])})), + ComprehendConn: comprehend.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Comprehend])})), + ComprehendMedicalConn: comprehendmedical.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ComprehendMedical])})), + ComputeOptimizerConn: computeoptimizer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ComputeOptimizer])})), + ConfigServiceConn: configservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ConfigService])})), + ConnectConn: connect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Connect])})), + ConnectContactLensConn: connectcontactlens.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ConnectContactLens])})), + ConnectParticipantConn: connectparticipant.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ConnectParticipant])})), + CustomerProfilesConn: customerprofiles.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.CustomerProfiles])})), + DAXConn: dax.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DAX])})), + DLMConn: dlm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DLM])})), + DMSConn: databasemigrationservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DMS])})), + DRSConn: drs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DRS])})), + DSConn: directoryservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DS])})), + DataBrewConn: gluedatabrew.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DataBrew])})), + DataExchangeConn: dataexchange.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DataExchange])})), + DataPipelineConn: datapipeline.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DataPipeline])})), + DataSyncConn: datasync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DataSync])})), + DeployConn: codedeploy.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Deploy])})), + DetectiveConn: detective.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Detective])})), + DevOpsGuruConn: devopsguru.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DevOpsGuru])})), + DeviceFarmConn: devicefarm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DeviceFarm])})), + DirectConnectConn: directconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DirectConnect])})), + DiscoveryConn: applicationdiscoveryservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Discovery])})), + DocDBConn: docdb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DocDB])})), + DynamoDBConn: dynamodb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DynamoDB])})), + DynamoDBStreamsConn: dynamodbstreams.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.DynamoDBStreams])})), + EBSConn: ebs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EBS])})), + EC2Conn: ec2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EC2])})), + EC2InstanceConnectConn: ec2instanceconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EC2InstanceConnect])})), + ECRConn: ecr.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ECR])})), + ECRPublicConn: ecrpublic.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ECRPublic])})), + ECSConn: ecs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ECS])})), + EFSConn: efs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EFS])})), + EKSConn: eks.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EKS])})), + ELBConn: elb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ELB])})), + ELBV2Conn: elbv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ELBV2])})), + EMRConn: emr.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EMR])})), + EMRContainersConn: emrcontainers.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EMRContainers])})), + EMRServerlessConn: emrserverless.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.EMRServerless])})), + ElastiCacheConn: elasticache.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ElastiCache])})), + ElasticBeanstalkConn: elasticbeanstalk.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ElasticBeanstalk])})), + ElasticInferenceConn: elasticinference.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ElasticInference])})), + ElasticTranscoderConn: elastictranscoder.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ElasticTranscoder])})), + ElasticsearchConn: elasticsearchservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Elasticsearch])})), + EventsConn: eventbridge.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Events])})), + EvidentlyConn: cloudwatchevidently.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Evidently])})), + FMSConn: fms.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.FMS])})), + FSxConn: fsx.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.FSx])})), + FinSpaceConn: finspace.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.FinSpace])})), + FinSpaceDataConn: finspacedata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.FinSpaceData])})), + FirehoseConn: firehose.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Firehose])})), + ForecastConn: forecastservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Forecast])})), + ForecastQueryConn: forecastqueryservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ForecastQuery])})), + FraudDetectorConn: frauddetector.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.FraudDetector])})), + GameLiftConn: gamelift.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.GameLift])})), + GlacierConn: glacier.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Glacier])})), + GlueConn: glue.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Glue])})), + GrafanaConn: managedgrafana.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Grafana])})), + GreengrassConn: greengrass.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Greengrass])})), + GreengrassV2Conn: greengrassv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.GreengrassV2])})), + GroundStationConn: groundstation.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.GroundStation])})), + GuardDutyConn: guardduty.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.GuardDuty])})), + HealthConn: health.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Health])})), + HealthLakeConn: healthlake.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.HealthLake])})), + HoneycodeConn: honeycode.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Honeycode])})), + IAMConn: iam.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IAM])})), + IVSConn: ivs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IVS])})), + IdentityStoreConn: identitystore.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IdentityStore])})), + ImageBuilderConn: imagebuilder.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ImageBuilder])})), + InspectorConn: inspector.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Inspector])})), + Inspector2Conn: inspector2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Inspector2])})), + IoTConn: iot.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoT])})), + IoT1ClickDevicesConn: iot1clickdevicesservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoT1ClickDevices])})), + IoT1ClickProjectsConn: iot1clickprojects.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoT1ClickProjects])})), + IoTAnalyticsConn: iotanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTAnalytics])})), + IoTDataConn: iotdataplane.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTData])})), + IoTDeviceAdvisorConn: iotdeviceadvisor.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTDeviceAdvisor])})), + IoTEventsConn: iotevents.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTEvents])})), + IoTEventsDataConn: ioteventsdata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTEventsData])})), + IoTFleetHubConn: iotfleethub.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTFleetHub])})), + IoTJobsDataConn: iotjobsdataplane.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTJobsData])})), + IoTSecureTunnelingConn: iotsecuretunneling.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTSecureTunneling])})), + IoTSiteWiseConn: iotsitewise.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTSiteWise])})), + IoTThingsGraphConn: iotthingsgraph.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTThingsGraph])})), + IoTTwinMakerConn: iottwinmaker.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTTwinMaker])})), + IoTWirelessConn: iotwireless.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.IoTWireless])})), + KMSConn: kms.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KMS])})), + KafkaConn: kafka.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Kafka])})), + KafkaConnectConn: kafkaconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KafkaConnect])})), + KeyspacesConn: keyspaces.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Keyspaces])})), + KinesisConn: kinesis.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Kinesis])})), + KinesisAnalyticsConn: kinesisanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KinesisAnalytics])})), + KinesisAnalyticsV2Conn: kinesisanalyticsv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KinesisAnalyticsV2])})), + KinesisVideoConn: kinesisvideo.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KinesisVideo])})), + KinesisVideoArchivedMediaConn: kinesisvideoarchivedmedia.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KinesisVideoArchivedMedia])})), + KinesisVideoMediaConn: kinesisvideomedia.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KinesisVideoMedia])})), + KinesisVideoSignalingConn: kinesisvideosignalingchannels.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.KinesisVideoSignaling])})), + LakeFormationConn: lakeformation.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LakeFormation])})), + LambdaConn: lambda.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Lambda])})), + LexModelsConn: lexmodelbuildingservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LexModels])})), + LexModelsV2Conn: lexmodelsv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LexModelsV2])})), + LexRuntimeConn: lexruntimeservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LexRuntime])})), + LexRuntimeV2Conn: lexruntimev2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LexRuntimeV2])})), + LicenseManagerConn: licensemanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LicenseManager])})), + LightsailConn: lightsail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Lightsail])})), + LocationConn: locationservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Location])})), + LogsConn: cloudwatchlogs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Logs])})), + LookoutEquipmentConn: lookoutequipment.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LookoutEquipment])})), + LookoutMetricsConn: lookoutmetrics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LookoutMetrics])})), + LookoutVisionConn: lookoutforvision.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.LookoutVision])})), + MQConn: mq.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MQ])})), + MTurkConn: mturk.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MTurk])})), + MWAAConn: mwaa.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MWAA])})), + MachineLearningConn: machinelearning.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MachineLearning])})), + MacieConn: macie.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Macie])})), + Macie2Conn: macie2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Macie2])})), + ManagedBlockchainConn: managedblockchain.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ManagedBlockchain])})), + MarketplaceCatalogConn: marketplacecatalog.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MarketplaceCatalog])})), + MarketplaceCommerceAnalyticsConn: marketplacecommerceanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MarketplaceCommerceAnalytics])})), + MarketplaceEntitlementConn: marketplaceentitlementservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MarketplaceEntitlement])})), + MarketplaceMeteringConn: marketplacemetering.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MarketplaceMetering])})), + MediaConnectConn: mediaconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaConnect])})), + MediaConvertConn: mediaconvert.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaConvert])})), + MediaLiveConn: medialive.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaLive])})), + MediaPackageConn: mediapackage.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaPackage])})), + MediaPackageVODConn: mediapackagevod.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaPackageVOD])})), + MediaStoreConn: mediastore.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaStore])})), + MediaStoreDataConn: mediastoredata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaStoreData])})), + MediaTailorConn: mediatailor.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MediaTailor])})), + MemoryDBConn: memorydb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MemoryDB])})), + MgHConn: migrationhub.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MgH])})), + MgnConn: mgn.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Mgn])})), + MigrationHubConfigConn: migrationhubconfig.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MigrationHubConfig])})), + MigrationHubRefactorSpacesConn: migrationhubrefactorspaces.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MigrationHubRefactorSpaces])})), + MigrationHubStrategyConn: migrationhubstrategyrecommendations.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.MigrationHubStrategy])})), + MobileConn: mobile.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Mobile])})), + NeptuneConn: neptune.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Neptune])})), + NetworkFirewallConn: networkfirewall.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.NetworkFirewall])})), + NetworkManagerConn: networkmanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.NetworkManager])})), + NimbleConn: nimblestudio.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Nimble])})), + OpenSearchConn: opensearchservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.OpenSearch])})), + OpsWorksConn: opsworks.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.OpsWorks])})), + OpsWorksCMConn: opsworkscm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.OpsWorksCM])})), + OrganizationsConn: organizations.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Organizations])})), + OutpostsConn: outposts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Outposts])})), + PIConn: pi.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.PI])})), + PanoramaConn: panorama.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Panorama])})), + PersonalizeConn: personalize.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Personalize])})), + PersonalizeEventsConn: personalizeevents.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.PersonalizeEvents])})), + PersonalizeRuntimeConn: personalizeruntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.PersonalizeRuntime])})), + PinpointConn: pinpoint.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Pinpoint])})), + PinpointEmailConn: pinpointemail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.PinpointEmail])})), + PinpointSMSVoiceConn: pinpointsmsvoice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.PinpointSMSVoice])})), + PollyConn: polly.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Polly])})), + PricingConn: pricing.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Pricing])})), + ProtonConn: proton.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Proton])})), + QLDBConn: qldb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.QLDB])})), + QLDBSessionConn: qldbsession.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.QLDBSession])})), + QuickSightConn: quicksight.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.QuickSight])})), + RAMConn: ram.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RAM])})), + RBinConn: recyclebin.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RBin])})), + RDSConn: rds.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RDS])})), + RDSDataConn: rdsdataservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RDSData])})), + RUMConn: cloudwatchrum.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RUM])})), + RedshiftConn: redshift.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Redshift])})), + RedshiftDataConn: redshiftdataapiservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RedshiftData])})), + RedshiftServerlessConn: redshiftserverless.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RedshiftServerless])})), + RekognitionConn: rekognition.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Rekognition])})), + ResilienceHubConn: resiliencehub.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ResilienceHub])})), + ResourceGroupsConn: resourcegroups.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ResourceGroups])})), + ResourceGroupsTaggingAPIConn: resourcegroupstaggingapi.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ResourceGroupsTaggingAPI])})), + RoboMakerConn: robomaker.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.RoboMaker])})), + Route53RecoveryClusterConn: route53recoverycluster.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Route53RecoveryCluster])})), + Route53ResolverConn: route53resolver.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Route53Resolver])})), + S3ControlConn: s3control.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.S3Control])})), + S3OutpostsConn: s3outposts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.S3Outposts])})), + SESConn: ses.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SES])})), + SESV2Conn: sesv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SESV2])})), + SFNConn: sfn.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SFN])})), + SMSConn: sms.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SMS])})), + SNSConn: sns.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SNS])})), + SQSConn: sqs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SQS])})), + SSMConn: ssm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SSM])})), + SSMContactsConn: ssmcontacts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SSMContacts])})), + SSMIncidentsConn: ssmincidents.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SSMIncidents])})), + SSOConn: sso.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SSO])})), + SSOAdminConn: ssoadmin.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SSOAdmin])})), + SSOOIDCConn: ssooidc.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SSOOIDC])})), + SWFConn: swf.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SWF])})), + SageMakerConn: sagemaker.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SageMaker])})), + SageMakerA2IRuntimeConn: augmentedairuntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SageMakerA2IRuntime])})), + SageMakerEdgeConn: sagemakeredgemanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SageMakerEdge])})), + SageMakerFeatureStoreRuntimeConn: sagemakerfeaturestoreruntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SageMakerFeatureStoreRuntime])})), + SageMakerRuntimeConn: sagemakerruntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SageMakerRuntime])})), + SavingsPlansConn: savingsplans.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SavingsPlans])})), + SchemasConn: schemas.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Schemas])})), + SecretsManagerConn: secretsmanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SecretsManager])})), + SecurityHubConn: securityhub.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SecurityHub])})), + ServerlessRepoConn: serverlessapplicationrepository.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ServerlessRepo])})), + ServiceCatalogConn: servicecatalog.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ServiceCatalog])})), + ServiceCatalogAppRegistryConn: appregistry.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ServiceCatalogAppRegistry])})), + ServiceDiscoveryConn: servicediscovery.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ServiceDiscovery])})), + ServiceQuotasConn: servicequotas.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.ServiceQuotas])})), + SignerConn: signer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Signer])})), + SimpleDBConn: simpledb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SimpleDB])})), + SnowDeviceManagementConn: snowdevicemanagement.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.SnowDeviceManagement])})), + SnowballConn: snowball.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Snowball])})), + StorageGatewayConn: storagegateway.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.StorageGateway])})), + SupportConn: support.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Support])})), + SyntheticsConn: synthetics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Synthetics])})), + TextractConn: textract.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Textract])})), + TimestreamQueryConn: timestreamquery.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.TimestreamQuery])})), + TimestreamWriteConn: timestreamwrite.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.TimestreamWrite])})), + TranscribeStreamingConn: transcribestreamingservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.TranscribeStreaming])})), + TransferConn: transfer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Transfer])})), + TranslateConn: translate.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Translate])})), + VoiceIDConn: voiceid.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.VoiceID])})), + WAFConn: waf.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WAF])})), + WAFRegionalConn: wafregional.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WAFRegional])})), + WAFV2Conn: wafv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WAFV2])})), + WellArchitectedConn: wellarchitected.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WellArchitected])})), + WisdomConn: connectwisdomservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.Wisdom])})), + WorkDocsConn: workdocs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WorkDocs])})), + WorkLinkConn: worklink.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WorkLink])})), + WorkMailConn: workmail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WorkMail])})), + WorkMailMessageFlowConn: workmailmessageflow.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WorkMailMessageFlow])})), + WorkSpacesConn: workspaces.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WorkSpaces])})), + WorkSpacesWebConn: workspacesweb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.WorkSpacesWeb])})), + XRayConn: xray.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[names.XRay])})), + } +} diff --git a/internal/conns/conns.go b/internal/conns/conns.go index bec84af..883f055 100644 --- a/internal/conns/conns.go +++ b/internal/conns/conns.go @@ -2,1831 +2,40 @@ package conns import ( "fmt" - "log" "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/endpoints" - "github.com/aws/aws-sdk-go/aws/request" "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/accessanalyzer" - "github.com/aws/aws-sdk-go/service/acm" - "github.com/aws/aws-sdk-go/service/acmpca" - "github.com/aws/aws-sdk-go/service/alexaforbusiness" - "github.com/aws/aws-sdk-go/service/amplify" - "github.com/aws/aws-sdk-go/service/amplifybackend" - "github.com/aws/aws-sdk-go/service/apigateway" - "github.com/aws/aws-sdk-go/service/apigatewayv2" - "github.com/aws/aws-sdk-go/service/appconfig" - "github.com/aws/aws-sdk-go/service/appflow" - "github.com/aws/aws-sdk-go/service/appintegrationsservice" - "github.com/aws/aws-sdk-go/service/applicationautoscaling" - "github.com/aws/aws-sdk-go/service/applicationcostprofiler" - "github.com/aws/aws-sdk-go/service/applicationdiscoveryservice" - "github.com/aws/aws-sdk-go/service/applicationinsights" - "github.com/aws/aws-sdk-go/service/appmesh" - "github.com/aws/aws-sdk-go/service/appregistry" - "github.com/aws/aws-sdk-go/service/apprunner" - "github.com/aws/aws-sdk-go/service/appstream" - "github.com/aws/aws-sdk-go/service/appsync" - "github.com/aws/aws-sdk-go/service/athena" - "github.com/aws/aws-sdk-go/service/auditmanager" - "github.com/aws/aws-sdk-go/service/augmentedairuntime" - "github.com/aws/aws-sdk-go/service/autoscaling" - "github.com/aws/aws-sdk-go/service/autoscalingplans" - "github.com/aws/aws-sdk-go/service/backup" - "github.com/aws/aws-sdk-go/service/batch" - "github.com/aws/aws-sdk-go/service/braket" - "github.com/aws/aws-sdk-go/service/budgets" - "github.com/aws/aws-sdk-go/service/chime" - "github.com/aws/aws-sdk-go/service/cloud9" - "github.com/aws/aws-sdk-go/service/cloudcontrolapi" - "github.com/aws/aws-sdk-go/service/clouddirectory" - "github.com/aws/aws-sdk-go/service/cloudformation" - "github.com/aws/aws-sdk-go/service/cloudfront" - "github.com/aws/aws-sdk-go/service/cloudhsmv2" - "github.com/aws/aws-sdk-go/service/cloudsearch" - "github.com/aws/aws-sdk-go/service/cloudsearchdomain" - "github.com/aws/aws-sdk-go/service/cloudtrail" - "github.com/aws/aws-sdk-go/service/cloudwatch" - "github.com/aws/aws-sdk-go/service/cloudwatchlogs" - "github.com/aws/aws-sdk-go/service/codeartifact" - "github.com/aws/aws-sdk-go/service/codebuild" - "github.com/aws/aws-sdk-go/service/codecommit" - "github.com/aws/aws-sdk-go/service/codedeploy" - "github.com/aws/aws-sdk-go/service/codeguruprofiler" - "github.com/aws/aws-sdk-go/service/codegurureviewer" - "github.com/aws/aws-sdk-go/service/codepipeline" - "github.com/aws/aws-sdk-go/service/codestar" - "github.com/aws/aws-sdk-go/service/codestarconnections" - "github.com/aws/aws-sdk-go/service/codestarnotifications" - "github.com/aws/aws-sdk-go/service/cognitoidentity" - "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" - "github.com/aws/aws-sdk-go/service/cognitosync" - "github.com/aws/aws-sdk-go/service/comprehend" - "github.com/aws/aws-sdk-go/service/comprehendmedical" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/aws/aws-sdk-go/service/connect" - "github.com/aws/aws-sdk-go/service/connectcontactlens" - "github.com/aws/aws-sdk-go/service/connectparticipant" - "github.com/aws/aws-sdk-go/service/costandusagereportservice" - "github.com/aws/aws-sdk-go/service/costexplorer" - "github.com/aws/aws-sdk-go/service/databasemigrationservice" - "github.com/aws/aws-sdk-go/service/dataexchange" - "github.com/aws/aws-sdk-go/service/datapipeline" - "github.com/aws/aws-sdk-go/service/datasync" - "github.com/aws/aws-sdk-go/service/dax" - "github.com/aws/aws-sdk-go/service/detective" - "github.com/aws/aws-sdk-go/service/devicefarm" - "github.com/aws/aws-sdk-go/service/devopsguru" - "github.com/aws/aws-sdk-go/service/directconnect" - "github.com/aws/aws-sdk-go/service/directoryservice" - "github.com/aws/aws-sdk-go/service/dlm" - "github.com/aws/aws-sdk-go/service/docdb" - "github.com/aws/aws-sdk-go/service/dynamodb" - "github.com/aws/aws-sdk-go/service/dynamodbstreams" "github.com/aws/aws-sdk-go/service/ec2" - "github.com/aws/aws-sdk-go/service/ec2instanceconnect" - "github.com/aws/aws-sdk-go/service/ecr" - "github.com/aws/aws-sdk-go/service/ecrpublic" - "github.com/aws/aws-sdk-go/service/ecs" - "github.com/aws/aws-sdk-go/service/efs" - "github.com/aws/aws-sdk-go/service/eks" - "github.com/aws/aws-sdk-go/service/elasticache" - "github.com/aws/aws-sdk-go/service/elasticbeanstalk" - "github.com/aws/aws-sdk-go/service/elasticinference" - elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" - "github.com/aws/aws-sdk-go/service/elastictranscoder" - "github.com/aws/aws-sdk-go/service/elb" - "github.com/aws/aws-sdk-go/service/elbv2" - "github.com/aws/aws-sdk-go/service/emr" - "github.com/aws/aws-sdk-go/service/emrcontainers" - "github.com/aws/aws-sdk-go/service/eventbridge" - "github.com/aws/aws-sdk-go/service/finspace" - "github.com/aws/aws-sdk-go/service/finspacedata" - "github.com/aws/aws-sdk-go/service/firehose" - "github.com/aws/aws-sdk-go/service/fis" - "github.com/aws/aws-sdk-go/service/fms" - "github.com/aws/aws-sdk-go/service/forecastqueryservice" - "github.com/aws/aws-sdk-go/service/forecastservice" - "github.com/aws/aws-sdk-go/service/frauddetector" - "github.com/aws/aws-sdk-go/service/fsx" - "github.com/aws/aws-sdk-go/service/gamelift" - "github.com/aws/aws-sdk-go/service/glacier" - "github.com/aws/aws-sdk-go/service/globalaccelerator" - "github.com/aws/aws-sdk-go/service/glue" - "github.com/aws/aws-sdk-go/service/gluedatabrew" - "github.com/aws/aws-sdk-go/service/greengrass" - "github.com/aws/aws-sdk-go/service/greengrassv2" - "github.com/aws/aws-sdk-go/service/groundstation" - "github.com/aws/aws-sdk-go/service/guardduty" - "github.com/aws/aws-sdk-go/service/health" - "github.com/aws/aws-sdk-go/service/healthlake" - "github.com/aws/aws-sdk-go/service/honeycode" - "github.com/aws/aws-sdk-go/service/iam" - "github.com/aws/aws-sdk-go/service/identitystore" - "github.com/aws/aws-sdk-go/service/imagebuilder" - "github.com/aws/aws-sdk-go/service/inspector" - "github.com/aws/aws-sdk-go/service/iot" - "github.com/aws/aws-sdk-go/service/iot1clickdevicesservice" - "github.com/aws/aws-sdk-go/service/iot1clickprojects" - "github.com/aws/aws-sdk-go/service/iotanalytics" - "github.com/aws/aws-sdk-go/service/iotdataplane" - "github.com/aws/aws-sdk-go/service/iotdeviceadvisor" - "github.com/aws/aws-sdk-go/service/iotevents" - "github.com/aws/aws-sdk-go/service/ioteventsdata" - "github.com/aws/aws-sdk-go/service/iotfleethub" - "github.com/aws/aws-sdk-go/service/iotjobsdataplane" - "github.com/aws/aws-sdk-go/service/iotsecuretunneling" - "github.com/aws/aws-sdk-go/service/iotsitewise" - "github.com/aws/aws-sdk-go/service/iotthingsgraph" - "github.com/aws/aws-sdk-go/service/iotwireless" - "github.com/aws/aws-sdk-go/service/kafka" - "github.com/aws/aws-sdk-go/service/kendra" - "github.com/aws/aws-sdk-go/service/kinesis" - "github.com/aws/aws-sdk-go/service/kinesisanalytics" - "github.com/aws/aws-sdk-go/service/kinesisanalyticsv2" - "github.com/aws/aws-sdk-go/service/kinesisvideo" - "github.com/aws/aws-sdk-go/service/kinesisvideoarchivedmedia" - "github.com/aws/aws-sdk-go/service/kinesisvideomedia" - "github.com/aws/aws-sdk-go/service/kinesisvideosignalingchannels" - "github.com/aws/aws-sdk-go/service/kms" - "github.com/aws/aws-sdk-go/service/lakeformation" - "github.com/aws/aws-sdk-go/service/lambda" - "github.com/aws/aws-sdk-go/service/lexmodelbuildingservice" - "github.com/aws/aws-sdk-go/service/lexmodelsv2" - "github.com/aws/aws-sdk-go/service/lexruntimeservice" - "github.com/aws/aws-sdk-go/service/lexruntimev2" - "github.com/aws/aws-sdk-go/service/licensemanager" - "github.com/aws/aws-sdk-go/service/lightsail" - "github.com/aws/aws-sdk-go/service/locationservice" - "github.com/aws/aws-sdk-go/service/lookoutequipment" - "github.com/aws/aws-sdk-go/service/lookoutforvision" - "github.com/aws/aws-sdk-go/service/lookoutmetrics" - "github.com/aws/aws-sdk-go/service/machinelearning" - "github.com/aws/aws-sdk-go/service/macie" - "github.com/aws/aws-sdk-go/service/macie2" - "github.com/aws/aws-sdk-go/service/managedblockchain" - "github.com/aws/aws-sdk-go/service/marketplacecatalog" - "github.com/aws/aws-sdk-go/service/marketplacecommerceanalytics" - "github.com/aws/aws-sdk-go/service/marketplaceentitlementservice" - "github.com/aws/aws-sdk-go/service/marketplacemetering" - "github.com/aws/aws-sdk-go/service/mediaconnect" - "github.com/aws/aws-sdk-go/service/mediaconvert" - "github.com/aws/aws-sdk-go/service/medialive" - "github.com/aws/aws-sdk-go/service/mediapackage" - "github.com/aws/aws-sdk-go/service/mediapackagevod" - "github.com/aws/aws-sdk-go/service/mediastore" - "github.com/aws/aws-sdk-go/service/mediastoredata" - "github.com/aws/aws-sdk-go/service/mediatailor" - "github.com/aws/aws-sdk-go/service/memorydb" - "github.com/aws/aws-sdk-go/service/mgn" - "github.com/aws/aws-sdk-go/service/migrationhub" - "github.com/aws/aws-sdk-go/service/migrationhubconfig" - "github.com/aws/aws-sdk-go/service/mobile" - "github.com/aws/aws-sdk-go/service/mobileanalytics" - "github.com/aws/aws-sdk-go/service/mq" - "github.com/aws/aws-sdk-go/service/mturk" - "github.com/aws/aws-sdk-go/service/mwaa" - "github.com/aws/aws-sdk-go/service/neptune" - "github.com/aws/aws-sdk-go/service/networkfirewall" - "github.com/aws/aws-sdk-go/service/networkmanager" - "github.com/aws/aws-sdk-go/service/nimblestudio" - "github.com/aws/aws-sdk-go/service/opsworks" - "github.com/aws/aws-sdk-go/service/opsworkscm" - "github.com/aws/aws-sdk-go/service/organizations" - "github.com/aws/aws-sdk-go/service/outposts" - "github.com/aws/aws-sdk-go/service/personalize" - "github.com/aws/aws-sdk-go/service/personalizeevents" - "github.com/aws/aws-sdk-go/service/personalizeruntime" - "github.com/aws/aws-sdk-go/service/pi" - "github.com/aws/aws-sdk-go/service/pinpoint" - "github.com/aws/aws-sdk-go/service/pinpointemail" - "github.com/aws/aws-sdk-go/service/pinpointsmsvoice" - "github.com/aws/aws-sdk-go/service/polly" - "github.com/aws/aws-sdk-go/service/pricing" - "github.com/aws/aws-sdk-go/service/prometheusservice" - "github.com/aws/aws-sdk-go/service/proton" - "github.com/aws/aws-sdk-go/service/qldb" - "github.com/aws/aws-sdk-go/service/qldbsession" - "github.com/aws/aws-sdk-go/service/quicksight" - "github.com/aws/aws-sdk-go/service/ram" - "github.com/aws/aws-sdk-go/service/rds" - "github.com/aws/aws-sdk-go/service/rdsdataservice" - "github.com/aws/aws-sdk-go/service/redshift" - "github.com/aws/aws-sdk-go/service/redshiftdataapiservice" - "github.com/aws/aws-sdk-go/service/rekognition" - "github.com/aws/aws-sdk-go/service/resourcegroups" - "github.com/aws/aws-sdk-go/service/resourcegroupstaggingapi" - "github.com/aws/aws-sdk-go/service/robomaker" - "github.com/aws/aws-sdk-go/service/route53" - "github.com/aws/aws-sdk-go/service/route53domains" - "github.com/aws/aws-sdk-go/service/route53recoverycontrolconfig" - "github.com/aws/aws-sdk-go/service/route53recoveryreadiness" - "github.com/aws/aws-sdk-go/service/route53resolver" - "github.com/aws/aws-sdk-go/service/s3" - "github.com/aws/aws-sdk-go/service/s3control" - "github.com/aws/aws-sdk-go/service/s3outposts" - "github.com/aws/aws-sdk-go/service/sagemaker" - "github.com/aws/aws-sdk-go/service/sagemakeredgemanager" - "github.com/aws/aws-sdk-go/service/sagemakerfeaturestoreruntime" - "github.com/aws/aws-sdk-go/service/sagemakerruntime" - "github.com/aws/aws-sdk-go/service/savingsplans" - "github.com/aws/aws-sdk-go/service/schemas" - "github.com/aws/aws-sdk-go/service/secretsmanager" - "github.com/aws/aws-sdk-go/service/securityhub" - "github.com/aws/aws-sdk-go/service/serverlessapplicationrepository" - "github.com/aws/aws-sdk-go/service/servicecatalog" - "github.com/aws/aws-sdk-go/service/servicediscovery" - "github.com/aws/aws-sdk-go/service/servicequotas" - "github.com/aws/aws-sdk-go/service/ses" - "github.com/aws/aws-sdk-go/service/sesv2" - "github.com/aws/aws-sdk-go/service/sfn" - "github.com/aws/aws-sdk-go/service/shield" - "github.com/aws/aws-sdk-go/service/signer" - "github.com/aws/aws-sdk-go/service/simpledb" - "github.com/aws/aws-sdk-go/service/sms" - "github.com/aws/aws-sdk-go/service/snowball" - "github.com/aws/aws-sdk-go/service/sns" - "github.com/aws/aws-sdk-go/service/sqs" - "github.com/aws/aws-sdk-go/service/ssm" - "github.com/aws/aws-sdk-go/service/ssmcontacts" - "github.com/aws/aws-sdk-go/service/ssmincidents" - "github.com/aws/aws-sdk-go/service/sso" - "github.com/aws/aws-sdk-go/service/ssoadmin" - "github.com/aws/aws-sdk-go/service/ssooidc" - "github.com/aws/aws-sdk-go/service/storagegateway" - "github.com/aws/aws-sdk-go/service/sts" - "github.com/aws/aws-sdk-go/service/support" - "github.com/aws/aws-sdk-go/service/swf" - "github.com/aws/aws-sdk-go/service/synthetics" - "github.com/aws/aws-sdk-go/service/textract" - "github.com/aws/aws-sdk-go/service/timestreamquery" - "github.com/aws/aws-sdk-go/service/timestreamwrite" - "github.com/aws/aws-sdk-go/service/transcribeservice" - "github.com/aws/aws-sdk-go/service/transcribestreamingservice" - "github.com/aws/aws-sdk-go/service/transfer" - "github.com/aws/aws-sdk-go/service/translate" - "github.com/aws/aws-sdk-go/service/waf" - "github.com/aws/aws-sdk-go/service/wafregional" - "github.com/aws/aws-sdk-go/service/wafv2" - "github.com/aws/aws-sdk-go/service/wellarchitected" - "github.com/aws/aws-sdk-go/service/workdocs" - "github.com/aws/aws-sdk-go/service/worklink" - "github.com/aws/aws-sdk-go/service/workmail" - "github.com/aws/aws-sdk-go/service/workmailmessageflow" - "github.com/aws/aws-sdk-go/service/workspaces" - "github.com/aws/aws-sdk-go/service/xray" - tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" "github.com/cloudposse/terraform-provider-awsutils/version" - awsbase "github.com/hashicorp/aws-sdk-go-base" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" + awsbase "github.com/hashicorp/aws-sdk-go-base/v2" + awsbasev1 "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2" ) -const ( - AccessAnalyzer = "accessanalyzer" - ACM = "acm" - ACMPCA = "acmpca" - AlexaForBusiness = "alexaforbusiness" - AMP = "amp" - Amplify = "amplify" - AmplifyBackend = "amplifybackend" - APIGateway = "apigateway" - APIGatewayV2 = "apigatewayv2" - AppAutoScaling = "appautoscaling" - AppConfig = "appconfig" - AppFlow = "appflow" - AppIntegrations = "appintegrations" - ApplicationCostProfiler = "applicationcostprofiler" - ApplicationDiscovery = "applicationdiscovery" - ApplicationInsights = "applicationinsights" - AppMesh = "appmesh" - AppRegistry = "appregistry" - AppRunner = "apprunner" - AppStream = "appstream" - AppSync = "appsync" - Athena = "athena" - AuditManager = "auditmanager" - AugmentedAIRuntime = "augmentedairuntime" - AutoScaling = "autoscaling" - AutoScalingPlans = "autoscalingplans" - Backup = "backup" - Batch = "batch" - Braket = "braket" - Budgets = "budgets" - Chime = "chime" - Cloud9 = "cloud9" - CloudControl = "cloudcontrol" - CloudDirectory = "clouddirectory" - CloudFormation = "cloudformation" - CloudFront = "cloudfront" - CloudHSMV2 = "cloudhsmv2" - CloudSearch = "cloudsearch" - CloudSearchDomain = "cloudsearchdomain" - CloudTrail = "cloudtrail" - CloudWatch = "cloudwatch" - CloudWatchLogs = "cloudwatchlogs" - CodeArtifact = "codeartifact" - CodeBuild = "codebuild" - CodeCommit = "codecommit" - CodeDeploy = "codedeploy" - CodeGuruProfiler = "codeguruprofiler" - CodeGuruReviewer = "codegurureviewer" - CodePipeline = "codepipeline" - CodeStar = "codestar" - CodeStarConnections = "codestarconnections" - CodeStarNotifications = "codestarnotifications" - CognitoIdentity = "cognitoidentity" - CognitoIDP = "cognitoidp" - CognitoSync = "cognitosync" - Comprehend = "comprehend" - ComprehendMedical = "comprehendmedical" - ComputeOptimizer = "computeoptimizer" - ConfigService = "config" - Connect = "connect" - ConnectContactLens = "connectcontactlens" - ConnectParticipant = "connectparticipant" - CostExplorer = "costexplorer" - CUR = "cur" - CustomerProfiles = "customerprofiles" - DataExchange = "dataexchange" - DataPipeline = "datapipeline" - DataSync = "datasync" - DAX = "dax" - Detective = "detective" - DeviceFarm = "devicefarm" - DevOpsGuru = "devopsguru" - DirectConnect = "directconnect" - DLM = "dlm" - DMS = "dms" - DocDB = "docdb" - DS = "ds" - DynamoDB = "dynamodb" - DynamoDBStreams = "dynamodbstreams" - EC2 = "ec2" - EC2InstanceConnect = "ec2instanceconnect" - ECR = "ecr" - ECRPublic = "ecrpublic" - ECS = "ecs" - EFS = "efs" - EKS = "eks" - ElastiCache = "elasticache" - ElasticBeanstalk = "elasticbeanstalk" - ElasticInference = "elasticinference" - Elasticsearch = "elasticsearch" - ElasticTranscoder = "elastictranscoder" - ELB = "elb" - ELBV2 = "elbv2" - EMR = "emr" - EMRContainers = "emrcontainers" - Events = "events" - FinSpace = "finspace" - FinSpaceData = "finspacedata" - Firehose = "firehose" - FIS = "fis" - FMS = "fms" - Forecast = "forecast" - ForecastQuery = "forecastquery" - FraudDetector = "frauddetector" - FSx = "fsx" - GameLift = "gamelift" - Glacier = "glacier" - GlobalAccelerator = "globalaccelerator" - Glue = "glue" - GlueDataBrew = "gluedatabrew" - Greengrass = "greengrass" - GreengrassV2 = "greengrassv2" - GroundStation = "groundstation" - GuardDuty = "guardduty" - Health = "health" - HealthLake = "healthlake" - Honeycode = "honeycode" - IAM = "iam" - IdentityStore = "identitystore" - ImageBuilder = "imagebuilder" - Inspector = "inspector" - IoT = "iot" - IoT1ClickDevices = "iot1clickdevices" - IoT1ClickProjects = "iot1clickprojects" - IoTAnalytics = "iotanalytics" - IoTDataPlane = "iotdataplane" - IoTDeviceAdvisor = "iotdeviceadvisor" - IoTEvents = "iotevents" - IoTEventsData = "ioteventsdata" - IoTFleetHub = "iotfleethub" - IoTJobsDataPlane = "iotjobsdataplane" - IoTSecureTunneling = "iotsecuretunneling" - IoTSiteWise = "iotsitewise" - IoTThingsGraph = "iotthingsgraph" - IoTWireless = "iotwireless" - IVS = "ivs" - Kafka = "kafka" - Kendra = "kendra" - Kinesis = "kinesis" - KinesisAnalytics = "kinesisanalytics" - KinesisAnalyticsV2 = "kinesisanalyticsv2" - KinesisVideo = "kinesisvideo" - KinesisVideoArchivedMedia = "kinesisvideoarchivedmedia" - KinesisVideoMedia = "kinesisvideomedia" - KinesisVideoSignalingChannels = "kinesisvideosignalingchannels" - KMS = "kms" - LakeFormation = "lakeformation" - Lambda = "lambda" - LexModelBuilding = "lexmodelbuilding" - LexModelsV2 = "lexmodelsv2" - LexRuntime = "lexruntime" - LexRuntimeV2 = "lexruntimev2" - LicenseManager = "licensemanager" - Lightsail = "lightsail" - Location = "location" - LookoutEquipment = "lookoutequipment" - LookoutForVision = "lookoutforvision" - LookoutMetrics = "lookoutmetrics" - MachineLearning = "machinelearning" - Macie = "macie" - Macie2 = "macie2" - ManagedBlockchain = "managedblockchain" - MarketplaceCatalog = "marketplacecatalog" - MarketplaceCommerceAnalytics = "marketplacecommerceanalytics" - MarketplaceEntitlement = "marketplaceentitlement" - MarketplaceMetering = "marketplacemetering" - MediaConnect = "mediaconnect" - MediaConvert = "mediaconvert" - MediaLive = "medialive" - MediaPackage = "mediapackage" - MediaPackageVOD = "mediapackagevod" - MediaStore = "mediastore" - MediaStoreData = "mediastoredata" - MediaTailor = "mediatailor" - MemoryDB = "memorydb" - Mgn = "mgn" - MigrationHub = "migrationhub" - MigrationHubConfig = "migrationhubconfig" - Mobile = "mobile" - MobileAnalytics = "mobileanalytics" - MQ = "mq" - MTurk = "mturk" - MWAA = "mwaa" - Neptune = "neptune" - NetworkFirewall = "networkfirewall" - NetworkManager = "networkmanager" - NimbleStudio = "nimblestudio" - OpsWorks = "opsworks" - OpsWorksCM = "opsworkscm" - Organizations = "organizations" - Outposts = "outposts" - Personalize = "personalize" - PersonalizeEvents = "personalizeevents" - PersonalizeRuntime = "personalizeruntime" - PI = "pi" - Pinpoint = "pinpoint" - PinpointEmail = "pinpointemail" - PinpointSMSVoice = "pinpointsmsvoice" - Polly = "polly" - Pricing = "pricing" - Proton = "proton" - QLDB = "qldb" - QLDBSession = "qldbsession" - QuickSight = "quicksight" - RAM = "ram" - RDS = "rds" - RDSData = "rdsdata" - Redshift = "redshift" - RedshiftData = "redshiftdata" - Rekognition = "rekognition" - ResourceGroups = "resourcegroups" - ResourceGroupsTaggingAPI = "resourcegroupstaggingapi" - RoboMaker = "robomaker" - Route53 = "route53" - Route53Domains = "route53domains" - Route53RecoveryControlConfig = "route53recoverycontrolconfig" - Route53RecoveryReadiness = "route53recoveryreadiness" - Route53Resolver = "route53resolver" - S3 = "s3" - S3Control = "s3control" - S3Outposts = "s3outposts" - SageMaker = "sagemaker" - SageMakerEdgeManager = "sagemakeredgemanager" - SageMakerFeatureStoreRuntime = "sagemakerfeaturestoreruntime" - SageMakerRuntime = "sagemakerruntime" - SavingsPlans = "savingsplans" - Schemas = "schemas" - SecretsManager = "secretsmanager" - SecurityHub = "securityhub" - ServerlessAppRepo = "serverlessapprepo" - ServiceCatalog = "servicecatalog" - ServiceDiscovery = "servicediscovery" - ServiceQuotas = "servicequotas" - SES = "ses" - SESV2 = "sesv2" - SFN = "sfn" - Shield = "shield" - Signer = "signer" - SimpleDB = "simpledb" - SMS = "sms" - Snowball = "snowball" - SNS = "sns" - SQS = "sqs" - SSM = "ssm" - SSMContacts = "ssmcontacts" - SSMIncidents = "ssmincidents" - SSO = "sso" - SSOAdmin = "ssoadmin" - SSOOIDC = "ssooidc" - StorageGateway = "storagegateway" - STS = "sts" - Support = "support" - SWF = "swf" - Synthetics = "synthetics" - Textract = "textract" - TimestreamQuery = "timestreamquery" - TimestreamWrite = "timestreamwrite" - Transcribe = "transcribe" - TranscribeStreaming = "transcribestreaming" - Transfer = "transfer" - Translate = "translate" - WAF = "waf" - WAFRegional = "wafregional" - WAFV2 = "wafv2" - WellArchitected = "wellarchitected" - WorkDocs = "workdocs" - WorkLink = "worklink" - WorkMail = "workmail" - WorkMailMessageFlow = "workmailmessageflow" - WorkSpaces = "workspaces" - XRay = "xray" -) - -type ServiceDatum struct { - AWSClientName string - AWSServiceName string - AWSEndpointsID string - AWSServiceID string - ProviderNameUpper string - HCLKeys []string -} - -var serviceData map[string]*ServiceDatum - -func init() { - serviceData = make(map[string]*ServiceDatum) - - serviceData[AccessAnalyzer] = &ServiceDatum{AWSClientName: "AccessAnalyzer", AWSServiceName: accessanalyzer.ServiceName, AWSEndpointsID: accessanalyzer.EndpointsID, AWSServiceID: accessanalyzer.ServiceID, ProviderNameUpper: "AccessAnalyzer", HCLKeys: []string{"accessanalyzer"}} - serviceData[ACM] = &ServiceDatum{AWSClientName: "ACM", AWSServiceName: acm.ServiceName, AWSEndpointsID: acm.EndpointsID, AWSServiceID: acm.ServiceID, ProviderNameUpper: "ACM", HCLKeys: []string{"acm"}} - serviceData[ACMPCA] = &ServiceDatum{AWSClientName: "ACMPCA", AWSServiceName: acmpca.ServiceName, AWSEndpointsID: acmpca.EndpointsID, AWSServiceID: acmpca.ServiceID, ProviderNameUpper: "ACMPCA", HCLKeys: []string{"acmpca"}} - serviceData[AlexaForBusiness] = &ServiceDatum{AWSClientName: "AlexaForBusiness", AWSServiceName: alexaforbusiness.ServiceName, AWSEndpointsID: alexaforbusiness.EndpointsID, AWSServiceID: alexaforbusiness.ServiceID, ProviderNameUpper: "AlexaForBusiness", HCLKeys: []string{"alexaforbusiness"}} - serviceData[AMP] = &ServiceDatum{AWSClientName: "PrometheusService", AWSServiceName: prometheusservice.ServiceName, AWSEndpointsID: prometheusservice.EndpointsID, AWSServiceID: prometheusservice.ServiceID, ProviderNameUpper: "AMP", HCLKeys: []string{"amp", "prometheus", "prometheusservice"}} - serviceData[Amplify] = &ServiceDatum{AWSClientName: "Amplify", AWSServiceName: amplify.ServiceName, AWSEndpointsID: amplify.EndpointsID, AWSServiceID: amplify.ServiceID, ProviderNameUpper: "Amplify", HCLKeys: []string{"amplify"}} - serviceData[AmplifyBackend] = &ServiceDatum{AWSClientName: "AmplifyBackend", AWSServiceName: amplifybackend.ServiceName, AWSEndpointsID: amplifybackend.EndpointsID, AWSServiceID: amplifybackend.ServiceID, ProviderNameUpper: "AmplifyBackend", HCLKeys: []string{"amplifybackend"}} - serviceData[APIGateway] = &ServiceDatum{AWSClientName: "APIGateway", AWSServiceName: apigateway.ServiceName, AWSEndpointsID: apigateway.EndpointsID, AWSServiceID: apigateway.ServiceID, ProviderNameUpper: "APIGateway", HCLKeys: []string{"apigateway"}} - serviceData[APIGatewayV2] = &ServiceDatum{AWSClientName: "APIGatewayV2", AWSServiceName: apigatewayv2.ServiceName, AWSEndpointsID: apigatewayv2.EndpointsID, AWSServiceID: apigatewayv2.ServiceID, ProviderNameUpper: "APIGatewayV2", HCLKeys: []string{"apigatewayv2"}} - serviceData[AppAutoScaling] = &ServiceDatum{AWSClientName: "ApplicationAutoScaling", AWSServiceName: applicationautoscaling.ServiceName, AWSEndpointsID: applicationautoscaling.EndpointsID, AWSServiceID: applicationautoscaling.ServiceID, ProviderNameUpper: "AppAutoScaling", HCLKeys: []string{"appautoscaling", "applicationautoscaling"}} - serviceData[AppConfig] = &ServiceDatum{AWSClientName: "AppConfig", AWSServiceName: appconfig.ServiceName, AWSEndpointsID: appconfig.EndpointsID, AWSServiceID: appconfig.ServiceID, ProviderNameUpper: "AppConfig", HCLKeys: []string{"appconfig"}} - serviceData[AppFlow] = &ServiceDatum{AWSClientName: "Appflow", AWSServiceName: appflow.ServiceName, AWSEndpointsID: appflow.EndpointsID, AWSServiceID: appflow.ServiceID, ProviderNameUpper: "AppFlow", HCLKeys: []string{"appflow"}} - serviceData[AppIntegrations] = &ServiceDatum{AWSClientName: "AppIntegrationsService", AWSServiceName: appintegrationsservice.ServiceName, AWSEndpointsID: appintegrationsservice.EndpointsID, AWSServiceID: appintegrationsservice.ServiceID, ProviderNameUpper: "AppIntegrations", HCLKeys: []string{"appintegrations", "appintegrationsservice"}} - serviceData[ApplicationCostProfiler] = &ServiceDatum{AWSClientName: "ApplicationCostProfiler", AWSServiceName: applicationcostprofiler.ServiceName, AWSEndpointsID: applicationcostprofiler.EndpointsID, AWSServiceID: applicationcostprofiler.ServiceID, ProviderNameUpper: "ApplicationCostProfiler", HCLKeys: []string{"applicationcostprofiler"}} - serviceData[ApplicationDiscovery] = &ServiceDatum{AWSClientName: "ApplicationDiscoveryService", AWSServiceName: applicationdiscoveryservice.ServiceName, AWSEndpointsID: applicationdiscoveryservice.EndpointsID, AWSServiceID: applicationdiscoveryservice.ServiceID, ProviderNameUpper: "ApplicationDiscovery", HCLKeys: []string{"applicationdiscovery", "applicationdiscoveryservice"}} - serviceData[ApplicationInsights] = &ServiceDatum{AWSClientName: "ApplicationInsights", AWSServiceName: applicationinsights.ServiceName, AWSEndpointsID: applicationinsights.EndpointsID, AWSServiceID: applicationinsights.ServiceID, ProviderNameUpper: "ApplicationInsights", HCLKeys: []string{"applicationinsights"}} - serviceData[AppMesh] = &ServiceDatum{AWSClientName: "AppMesh", AWSServiceName: appmesh.ServiceName, AWSEndpointsID: appmesh.EndpointsID, AWSServiceID: appmesh.ServiceID, ProviderNameUpper: "AppMesh", HCLKeys: []string{"appmesh"}} - serviceData[AppRegistry] = &ServiceDatum{AWSClientName: "AppRegistry", AWSServiceName: appregistry.ServiceName, AWSEndpointsID: appregistry.EndpointsID, AWSServiceID: appregistry.ServiceID, ProviderNameUpper: "AppRegistry", HCLKeys: []string{"appregistry"}} - serviceData[AppRunner] = &ServiceDatum{AWSClientName: "AppRunner", AWSServiceName: apprunner.ServiceName, AWSEndpointsID: apprunner.EndpointsID, AWSServiceID: apprunner.ServiceID, ProviderNameUpper: "AppRunner", HCLKeys: []string{"apprunner"}} - serviceData[AppStream] = &ServiceDatum{AWSClientName: "AppStream", AWSServiceName: appstream.ServiceName, AWSEndpointsID: appstream.EndpointsID, AWSServiceID: appstream.ServiceID, ProviderNameUpper: "AppStream", HCLKeys: []string{"appstream"}} - serviceData[AppSync] = &ServiceDatum{AWSClientName: "AppSync", AWSServiceName: appsync.ServiceName, AWSEndpointsID: appsync.EndpointsID, AWSServiceID: appsync.ServiceID, ProviderNameUpper: "AppSync", HCLKeys: []string{"appsync"}} - serviceData[Athena] = &ServiceDatum{AWSClientName: "Athena", AWSServiceName: athena.ServiceName, AWSEndpointsID: athena.EndpointsID, AWSServiceID: athena.ServiceID, ProviderNameUpper: "Athena", HCLKeys: []string{"athena"}} - serviceData[AuditManager] = &ServiceDatum{AWSClientName: "AuditManager", AWSServiceName: auditmanager.ServiceName, AWSEndpointsID: auditmanager.EndpointsID, AWSServiceID: auditmanager.ServiceID, ProviderNameUpper: "AuditManager", HCLKeys: []string{"auditmanager"}} - serviceData[AugmentedAIRuntime] = &ServiceDatum{AWSClientName: "AugmentedAIRuntime", AWSServiceName: augmentedairuntime.ServiceName, AWSEndpointsID: augmentedairuntime.EndpointsID, AWSServiceID: augmentedairuntime.ServiceID, ProviderNameUpper: "AugmentedAIRuntime", HCLKeys: []string{"augmentedairuntime"}} - serviceData[AutoScaling] = &ServiceDatum{AWSClientName: "AutoScaling", AWSServiceName: autoscaling.ServiceName, AWSEndpointsID: autoscaling.EndpointsID, AWSServiceID: autoscaling.ServiceID, ProviderNameUpper: "AutoScaling", HCLKeys: []string{"autoscaling"}} - serviceData[AutoScalingPlans] = &ServiceDatum{AWSClientName: "AutoScalingPlans", AWSServiceName: autoscalingplans.ServiceName, AWSEndpointsID: autoscalingplans.EndpointsID, AWSServiceID: autoscalingplans.ServiceID, ProviderNameUpper: "AutoScalingPlans", HCLKeys: []string{"autoscalingplans"}} - serviceData[Backup] = &ServiceDatum{AWSClientName: "Backup", AWSServiceName: backup.ServiceName, AWSEndpointsID: backup.EndpointsID, AWSServiceID: backup.ServiceID, ProviderNameUpper: "Backup", HCLKeys: []string{"backup"}} - serviceData[Batch] = &ServiceDatum{AWSClientName: "Batch", AWSServiceName: batch.ServiceName, AWSEndpointsID: batch.EndpointsID, AWSServiceID: batch.ServiceID, ProviderNameUpper: "Batch", HCLKeys: []string{"batch"}} - serviceData[Braket] = &ServiceDatum{AWSClientName: "Braket", AWSServiceName: braket.ServiceName, AWSEndpointsID: braket.EndpointsID, AWSServiceID: braket.ServiceID, ProviderNameUpper: "Braket", HCLKeys: []string{"braket"}} - serviceData[Budgets] = &ServiceDatum{AWSClientName: "Budgets", AWSServiceName: budgets.ServiceName, AWSEndpointsID: budgets.EndpointsID, AWSServiceID: budgets.ServiceID, ProviderNameUpper: "Budgets", HCLKeys: []string{"budgets"}} - serviceData[Chime] = &ServiceDatum{AWSClientName: "Chime", AWSServiceName: chime.ServiceName, AWSEndpointsID: chime.EndpointsID, AWSServiceID: chime.ServiceID, ProviderNameUpper: "Chime", HCLKeys: []string{"chime"}} - serviceData[Cloud9] = &ServiceDatum{AWSClientName: "Cloud9", AWSServiceName: cloud9.ServiceName, AWSEndpointsID: cloud9.EndpointsID, AWSServiceID: cloud9.ServiceID, ProviderNameUpper: "Cloud9", HCLKeys: []string{"cloud9"}} - serviceData[CloudControl] = &ServiceDatum{AWSClientName: "CloudControlApi", AWSServiceName: cloudcontrolapi.ServiceName, AWSEndpointsID: cloudcontrolapi.EndpointsID, AWSServiceID: cloudcontrolapi.ServiceID, ProviderNameUpper: "CloudControl", HCLKeys: []string{"cloudcontrolapi", "cloudcontrol"}} - serviceData[CloudDirectory] = &ServiceDatum{AWSClientName: "CloudDirectory", AWSServiceName: clouddirectory.ServiceName, AWSEndpointsID: clouddirectory.EndpointsID, AWSServiceID: clouddirectory.ServiceID, ProviderNameUpper: "CloudDirectory", HCLKeys: []string{"clouddirectory"}} - serviceData[CloudFormation] = &ServiceDatum{AWSClientName: "CloudFormation", AWSServiceName: cloudformation.ServiceName, AWSEndpointsID: cloudformation.EndpointsID, AWSServiceID: cloudformation.ServiceID, ProviderNameUpper: "CloudFormation", HCLKeys: []string{"cloudformation"}} - serviceData[CloudFront] = &ServiceDatum{AWSClientName: "CloudFront", AWSServiceName: cloudfront.ServiceName, AWSEndpointsID: cloudfront.EndpointsID, AWSServiceID: cloudfront.ServiceID, ProviderNameUpper: "CloudFront", HCLKeys: []string{"cloudfront"}} - serviceData[CloudHSMV2] = &ServiceDatum{AWSClientName: "CloudHSMV2", AWSServiceName: cloudhsmv2.ServiceName, AWSEndpointsID: cloudhsmv2.EndpointsID, AWSServiceID: cloudhsmv2.ServiceID, ProviderNameUpper: "CloudHSMV2", HCLKeys: []string{"cloudhsm", "cloudhsmv2"}} - serviceData[CloudSearch] = &ServiceDatum{AWSClientName: "CloudSearch", AWSServiceName: cloudsearch.ServiceName, AWSEndpointsID: cloudsearch.EndpointsID, AWSServiceID: cloudsearch.ServiceID, ProviderNameUpper: "CloudSearch", HCLKeys: []string{"cloudsearch"}} - serviceData[CloudSearchDomain] = &ServiceDatum{AWSClientName: "CloudSearchDomain", AWSServiceName: cloudsearchdomain.ServiceName, AWSEndpointsID: cloudsearchdomain.EndpointsID, AWSServiceID: cloudsearchdomain.ServiceID, ProviderNameUpper: "CloudSearchDomain", HCLKeys: []string{"cloudsearchdomain"}} - serviceData[CloudTrail] = &ServiceDatum{AWSClientName: "CloudTrail", AWSServiceName: cloudtrail.ServiceName, AWSEndpointsID: cloudtrail.EndpointsID, AWSServiceID: cloudtrail.ServiceID, ProviderNameUpper: "CloudTrail", HCLKeys: []string{"cloudtrail"}} - serviceData[CloudWatch] = &ServiceDatum{AWSClientName: "CloudWatch", AWSServiceName: cloudwatch.ServiceName, AWSEndpointsID: cloudwatch.EndpointsID, AWSServiceID: cloudwatch.ServiceID, ProviderNameUpper: "CloudWatch", HCLKeys: []string{"cloudwatch"}} - serviceData[CloudWatchLogs] = &ServiceDatum{AWSClientName: "CloudWatchLogs", AWSServiceName: cloudwatchlogs.ServiceName, AWSEndpointsID: cloudwatchlogs.EndpointsID, AWSServiceID: cloudwatchlogs.ServiceID, ProviderNameUpper: "CloudWatchLogs", HCLKeys: []string{"cloudwatchlogs"}} - serviceData[CodeArtifact] = &ServiceDatum{AWSClientName: "CodeArtifact", AWSServiceName: codeartifact.ServiceName, AWSEndpointsID: codeartifact.EndpointsID, AWSServiceID: codeartifact.ServiceID, ProviderNameUpper: "CodeArtifact", HCLKeys: []string{"codeartifact"}} - serviceData[CodeBuild] = &ServiceDatum{AWSClientName: "CodeBuild", AWSServiceName: codebuild.ServiceName, AWSEndpointsID: codebuild.EndpointsID, AWSServiceID: codebuild.ServiceID, ProviderNameUpper: "CodeBuild", HCLKeys: []string{"codebuild"}} - serviceData[CodeCommit] = &ServiceDatum{AWSClientName: "CodeCommit", AWSServiceName: codecommit.ServiceName, AWSEndpointsID: codecommit.EndpointsID, AWSServiceID: codecommit.ServiceID, ProviderNameUpper: "CodeCommit", HCLKeys: []string{"codecommit"}} - serviceData[CodeDeploy] = &ServiceDatum{AWSClientName: "CodeDeploy", AWSServiceName: codedeploy.ServiceName, AWSEndpointsID: codedeploy.EndpointsID, AWSServiceID: codedeploy.ServiceID, ProviderNameUpper: "CodeDeploy", HCLKeys: []string{"codedeploy"}} - serviceData[CodeGuruProfiler] = &ServiceDatum{AWSClientName: "CodeGuruProfiler", AWSServiceName: codeguruprofiler.ServiceName, AWSEndpointsID: codeguruprofiler.EndpointsID, AWSServiceID: codeguruprofiler.ServiceID, ProviderNameUpper: "CodeGuruProfiler", HCLKeys: []string{"codeguruprofiler"}} - serviceData[CodeGuruReviewer] = &ServiceDatum{AWSClientName: "CodeGuruReviewer", AWSServiceName: codegurureviewer.ServiceName, AWSEndpointsID: codegurureviewer.EndpointsID, AWSServiceID: codegurureviewer.ServiceID, ProviderNameUpper: "CodeGuruReviewer", HCLKeys: []string{"codegurureviewer"}} - serviceData[CodePipeline] = &ServiceDatum{AWSClientName: "CodePipeline", AWSServiceName: codepipeline.ServiceName, AWSEndpointsID: codepipeline.EndpointsID, AWSServiceID: codepipeline.ServiceID, ProviderNameUpper: "CodePipeline", HCLKeys: []string{"codepipeline"}} - serviceData[CodeStar] = &ServiceDatum{AWSClientName: "CodeStar", AWSServiceName: codestar.ServiceName, AWSEndpointsID: codestar.EndpointsID, AWSServiceID: codestar.ServiceID, ProviderNameUpper: "CodeStar", HCLKeys: []string{"codestar"}} - serviceData[CodeStarConnections] = &ServiceDatum{AWSClientName: "CodeStarConnections", AWSServiceName: codestarconnections.ServiceName, AWSEndpointsID: codestarconnections.EndpointsID, AWSServiceID: codestarconnections.ServiceID, ProviderNameUpper: "CodeStarConnections", HCLKeys: []string{"codestarconnections"}} - serviceData[CodeStarNotifications] = &ServiceDatum{AWSClientName: "CodeStarNotifications", AWSServiceName: codestarnotifications.ServiceName, AWSEndpointsID: codestarnotifications.EndpointsID, AWSServiceID: codestarnotifications.ServiceID, ProviderNameUpper: "CodeStarNotifications", HCLKeys: []string{"codestarnotifications"}} - serviceData[CognitoIdentity] = &ServiceDatum{AWSClientName: "CognitoIdentity", AWSServiceName: cognitoidentity.ServiceName, AWSEndpointsID: cognitoidentity.EndpointsID, AWSServiceID: cognitoidentity.ServiceID, ProviderNameUpper: "CognitoIdentity", HCLKeys: []string{"cognitoidentity"}} - serviceData[CognitoIDP] = &ServiceDatum{AWSClientName: "CognitoIdentityProvider", AWSServiceName: cognitoidentityprovider.ServiceName, AWSEndpointsID: cognitoidentityprovider.EndpointsID, AWSServiceID: cognitoidentityprovider.ServiceID, ProviderNameUpper: "CognitoIDP", HCLKeys: []string{"cognitoidp", "cognitoidentityprovider"}} - serviceData[CognitoSync] = &ServiceDatum{AWSClientName: "CognitoSync", AWSServiceName: cognitosync.ServiceName, AWSEndpointsID: cognitosync.EndpointsID, AWSServiceID: cognitosync.ServiceID, ProviderNameUpper: "CognitoSync", HCLKeys: []string{"cognitosync"}} - serviceData[Comprehend] = &ServiceDatum{AWSClientName: "Comprehend", AWSServiceName: comprehend.ServiceName, AWSEndpointsID: comprehend.EndpointsID, AWSServiceID: comprehend.ServiceID, ProviderNameUpper: "Comprehend", HCLKeys: []string{"comprehend"}} - serviceData[ComprehendMedical] = &ServiceDatum{AWSClientName: "ComprehendMedical", AWSServiceName: comprehendmedical.ServiceName, AWSEndpointsID: comprehendmedical.EndpointsID, AWSServiceID: comprehendmedical.ServiceID, ProviderNameUpper: "ComprehendMedical", HCLKeys: []string{"comprehendmedical"}} - serviceData[ConfigService] = &ServiceDatum{AWSClientName: "ConfigService", AWSServiceName: configservice.ServiceName, AWSEndpointsID: configservice.EndpointsID, AWSServiceID: configservice.ServiceID, ProviderNameUpper: "Config", HCLKeys: []string{"configservice", "config"}} - serviceData[Connect] = &ServiceDatum{AWSClientName: "Connect", AWSServiceName: connect.ServiceName, AWSEndpointsID: connect.EndpointsID, AWSServiceID: connect.ServiceID, ProviderNameUpper: "Connect", HCLKeys: []string{"connect"}} - serviceData[ConnectContactLens] = &ServiceDatum{AWSClientName: "ConnectContactLens", AWSServiceName: connectcontactlens.ServiceName, AWSEndpointsID: connectcontactlens.EndpointsID, AWSServiceID: connectcontactlens.ServiceID, ProviderNameUpper: "ConnectContactLens", HCLKeys: []string{"connectcontactlens"}} - serviceData[ConnectParticipant] = &ServiceDatum{AWSClientName: "ConnectParticipant", AWSServiceName: connectparticipant.ServiceName, AWSEndpointsID: connectparticipant.EndpointsID, AWSServiceID: connectparticipant.ServiceID, ProviderNameUpper: "ConnectParticipant", HCLKeys: []string{"connectparticipant"}} - serviceData[CostExplorer] = &ServiceDatum{AWSClientName: "CostExplorer", AWSServiceName: costexplorer.ServiceName, AWSEndpointsID: costexplorer.EndpointsID, AWSServiceID: costexplorer.ServiceID, ProviderNameUpper: "CostExplorer", HCLKeys: []string{"costexplorer"}} - serviceData[CUR] = &ServiceDatum{AWSClientName: "CostandUsageReportService", AWSServiceName: costandusagereportservice.ServiceName, AWSEndpointsID: costandusagereportservice.EndpointsID, AWSServiceID: costandusagereportservice.ServiceID, ProviderNameUpper: "CUR", HCLKeys: []string{"cur", "costandusagereportservice"}} - serviceData[DataExchange] = &ServiceDatum{AWSClientName: "DataExchange", AWSServiceName: dataexchange.ServiceName, AWSEndpointsID: dataexchange.EndpointsID, AWSServiceID: dataexchange.ServiceID, ProviderNameUpper: "DataExchange", HCLKeys: []string{"dataexchange"}} - serviceData[DataPipeline] = &ServiceDatum{AWSClientName: "DataPipeline", AWSServiceName: datapipeline.ServiceName, AWSEndpointsID: datapipeline.EndpointsID, AWSServiceID: datapipeline.ServiceID, ProviderNameUpper: "DataPipeline", HCLKeys: []string{"datapipeline"}} - serviceData[DataSync] = &ServiceDatum{AWSClientName: "DataSync", AWSServiceName: datasync.ServiceName, AWSEndpointsID: datasync.EndpointsID, AWSServiceID: datasync.ServiceID, ProviderNameUpper: "DataSync", HCLKeys: []string{"datasync"}} - serviceData[DAX] = &ServiceDatum{AWSClientName: "DAX", AWSServiceName: dax.ServiceName, AWSEndpointsID: dax.EndpointsID, AWSServiceID: dax.ServiceID, ProviderNameUpper: "DAX", HCLKeys: []string{"dax"}} - serviceData[Detective] = &ServiceDatum{AWSClientName: "Detective", AWSServiceName: detective.ServiceName, AWSEndpointsID: detective.EndpointsID, AWSServiceID: detective.ServiceID, ProviderNameUpper: "Detective", HCLKeys: []string{"detective"}} - serviceData[DeviceFarm] = &ServiceDatum{AWSClientName: "DeviceFarm", AWSServiceName: devicefarm.ServiceName, AWSEndpointsID: devicefarm.EndpointsID, AWSServiceID: devicefarm.ServiceID, ProviderNameUpper: "DeviceFarm", HCLKeys: []string{"devicefarm"}} - serviceData[DevOpsGuru] = &ServiceDatum{AWSClientName: "DevOpsGuru", AWSServiceName: devopsguru.ServiceName, AWSEndpointsID: devopsguru.EndpointsID, AWSServiceID: devopsguru.ServiceID, ProviderNameUpper: "DevOpsGuru", HCLKeys: []string{"devopsguru"}} - serviceData[DirectConnect] = &ServiceDatum{AWSClientName: "DirectConnect", AWSServiceName: directconnect.ServiceName, AWSEndpointsID: directconnect.EndpointsID, AWSServiceID: directconnect.ServiceID, ProviderNameUpper: "DirectConnect", HCLKeys: []string{"directconnect"}} - serviceData[DLM] = &ServiceDatum{AWSClientName: "DLM", AWSServiceName: dlm.ServiceName, AWSEndpointsID: dlm.EndpointsID, AWSServiceID: dlm.ServiceID, ProviderNameUpper: "DLM", HCLKeys: []string{"dlm"}} - serviceData[DMS] = &ServiceDatum{AWSClientName: "DatabaseMigrationService", AWSServiceName: databasemigrationservice.ServiceName, AWSEndpointsID: databasemigrationservice.EndpointsID, AWSServiceID: databasemigrationservice.ServiceID, ProviderNameUpper: "DMS", HCLKeys: []string{"dms", "databasemigration", "databasemigrationservice"}} - serviceData[DocDB] = &ServiceDatum{AWSClientName: "DocDB", AWSServiceName: docdb.ServiceName, AWSEndpointsID: docdb.EndpointsID, AWSServiceID: docdb.ServiceID, ProviderNameUpper: "DocDB", HCLKeys: []string{"docdb"}} - serviceData[DS] = &ServiceDatum{AWSClientName: "DirectoryService", AWSServiceName: directoryservice.ServiceName, AWSEndpointsID: directoryservice.EndpointsID, AWSServiceID: directoryservice.ServiceID, ProviderNameUpper: "DS", HCLKeys: []string{"ds"}} - serviceData[DynamoDB] = &ServiceDatum{AWSClientName: "DynamoDB", AWSServiceName: dynamodb.ServiceName, AWSEndpointsID: dynamodb.EndpointsID, AWSServiceID: dynamodb.ServiceID, ProviderNameUpper: "DynamoDB", HCLKeys: []string{"dynamodb"}} - serviceData[DynamoDBStreams] = &ServiceDatum{AWSClientName: "DynamoDBStreams", AWSServiceName: dynamodbstreams.ServiceName, AWSEndpointsID: dynamodbstreams.EndpointsID, AWSServiceID: dynamodbstreams.ServiceID, ProviderNameUpper: "DynamoDBStreams", HCLKeys: []string{"dynamodbstreams"}} - serviceData[EC2] = &ServiceDatum{AWSClientName: "EC2", AWSServiceName: ec2.ServiceName, AWSEndpointsID: ec2.EndpointsID, AWSServiceID: ec2.ServiceID, ProviderNameUpper: "EC2", HCLKeys: []string{"ec2"}} - serviceData[EC2InstanceConnect] = &ServiceDatum{AWSClientName: "EC2InstanceConnect", AWSServiceName: ec2instanceconnect.ServiceName, AWSEndpointsID: ec2instanceconnect.EndpointsID, AWSServiceID: ec2instanceconnect.ServiceID, ProviderNameUpper: "EC2InstanceConnect", HCLKeys: []string{"ec2instanceconnect"}} - serviceData[ECR] = &ServiceDatum{AWSClientName: "ECR", AWSServiceName: ecr.ServiceName, AWSEndpointsID: ecr.EndpointsID, AWSServiceID: ecr.ServiceID, ProviderNameUpper: "ECR", HCLKeys: []string{"ecr"}} - serviceData[ECRPublic] = &ServiceDatum{AWSClientName: "ECRPublic", AWSServiceName: ecrpublic.ServiceName, AWSEndpointsID: ecrpublic.EndpointsID, AWSServiceID: ecrpublic.ServiceID, ProviderNameUpper: "ECRPublic", HCLKeys: []string{"ecrpublic"}} - serviceData[ECS] = &ServiceDatum{AWSClientName: "ECS", AWSServiceName: ecs.ServiceName, AWSEndpointsID: ecs.EndpointsID, AWSServiceID: ecs.ServiceID, ProviderNameUpper: "ECS", HCLKeys: []string{"ecs"}} - serviceData[EFS] = &ServiceDatum{AWSClientName: "EFS", AWSServiceName: efs.ServiceName, AWSEndpointsID: efs.EndpointsID, AWSServiceID: efs.ServiceID, ProviderNameUpper: "EFS", HCLKeys: []string{"efs"}} - serviceData[EKS] = &ServiceDatum{AWSClientName: "EKS", AWSServiceName: eks.ServiceName, AWSEndpointsID: eks.EndpointsID, AWSServiceID: eks.ServiceID, ProviderNameUpper: "EKS", HCLKeys: []string{"eks"}} - serviceData[ElastiCache] = &ServiceDatum{AWSClientName: "ElastiCache", AWSServiceName: elasticache.ServiceName, AWSEndpointsID: elasticache.EndpointsID, AWSServiceID: elasticache.ServiceID, ProviderNameUpper: "ElastiCache", HCLKeys: []string{"elasticache"}} - serviceData[ElasticBeanstalk] = &ServiceDatum{AWSClientName: "ElasticBeanstalk", AWSServiceName: elasticbeanstalk.ServiceName, AWSEndpointsID: elasticbeanstalk.EndpointsID, AWSServiceID: elasticbeanstalk.ServiceID, ProviderNameUpper: "ElasticBeanstalk", HCLKeys: []string{"elasticbeanstalk"}} - serviceData[ElasticInference] = &ServiceDatum{AWSClientName: "ElasticInference", AWSServiceName: elasticinference.ServiceName, AWSEndpointsID: elasticinference.EndpointsID, AWSServiceID: elasticinference.ServiceID, ProviderNameUpper: "ElasticInference", HCLKeys: []string{"elasticinference"}} - serviceData[Elasticsearch] = &ServiceDatum{AWSClientName: "ElasticsearchService", AWSServiceName: elasticsearch.ServiceName, AWSEndpointsID: elasticsearch.EndpointsID, AWSServiceID: elasticsearch.ServiceID, ProviderNameUpper: "Elasticsearch", HCLKeys: []string{"es", "elasticsearch", "elasticsearchservice"}} - serviceData[ElasticTranscoder] = &ServiceDatum{AWSClientName: "ElasticTranscoder", AWSServiceName: elastictranscoder.ServiceName, AWSEndpointsID: elastictranscoder.EndpointsID, AWSServiceID: elastictranscoder.ServiceID, ProviderNameUpper: "ElasticTranscoder", HCLKeys: []string{"elastictranscoder"}} - serviceData[ELB] = &ServiceDatum{AWSClientName: "ELB", AWSServiceName: elb.ServiceName, AWSEndpointsID: elb.EndpointsID, AWSServiceID: elb.ServiceID, ProviderNameUpper: "ELB", HCLKeys: []string{"elb"}} - serviceData[ELBV2] = &ServiceDatum{AWSClientName: "ELBV2", AWSServiceName: elbv2.ServiceName, AWSEndpointsID: elbv2.EndpointsID, AWSServiceID: elbv2.ServiceID, ProviderNameUpper: "ELBV2", HCLKeys: []string{"elbv2"}} - serviceData[EMR] = &ServiceDatum{AWSClientName: "EMR", AWSServiceName: emr.ServiceName, AWSEndpointsID: emr.EndpointsID, AWSServiceID: emr.ServiceID, ProviderNameUpper: "EMR", HCLKeys: []string{"emr"}} - serviceData[EMRContainers] = &ServiceDatum{AWSClientName: "EMRContainers", AWSServiceName: emrcontainers.ServiceName, AWSEndpointsID: emrcontainers.EndpointsID, AWSServiceID: emrcontainers.ServiceID, ProviderNameUpper: "EMRContainers", HCLKeys: []string{"emrcontainers"}} - serviceData[Events] = &ServiceDatum{AWSClientName: "EventBridge", AWSServiceName: eventbridge.ServiceName, AWSEndpointsID: eventbridge.EndpointsID, AWSServiceID: eventbridge.ServiceID, ProviderNameUpper: "Events", HCLKeys: []string{"cloudwatchevents", "eventbridge", "events"}} - serviceData[FinSpace] = &ServiceDatum{AWSClientName: "Finspace", AWSServiceName: finspace.ServiceName, AWSEndpointsID: finspace.EndpointsID, AWSServiceID: finspace.ServiceID, ProviderNameUpper: "FinSpace", HCLKeys: []string{"finspace"}} - serviceData[FinSpaceData] = &ServiceDatum{AWSClientName: "FinSpaceData", AWSServiceName: finspacedata.ServiceName, AWSEndpointsID: finspacedata.EndpointsID, AWSServiceID: finspacedata.ServiceID, ProviderNameUpper: "FinSpaceData", HCLKeys: []string{"finspacedata"}} - serviceData[Firehose] = &ServiceDatum{AWSClientName: "Firehose", AWSServiceName: firehose.ServiceName, AWSEndpointsID: firehose.EndpointsID, AWSServiceID: firehose.ServiceID, ProviderNameUpper: "Firehose", HCLKeys: []string{"firehose"}} - serviceData[FIS] = &ServiceDatum{AWSClientName: "FIS", AWSServiceName: fis.ServiceName, AWSEndpointsID: fis.EndpointsID, AWSServiceID: fis.ServiceID, ProviderNameUpper: "FIS", HCLKeys: []string{"fis"}} - serviceData[FMS] = &ServiceDatum{AWSClientName: "FMS", AWSServiceName: fms.ServiceName, AWSEndpointsID: fms.EndpointsID, AWSServiceID: fms.ServiceID, ProviderNameUpper: "FMS", HCLKeys: []string{"fms"}} - serviceData[Forecast] = &ServiceDatum{AWSClientName: "ForecastService", AWSServiceName: forecastservice.ServiceName, AWSEndpointsID: forecastservice.EndpointsID, AWSServiceID: forecastservice.ServiceID, ProviderNameUpper: "Forecast", HCLKeys: []string{"forecast", "forecastservice"}} - serviceData[ForecastQuery] = &ServiceDatum{AWSClientName: "ForecastQueryService", AWSServiceName: forecastqueryservice.ServiceName, AWSEndpointsID: forecastqueryservice.EndpointsID, AWSServiceID: forecastqueryservice.ServiceID, ProviderNameUpper: "ForecastQuery", HCLKeys: []string{"forecastquery", "forecastqueryservice"}} - serviceData[FraudDetector] = &ServiceDatum{AWSClientName: "FraudDetector", AWSServiceName: frauddetector.ServiceName, AWSEndpointsID: frauddetector.EndpointsID, AWSServiceID: frauddetector.ServiceID, ProviderNameUpper: "FraudDetector", HCLKeys: []string{"frauddetector"}} - serviceData[FSx] = &ServiceDatum{AWSClientName: "FSx", AWSServiceName: fsx.ServiceName, AWSEndpointsID: fsx.EndpointsID, AWSServiceID: fsx.ServiceID, ProviderNameUpper: "FSx", HCLKeys: []string{"fsx"}} - serviceData[GameLift] = &ServiceDatum{AWSClientName: "GameLift", AWSServiceName: gamelift.ServiceName, AWSEndpointsID: gamelift.EndpointsID, AWSServiceID: gamelift.ServiceID, ProviderNameUpper: "GameLift", HCLKeys: []string{"gamelift"}} - serviceData[Glacier] = &ServiceDatum{AWSClientName: "Glacier", AWSServiceName: glacier.ServiceName, AWSEndpointsID: glacier.EndpointsID, AWSServiceID: glacier.ServiceID, ProviderNameUpper: "Glacier", HCLKeys: []string{"glacier"}} - serviceData[GlobalAccelerator] = &ServiceDatum{AWSClientName: "GlobalAccelerator", AWSServiceName: globalaccelerator.ServiceName, AWSEndpointsID: globalaccelerator.EndpointsID, AWSServiceID: globalaccelerator.ServiceID, ProviderNameUpper: "GlobalAccelerator", HCLKeys: []string{"globalaccelerator"}} - serviceData[Glue] = &ServiceDatum{AWSClientName: "Glue", AWSServiceName: glue.ServiceName, AWSEndpointsID: glue.EndpointsID, AWSServiceID: glue.ServiceID, ProviderNameUpper: "Glue", HCLKeys: []string{"glue"}} - serviceData[GlueDataBrew] = &ServiceDatum{AWSClientName: "GlueDataBrew", AWSServiceName: gluedatabrew.ServiceName, AWSEndpointsID: gluedatabrew.EndpointsID, AWSServiceID: gluedatabrew.ServiceID, ProviderNameUpper: "GlueDataBrew", HCLKeys: []string{"gluedatabrew"}} - serviceData[Greengrass] = &ServiceDatum{AWSClientName: "Greengrass", AWSServiceName: greengrass.ServiceName, AWSEndpointsID: greengrass.EndpointsID, AWSServiceID: greengrass.ServiceID, ProviderNameUpper: "Greengrass", HCLKeys: []string{"greengrass"}} - serviceData[GreengrassV2] = &ServiceDatum{AWSClientName: "GreengrassV2", AWSServiceName: greengrassv2.ServiceName, AWSEndpointsID: greengrassv2.EndpointsID, AWSServiceID: greengrassv2.ServiceID, ProviderNameUpper: "GreengrassV2", HCLKeys: []string{"greengrassv2"}} - serviceData[GroundStation] = &ServiceDatum{AWSClientName: "GroundStation", AWSServiceName: groundstation.ServiceName, AWSEndpointsID: groundstation.EndpointsID, AWSServiceID: groundstation.ServiceID, ProviderNameUpper: "GroundStation", HCLKeys: []string{"groundstation"}} - serviceData[GuardDuty] = &ServiceDatum{AWSClientName: "GuardDuty", AWSServiceName: guardduty.ServiceName, AWSEndpointsID: guardduty.EndpointsID, AWSServiceID: guardduty.ServiceID, ProviderNameUpper: "GuardDuty", HCLKeys: []string{"guardduty"}} - serviceData[Health] = &ServiceDatum{AWSClientName: "Health", AWSServiceName: health.ServiceName, AWSEndpointsID: health.EndpointsID, AWSServiceID: health.ServiceID, ProviderNameUpper: "Health", HCLKeys: []string{"health"}} - serviceData[HealthLake] = &ServiceDatum{AWSClientName: "HealthLake", AWSServiceName: healthlake.ServiceName, AWSEndpointsID: healthlake.EndpointsID, AWSServiceID: healthlake.ServiceID, ProviderNameUpper: "HealthLake", HCLKeys: []string{"healthlake"}} - serviceData[Honeycode] = &ServiceDatum{AWSClientName: "Honeycode", AWSServiceName: honeycode.ServiceName, AWSEndpointsID: honeycode.EndpointsID, AWSServiceID: honeycode.ServiceID, ProviderNameUpper: "Honeycode", HCLKeys: []string{"honeycode"}} - serviceData[IAM] = &ServiceDatum{AWSClientName: "IAM", AWSServiceName: iam.ServiceName, AWSEndpointsID: iam.EndpointsID, AWSServiceID: iam.ServiceID, ProviderNameUpper: "IAM", HCLKeys: []string{"iam"}} - serviceData[IdentityStore] = &ServiceDatum{AWSClientName: "IdentityStore", AWSServiceName: identitystore.ServiceName, AWSEndpointsID: identitystore.EndpointsID, AWSServiceID: identitystore.ServiceID, ProviderNameUpper: "IdentityStore", HCLKeys: []string{"identitystore"}} - serviceData[ImageBuilder] = &ServiceDatum{AWSClientName: "ImageBuilder", AWSServiceName: imagebuilder.ServiceName, AWSEndpointsID: imagebuilder.EndpointsID, AWSServiceID: imagebuilder.ServiceID, ProviderNameUpper: "ImageBuilder", HCLKeys: []string{"imagebuilder"}} - serviceData[Inspector] = &ServiceDatum{AWSClientName: "Inspector", AWSServiceName: inspector.ServiceName, AWSEndpointsID: inspector.EndpointsID, AWSServiceID: inspector.ServiceID, ProviderNameUpper: "Inspector", HCLKeys: []string{"inspector"}} - serviceData[IoT] = &ServiceDatum{AWSClientName: "IoT", AWSServiceName: iot.ServiceName, AWSEndpointsID: iot.EndpointsID, AWSServiceID: iot.ServiceID, ProviderNameUpper: "IoT", HCLKeys: []string{"iot"}} - serviceData[IoT1ClickDevices] = &ServiceDatum{AWSClientName: "IoT1ClickDevicesService", AWSServiceName: iot1clickdevicesservice.ServiceName, AWSEndpointsID: iot1clickdevicesservice.EndpointsID, AWSServiceID: iot1clickdevicesservice.ServiceID, ProviderNameUpper: "IoT1ClickDevices", HCLKeys: []string{"iot1clickdevices", "iot1clickdevicesservice"}} - serviceData[IoT1ClickProjects] = &ServiceDatum{AWSClientName: "IoT1ClickProjects", AWSServiceName: iot1clickprojects.ServiceName, AWSEndpointsID: iot1clickprojects.EndpointsID, AWSServiceID: iot1clickprojects.ServiceID, ProviderNameUpper: "IoT1ClickProjects", HCLKeys: []string{"iot1clickprojects"}} - serviceData[IoTAnalytics] = &ServiceDatum{AWSClientName: "IoTAnalytics", AWSServiceName: iotanalytics.ServiceName, AWSEndpointsID: iotanalytics.EndpointsID, AWSServiceID: iotanalytics.ServiceID, ProviderNameUpper: "IoTAnalytics", HCLKeys: []string{"iotanalytics"}} - serviceData[IoTDataPlane] = &ServiceDatum{AWSClientName: "IoTDataPlane", AWSServiceName: iotdataplane.ServiceName, AWSEndpointsID: iotdataplane.EndpointsID, AWSServiceID: iotdataplane.ServiceID, ProviderNameUpper: "IoTDataPlane", HCLKeys: []string{"iotdataplane"}} - serviceData[IoTDeviceAdvisor] = &ServiceDatum{AWSClientName: "IoTDeviceAdvisor", AWSServiceName: iotdeviceadvisor.ServiceName, AWSEndpointsID: iotdeviceadvisor.EndpointsID, AWSServiceID: iotdeviceadvisor.ServiceID, ProviderNameUpper: "IoTDeviceAdvisor", HCLKeys: []string{"iotdeviceadvisor"}} - serviceData[IoTEvents] = &ServiceDatum{AWSClientName: "IoTEvents", AWSServiceName: iotevents.ServiceName, AWSEndpointsID: iotevents.EndpointsID, AWSServiceID: iotevents.ServiceID, ProviderNameUpper: "IoTEvents", HCLKeys: []string{"iotevents"}} - serviceData[IoTEventsData] = &ServiceDatum{AWSClientName: "IoTEventsData", AWSServiceName: ioteventsdata.ServiceName, AWSEndpointsID: ioteventsdata.EndpointsID, AWSServiceID: ioteventsdata.ServiceID, ProviderNameUpper: "IoTEventsData", HCLKeys: []string{"ioteventsdata"}} - serviceData[IoTFleetHub] = &ServiceDatum{AWSClientName: "IoTFleetHub", AWSServiceName: iotfleethub.ServiceName, AWSEndpointsID: iotfleethub.EndpointsID, AWSServiceID: iotfleethub.ServiceID, ProviderNameUpper: "IoTFleetHub", HCLKeys: []string{"iotfleethub"}} - serviceData[IoTJobsDataPlane] = &ServiceDatum{AWSClientName: "IoTJobsDataPlane", AWSServiceName: iotjobsdataplane.ServiceName, AWSEndpointsID: iotjobsdataplane.EndpointsID, AWSServiceID: iotjobsdataplane.ServiceID, ProviderNameUpper: "IoTJobsDataPlane", HCLKeys: []string{"iotjobsdataplane"}} - serviceData[IoTSecureTunneling] = &ServiceDatum{AWSClientName: "IoTSecureTunneling", AWSServiceName: iotsecuretunneling.ServiceName, AWSEndpointsID: iotsecuretunneling.EndpointsID, AWSServiceID: iotsecuretunneling.ServiceID, ProviderNameUpper: "IoTSecureTunneling", HCLKeys: []string{"iotsecuretunneling"}} - serviceData[IoTSiteWise] = &ServiceDatum{AWSClientName: "IoTSiteWise", AWSServiceName: iotsitewise.ServiceName, AWSEndpointsID: iotsitewise.EndpointsID, AWSServiceID: iotsitewise.ServiceID, ProviderNameUpper: "IoTSiteWise", HCLKeys: []string{"iotsitewise"}} - serviceData[IoTThingsGraph] = &ServiceDatum{AWSClientName: "IoTThingsGraph", AWSServiceName: iotthingsgraph.ServiceName, AWSEndpointsID: iotthingsgraph.EndpointsID, AWSServiceID: iotthingsgraph.ServiceID, ProviderNameUpper: "IoTThingsGraph", HCLKeys: []string{"iotthingsgraph"}} - serviceData[IoTWireless] = &ServiceDatum{AWSClientName: "IoTWireless", AWSServiceName: iotwireless.ServiceName, AWSEndpointsID: iotwireless.EndpointsID, AWSServiceID: iotwireless.ServiceID, ProviderNameUpper: "IoTWireless", HCLKeys: []string{"iotwireless"}} - serviceData[Kafka] = &ServiceDatum{AWSClientName: "Kafka", AWSServiceName: kafka.ServiceName, AWSEndpointsID: kafka.EndpointsID, AWSServiceID: kafka.ServiceID, ProviderNameUpper: "Kafka", HCLKeys: []string{"kafka"}} - serviceData[Kendra] = &ServiceDatum{AWSClientName: "Kendra", AWSServiceName: kendra.ServiceName, AWSEndpointsID: kendra.EndpointsID, AWSServiceID: kendra.ServiceID, ProviderNameUpper: "Kendra", HCLKeys: []string{"kendra"}} - serviceData[Kinesis] = &ServiceDatum{AWSClientName: "Kinesis", AWSServiceName: kinesis.ServiceName, AWSEndpointsID: kinesis.EndpointsID, AWSServiceID: kinesis.ServiceID, ProviderNameUpper: "Kinesis", HCLKeys: []string{"kinesis"}} - serviceData[KinesisAnalytics] = &ServiceDatum{AWSClientName: "KinesisAnalytics", AWSServiceName: kinesisanalytics.ServiceName, AWSEndpointsID: kinesisanalytics.EndpointsID, AWSServiceID: kinesisanalytics.ServiceID, ProviderNameUpper: "KinesisAnalytics", HCLKeys: []string{"kinesisanalytics"}} - serviceData[KinesisAnalyticsV2] = &ServiceDatum{AWSClientName: "KinesisAnalyticsV2", AWSServiceName: kinesisanalyticsv2.ServiceName, AWSEndpointsID: kinesisanalyticsv2.EndpointsID, AWSServiceID: kinesisanalyticsv2.ServiceID, ProviderNameUpper: "KinesisAnalyticsV2", HCLKeys: []string{"kinesisanalyticsv2"}} - serviceData[KinesisVideo] = &ServiceDatum{AWSClientName: "KinesisVideo", AWSServiceName: kinesisvideo.ServiceName, AWSEndpointsID: kinesisvideo.EndpointsID, AWSServiceID: kinesisvideo.ServiceID, ProviderNameUpper: "KinesisVideo", HCLKeys: []string{"kinesisvideo"}} - serviceData[KinesisVideoArchivedMedia] = &ServiceDatum{AWSClientName: "KinesisVideoArchivedMedia", AWSServiceName: kinesisvideoarchivedmedia.ServiceName, AWSEndpointsID: kinesisvideoarchivedmedia.EndpointsID, AWSServiceID: kinesisvideoarchivedmedia.ServiceID, ProviderNameUpper: "KinesisVideoArchivedMedia", HCLKeys: []string{"kinesisvideoarchivedmedia"}} - serviceData[KinesisVideoMedia] = &ServiceDatum{AWSClientName: "KinesisVideoMedia", AWSServiceName: kinesisvideomedia.ServiceName, AWSEndpointsID: kinesisvideomedia.EndpointsID, AWSServiceID: kinesisvideomedia.ServiceID, ProviderNameUpper: "KinesisVideoMedia", HCLKeys: []string{"kinesisvideomedia"}} - serviceData[KinesisVideoSignalingChannels] = &ServiceDatum{AWSClientName: "KinesisVideoSignalingChannels", AWSServiceName: kinesisvideosignalingchannels.ServiceName, AWSEndpointsID: kinesisvideosignalingchannels.EndpointsID, AWSServiceID: kinesisvideosignalingchannels.ServiceID, ProviderNameUpper: "KinesisVideoSignalingChannels", HCLKeys: []string{"kinesisvideosignalingchannels"}} - serviceData[KMS] = &ServiceDatum{AWSClientName: "KMS", AWSServiceName: kms.ServiceName, AWSEndpointsID: kms.EndpointsID, AWSServiceID: kms.ServiceID, ProviderNameUpper: "KMS", HCLKeys: []string{"kms"}} - serviceData[LakeFormation] = &ServiceDatum{AWSClientName: "LakeFormation", AWSServiceName: lakeformation.ServiceName, AWSEndpointsID: lakeformation.EndpointsID, AWSServiceID: lakeformation.ServiceID, ProviderNameUpper: "LakeFormation", HCLKeys: []string{"lakeformation"}} - serviceData[Lambda] = &ServiceDatum{AWSClientName: "Lambda", AWSServiceName: lambda.ServiceName, AWSEndpointsID: lambda.EndpointsID, AWSServiceID: lambda.ServiceID, ProviderNameUpper: "Lambda", HCLKeys: []string{"lambda"}} - serviceData[LexModelBuilding] = &ServiceDatum{AWSClientName: "LexModelBuildingService", AWSServiceName: lexmodelbuildingservice.ServiceName, AWSEndpointsID: lexmodelbuildingservice.EndpointsID, AWSServiceID: lexmodelbuildingservice.ServiceID, ProviderNameUpper: "LexModelBuilding", HCLKeys: []string{"lexmodels", "lexmodelbuilding", "lexmodelbuildingservice"}} - serviceData[LexModelsV2] = &ServiceDatum{AWSClientName: "LexModelsV2", AWSServiceName: lexmodelsv2.ServiceName, AWSEndpointsID: lexmodelsv2.EndpointsID, AWSServiceID: lexmodelsv2.ServiceID, ProviderNameUpper: "LexModelsV2", HCLKeys: []string{"lexmodelsv2"}} - serviceData[LexRuntime] = &ServiceDatum{AWSClientName: "LexRuntimeService", AWSServiceName: lexruntimeservice.ServiceName, AWSEndpointsID: lexruntimeservice.EndpointsID, AWSServiceID: lexruntimeservice.ServiceID, ProviderNameUpper: "LexRuntime", HCLKeys: []string{"lexruntime", "lexruntimeservice"}} - serviceData[LexRuntimeV2] = &ServiceDatum{AWSClientName: "LexRuntimeV2", AWSServiceName: lexruntimev2.ServiceName, AWSEndpointsID: lexruntimev2.EndpointsID, AWSServiceID: lexruntimev2.ServiceID, ProviderNameUpper: "LexRuntimeV2", HCLKeys: []string{"lexruntimev2"}} - serviceData[LicenseManager] = &ServiceDatum{AWSClientName: "LicenseManager", AWSServiceName: licensemanager.ServiceName, AWSEndpointsID: licensemanager.EndpointsID, AWSServiceID: licensemanager.ServiceID, ProviderNameUpper: "LicenseManager", HCLKeys: []string{"licensemanager"}} - serviceData[Lightsail] = &ServiceDatum{AWSClientName: "Lightsail", AWSServiceName: lightsail.ServiceName, AWSEndpointsID: lightsail.EndpointsID, AWSServiceID: lightsail.ServiceID, ProviderNameUpper: "Lightsail", HCLKeys: []string{"lightsail"}} - serviceData[Location] = &ServiceDatum{AWSClientName: "LocationService", AWSServiceName: locationservice.ServiceName, AWSEndpointsID: locationservice.EndpointsID, AWSServiceID: locationservice.ServiceID, ProviderNameUpper: "Location", HCLKeys: []string{"location"}} - serviceData[LookoutEquipment] = &ServiceDatum{AWSClientName: "LookoutEquipment", AWSServiceName: lookoutequipment.ServiceName, AWSEndpointsID: lookoutequipment.EndpointsID, AWSServiceID: lookoutequipment.ServiceID, ProviderNameUpper: "LookoutEquipment", HCLKeys: []string{"lookoutequipment"}} - serviceData[LookoutForVision] = &ServiceDatum{AWSClientName: "LookoutForVision", AWSServiceName: lookoutforvision.ServiceName, AWSEndpointsID: lookoutforvision.EndpointsID, AWSServiceID: lookoutforvision.ServiceID, ProviderNameUpper: "LookoutForVision", HCLKeys: []string{"lookoutforvision"}} - serviceData[LookoutMetrics] = &ServiceDatum{AWSClientName: "LookoutMetrics", AWSServiceName: lookoutmetrics.ServiceName, AWSEndpointsID: lookoutmetrics.EndpointsID, AWSServiceID: lookoutmetrics.ServiceID, ProviderNameUpper: "LookoutMetrics", HCLKeys: []string{"lookoutmetrics"}} - serviceData[MachineLearning] = &ServiceDatum{AWSClientName: "MachineLearning", AWSServiceName: machinelearning.ServiceName, AWSEndpointsID: machinelearning.EndpointsID, AWSServiceID: machinelearning.ServiceID, ProviderNameUpper: "MachineLearning", HCLKeys: []string{"machinelearning"}} - serviceData[Macie] = &ServiceDatum{AWSClientName: "Macie", AWSServiceName: macie.ServiceName, AWSEndpointsID: macie.EndpointsID, AWSServiceID: macie.ServiceID, ProviderNameUpper: "Macie", HCLKeys: []string{"macie"}} - serviceData[Macie2] = &ServiceDatum{AWSClientName: "Macie2", AWSServiceName: macie2.ServiceName, AWSEndpointsID: macie2.EndpointsID, AWSServiceID: macie2.ServiceID, ProviderNameUpper: "Macie2", HCLKeys: []string{"macie2"}} - serviceData[ManagedBlockchain] = &ServiceDatum{AWSClientName: "ManagedBlockchain", AWSServiceName: managedblockchain.ServiceName, AWSEndpointsID: managedblockchain.EndpointsID, AWSServiceID: managedblockchain.ServiceID, ProviderNameUpper: "ManagedBlockchain", HCLKeys: []string{"managedblockchain"}} - serviceData[MarketplaceCatalog] = &ServiceDatum{AWSClientName: "MarketplaceCatalog", AWSServiceName: marketplacecatalog.ServiceName, AWSEndpointsID: marketplacecatalog.EndpointsID, AWSServiceID: marketplacecatalog.ServiceID, ProviderNameUpper: "MarketplaceCatalog", HCLKeys: []string{"marketplacecatalog"}} - serviceData[MarketplaceCommerceAnalytics] = &ServiceDatum{AWSClientName: "MarketplaceCommerceAnalytics", AWSServiceName: marketplacecommerceanalytics.ServiceName, AWSEndpointsID: marketplacecommerceanalytics.EndpointsID, AWSServiceID: marketplacecommerceanalytics.ServiceID, ProviderNameUpper: "MarketplaceCommerceAnalytics", HCLKeys: []string{"marketplacecommerceanalytics"}} - serviceData[MarketplaceEntitlement] = &ServiceDatum{AWSClientName: "MarketplaceEntitlementService", AWSServiceName: marketplaceentitlementservice.ServiceName, AWSEndpointsID: marketplaceentitlementservice.EndpointsID, AWSServiceID: marketplaceentitlementservice.ServiceID, ProviderNameUpper: "MarketplaceEntitlement", HCLKeys: []string{"marketplaceentitlement", "marketplaceentitlementservice"}} - serviceData[MarketplaceMetering] = &ServiceDatum{AWSClientName: "MarketplaceMetering", AWSServiceName: marketplacemetering.ServiceName, AWSEndpointsID: marketplacemetering.EndpointsID, AWSServiceID: marketplacemetering.ServiceID, ProviderNameUpper: "MarketplaceMetering", HCLKeys: []string{"marketplacemetering"}} - serviceData[MediaConnect] = &ServiceDatum{AWSClientName: "MediaConnect", AWSServiceName: mediaconnect.ServiceName, AWSEndpointsID: mediaconnect.EndpointsID, AWSServiceID: mediaconnect.ServiceID, ProviderNameUpper: "MediaConnect", HCLKeys: []string{"mediaconnect"}} - serviceData[MediaConvert] = &ServiceDatum{AWSClientName: "MediaConvert", AWSServiceName: mediaconvert.ServiceName, AWSEndpointsID: mediaconvert.EndpointsID, AWSServiceID: mediaconvert.ServiceID, ProviderNameUpper: "MediaConvert", HCLKeys: []string{"mediaconvert"}} - serviceData[MediaLive] = &ServiceDatum{AWSClientName: "MediaLive", AWSServiceName: medialive.ServiceName, AWSEndpointsID: medialive.EndpointsID, AWSServiceID: medialive.ServiceID, ProviderNameUpper: "MediaLive", HCLKeys: []string{"medialive"}} - serviceData[MediaPackage] = &ServiceDatum{AWSClientName: "MediaPackage", AWSServiceName: mediapackage.ServiceName, AWSEndpointsID: mediapackage.EndpointsID, AWSServiceID: mediapackage.ServiceID, ProviderNameUpper: "MediaPackage", HCLKeys: []string{"mediapackage"}} - serviceData[MediaPackageVOD] = &ServiceDatum{AWSClientName: "MediaPackageVOD", AWSServiceName: mediapackagevod.ServiceName, AWSEndpointsID: mediapackagevod.EndpointsID, AWSServiceID: mediapackagevod.ServiceID, ProviderNameUpper: "MediaPackageVOD", HCLKeys: []string{"mediapackagevod"}} - serviceData[MediaStore] = &ServiceDatum{AWSClientName: "MediaStore", AWSServiceName: mediastore.ServiceName, AWSEndpointsID: mediastore.EndpointsID, AWSServiceID: mediastore.ServiceID, ProviderNameUpper: "MediaStore", HCLKeys: []string{"mediastore"}} - serviceData[MediaStoreData] = &ServiceDatum{AWSClientName: "MediaStoreData", AWSServiceName: mediastoredata.ServiceName, AWSEndpointsID: mediastoredata.EndpointsID, AWSServiceID: mediastoredata.ServiceID, ProviderNameUpper: "MediaStoreData", HCLKeys: []string{"mediastoredata"}} - serviceData[MediaTailor] = &ServiceDatum{AWSClientName: "MediaTailor", AWSServiceName: mediatailor.ServiceName, AWSEndpointsID: mediatailor.EndpointsID, AWSServiceID: mediatailor.ServiceID, ProviderNameUpper: "MediaTailor", HCLKeys: []string{"mediatailor"}} - serviceData[MemoryDB] = &ServiceDatum{AWSClientName: "MemoryDB", AWSServiceName: memorydb.ServiceName, AWSEndpointsID: memorydb.EndpointsID, AWSServiceID: memorydb.ServiceID, ProviderNameUpper: "MemoryDB", HCLKeys: []string{"memorydb"}} - serviceData[Mgn] = &ServiceDatum{AWSClientName: "Mgn", AWSServiceName: mgn.ServiceName, AWSEndpointsID: mgn.EndpointsID, AWSServiceID: mgn.ServiceID, ProviderNameUpper: "Mgn", HCLKeys: []string{"mgn"}} - serviceData[MigrationHub] = &ServiceDatum{AWSClientName: "MigrationHub", AWSServiceName: migrationhub.ServiceName, AWSEndpointsID: migrationhub.EndpointsID, AWSServiceID: migrationhub.ServiceID, ProviderNameUpper: "MigrationHub", HCLKeys: []string{"migrationhub"}} - serviceData[MigrationHubConfig] = &ServiceDatum{AWSClientName: "MigrationHubConfig", AWSServiceName: migrationhubconfig.ServiceName, AWSEndpointsID: migrationhubconfig.EndpointsID, AWSServiceID: migrationhubconfig.ServiceID, ProviderNameUpper: "MigrationHubConfig", HCLKeys: []string{"migrationhubconfig"}} - serviceData[Mobile] = &ServiceDatum{AWSClientName: "Mobile", AWSServiceName: mobile.ServiceName, AWSEndpointsID: mobile.EndpointsID, AWSServiceID: mobile.ServiceID, ProviderNameUpper: "Mobile", HCLKeys: []string{"mobile"}} - serviceData[MobileAnalytics] = &ServiceDatum{AWSClientName: "MobileAnalytics", AWSServiceName: mobileanalytics.ServiceName, AWSEndpointsID: mobileanalytics.EndpointsID, AWSServiceID: mobileanalytics.ServiceID, ProviderNameUpper: "MobileAnalytics", HCLKeys: []string{"mobileanalytics"}} - serviceData[MQ] = &ServiceDatum{AWSClientName: "MQ", AWSServiceName: mq.ServiceName, AWSEndpointsID: mq.EndpointsID, AWSServiceID: mq.ServiceID, ProviderNameUpper: "MQ", HCLKeys: []string{"mq"}} - serviceData[MTurk] = &ServiceDatum{AWSClientName: "MTurk", AWSServiceName: mturk.ServiceName, AWSEndpointsID: mturk.EndpointsID, AWSServiceID: mturk.ServiceID, ProviderNameUpper: "MTurk", HCLKeys: []string{"mturk"}} - serviceData[MWAA] = &ServiceDatum{AWSClientName: "MWAA", AWSServiceName: mwaa.ServiceName, AWSEndpointsID: mwaa.EndpointsID, AWSServiceID: mwaa.ServiceID, ProviderNameUpper: "MWAA", HCLKeys: []string{"mwaa"}} - serviceData[Neptune] = &ServiceDatum{AWSClientName: "Neptune", AWSServiceName: neptune.ServiceName, AWSEndpointsID: neptune.EndpointsID, AWSServiceID: neptune.ServiceID, ProviderNameUpper: "Neptune", HCLKeys: []string{"neptune"}} - serviceData[NetworkFirewall] = &ServiceDatum{AWSClientName: "NetworkFirewall", AWSServiceName: networkfirewall.ServiceName, AWSEndpointsID: networkfirewall.EndpointsID, AWSServiceID: networkfirewall.ServiceID, ProviderNameUpper: "NetworkFirewall", HCLKeys: []string{"networkfirewall"}} - serviceData[NetworkManager] = &ServiceDatum{AWSClientName: "NetworkManager", AWSServiceName: networkmanager.ServiceName, AWSEndpointsID: networkmanager.EndpointsID, AWSServiceID: networkmanager.ServiceID, ProviderNameUpper: "NetworkManager", HCLKeys: []string{"networkmanager"}} - serviceData[NimbleStudio] = &ServiceDatum{AWSClientName: "NimbleStudio", AWSServiceName: nimblestudio.ServiceName, AWSEndpointsID: nimblestudio.EndpointsID, AWSServiceID: nimblestudio.ServiceID, ProviderNameUpper: "NimbleStudio", HCLKeys: []string{"nimblestudio"}} - serviceData[OpsWorks] = &ServiceDatum{AWSClientName: "OpsWorks", AWSServiceName: opsworks.ServiceName, AWSEndpointsID: opsworks.EndpointsID, AWSServiceID: opsworks.ServiceID, ProviderNameUpper: "OpsWorks", HCLKeys: []string{"opsworks"}} - serviceData[OpsWorksCM] = &ServiceDatum{AWSClientName: "OpsWorksCM", AWSServiceName: opsworkscm.ServiceName, AWSEndpointsID: opsworkscm.EndpointsID, AWSServiceID: opsworkscm.ServiceID, ProviderNameUpper: "OpsWorksCM", HCLKeys: []string{"opsworkscm"}} - serviceData[Organizations] = &ServiceDatum{AWSClientName: "Organizations", AWSServiceName: organizations.ServiceName, AWSEndpointsID: organizations.EndpointsID, AWSServiceID: organizations.ServiceID, ProviderNameUpper: "Organizations", HCLKeys: []string{"organizations"}} - serviceData[Outposts] = &ServiceDatum{AWSClientName: "Outposts", AWSServiceName: outposts.ServiceName, AWSEndpointsID: outposts.EndpointsID, AWSServiceID: outposts.ServiceID, ProviderNameUpper: "Outposts", HCLKeys: []string{"outposts"}} - serviceData[Personalize] = &ServiceDatum{AWSClientName: "Personalize", AWSServiceName: personalize.ServiceName, AWSEndpointsID: personalize.EndpointsID, AWSServiceID: personalize.ServiceID, ProviderNameUpper: "Personalize", HCLKeys: []string{"personalize"}} - serviceData[PersonalizeEvents] = &ServiceDatum{AWSClientName: "PersonalizeEvents", AWSServiceName: personalizeevents.ServiceName, AWSEndpointsID: personalizeevents.EndpointsID, AWSServiceID: personalizeevents.ServiceID, ProviderNameUpper: "PersonalizeEvents", HCLKeys: []string{"personalizeevents"}} - serviceData[PersonalizeRuntime] = &ServiceDatum{AWSClientName: "PersonalizeRuntime", AWSServiceName: personalizeruntime.ServiceName, AWSEndpointsID: personalizeruntime.EndpointsID, AWSServiceID: personalizeruntime.ServiceID, ProviderNameUpper: "PersonalizeRuntime", HCLKeys: []string{"personalizeruntime"}} - serviceData[PI] = &ServiceDatum{AWSClientName: "PI", AWSServiceName: pi.ServiceName, AWSEndpointsID: pi.EndpointsID, AWSServiceID: pi.ServiceID, ProviderNameUpper: "PI", HCLKeys: []string{"pi"}} - serviceData[Pinpoint] = &ServiceDatum{AWSClientName: "Pinpoint", AWSServiceName: pinpoint.ServiceName, AWSEndpointsID: pinpoint.EndpointsID, AWSServiceID: pinpoint.ServiceID, ProviderNameUpper: "Pinpoint", HCLKeys: []string{"pinpoint"}} - serviceData[PinpointEmail] = &ServiceDatum{AWSClientName: "PinpointEmail", AWSServiceName: pinpointemail.ServiceName, AWSEndpointsID: pinpointemail.EndpointsID, AWSServiceID: pinpointemail.ServiceID, ProviderNameUpper: "PinpointEmail", HCLKeys: []string{"pinpointemail"}} - serviceData[PinpointSMSVoice] = &ServiceDatum{AWSClientName: "PinpointSMSVoice", AWSServiceName: pinpointsmsvoice.ServiceName, AWSEndpointsID: pinpointsmsvoice.EndpointsID, AWSServiceID: pinpointsmsvoice.ServiceID, ProviderNameUpper: "PinpointSMSVoice", HCLKeys: []string{"pinpointsmsvoice"}} - serviceData[Polly] = &ServiceDatum{AWSClientName: "Polly", AWSServiceName: polly.ServiceName, AWSEndpointsID: polly.EndpointsID, AWSServiceID: polly.ServiceID, ProviderNameUpper: "Polly", HCLKeys: []string{"polly"}} - serviceData[Pricing] = &ServiceDatum{AWSClientName: "Pricing", AWSServiceName: pricing.ServiceName, AWSEndpointsID: pricing.EndpointsID, AWSServiceID: pricing.ServiceID, ProviderNameUpper: "Pricing", HCLKeys: []string{"pricing"}} - serviceData[Proton] = &ServiceDatum{AWSClientName: "Proton", AWSServiceName: proton.ServiceName, AWSEndpointsID: proton.EndpointsID, AWSServiceID: proton.ServiceID, ProviderNameUpper: "Proton", HCLKeys: []string{"proton"}} - serviceData[QLDB] = &ServiceDatum{AWSClientName: "QLDB", AWSServiceName: qldb.ServiceName, AWSEndpointsID: qldb.EndpointsID, AWSServiceID: qldb.ServiceID, ProviderNameUpper: "QLDB", HCLKeys: []string{"qldb"}} - serviceData[QLDBSession] = &ServiceDatum{AWSClientName: "QLDBSession", AWSServiceName: qldbsession.ServiceName, AWSEndpointsID: qldbsession.EndpointsID, AWSServiceID: qldbsession.ServiceID, ProviderNameUpper: "QLDBSession", HCLKeys: []string{"qldbsession"}} - serviceData[QuickSight] = &ServiceDatum{AWSClientName: "QuickSight", AWSServiceName: quicksight.ServiceName, AWSEndpointsID: quicksight.EndpointsID, AWSServiceID: quicksight.ServiceID, ProviderNameUpper: "QuickSight", HCLKeys: []string{"quicksight"}} - serviceData[RAM] = &ServiceDatum{AWSClientName: "RAM", AWSServiceName: ram.ServiceName, AWSEndpointsID: ram.EndpointsID, AWSServiceID: ram.ServiceID, ProviderNameUpper: "RAM", HCLKeys: []string{"ram"}} - serviceData[RDS] = &ServiceDatum{AWSClientName: "RDS", AWSServiceName: rds.ServiceName, AWSEndpointsID: rds.EndpointsID, AWSServiceID: rds.ServiceID, ProviderNameUpper: "RDS", HCLKeys: []string{"rds"}} - serviceData[RDSData] = &ServiceDatum{AWSClientName: "RDSDataService", AWSServiceName: rdsdataservice.ServiceName, AWSEndpointsID: rdsdataservice.EndpointsID, AWSServiceID: rdsdataservice.ServiceID, ProviderNameUpper: "RDSData", HCLKeys: []string{"rdsdata", "rdsdataservice"}} - serviceData[Redshift] = &ServiceDatum{AWSClientName: "Redshift", AWSServiceName: redshift.ServiceName, AWSEndpointsID: redshift.EndpointsID, AWSServiceID: redshift.ServiceID, ProviderNameUpper: "Redshift", HCLKeys: []string{"redshift"}} - serviceData[RedshiftData] = &ServiceDatum{AWSClientName: "RedshiftData", AWSServiceName: redshiftdataapiservice.ServiceName, AWSEndpointsID: redshiftdataapiservice.EndpointsID, AWSServiceID: redshiftdataapiservice.ServiceID, ProviderNameUpper: "RedshiftData", HCLKeys: []string{"redshiftdata"}} - serviceData[Rekognition] = &ServiceDatum{AWSClientName: "Rekognition", AWSServiceName: rekognition.ServiceName, AWSEndpointsID: rekognition.EndpointsID, AWSServiceID: rekognition.ServiceID, ProviderNameUpper: "Rekognition", HCLKeys: []string{"rekognition"}} - serviceData[ResourceGroups] = &ServiceDatum{AWSClientName: "ResourceGroups", AWSServiceName: resourcegroups.ServiceName, AWSEndpointsID: resourcegroups.EndpointsID, AWSServiceID: resourcegroups.ServiceID, ProviderNameUpper: "ResourceGroups", HCLKeys: []string{"resourcegroups"}} - serviceData[ResourceGroupsTaggingAPI] = &ServiceDatum{AWSClientName: "ResourceGroupsTaggingAPI", AWSServiceName: resourcegroupstaggingapi.ServiceName, AWSEndpointsID: resourcegroupstaggingapi.EndpointsID, AWSServiceID: resourcegroupstaggingapi.ServiceID, ProviderNameUpper: "ResourceGroupsTaggingAPI", HCLKeys: []string{"resourcegroupstaggingapi", "resourcegroupstagging"}} - serviceData[RoboMaker] = &ServiceDatum{AWSClientName: "RoboMaker", AWSServiceName: robomaker.ServiceName, AWSEndpointsID: robomaker.EndpointsID, AWSServiceID: robomaker.ServiceID, ProviderNameUpper: "RoboMaker", HCLKeys: []string{"robomaker"}} - serviceData[Route53] = &ServiceDatum{AWSClientName: "Route53", AWSServiceName: route53.ServiceName, AWSEndpointsID: route53.EndpointsID, AWSServiceID: route53.ServiceID, ProviderNameUpper: "Route53", HCLKeys: []string{"route53"}} - serviceData[Route53Domains] = &ServiceDatum{AWSClientName: "Route53Domains", AWSServiceName: route53domains.ServiceName, AWSEndpointsID: route53domains.EndpointsID, AWSServiceID: route53domains.ServiceID, ProviderNameUpper: "Route53Domains", HCLKeys: []string{"route53domains"}} - serviceData[Route53RecoveryControlConfig] = &ServiceDatum{AWSClientName: "Route53RecoveryControlConfig", AWSServiceName: route53recoverycontrolconfig.ServiceName, AWSEndpointsID: route53recoverycontrolconfig.EndpointsID, AWSServiceID: route53recoverycontrolconfig.ServiceID, ProviderNameUpper: "Route53RecoveryControlConfig", HCLKeys: []string{"route53recoverycontrolconfig"}} - serviceData[Route53RecoveryReadiness] = &ServiceDatum{AWSClientName: "Route53RecoveryReadiness", AWSServiceName: route53recoveryreadiness.ServiceName, AWSEndpointsID: route53recoveryreadiness.EndpointsID, AWSServiceID: route53recoveryreadiness.ServiceID, ProviderNameUpper: "Route53RecoveryReadiness", HCLKeys: []string{"route53recoveryreadiness"}} - serviceData[Route53Resolver] = &ServiceDatum{AWSClientName: "Route53Resolver", AWSServiceName: route53resolver.ServiceName, AWSEndpointsID: route53resolver.EndpointsID, AWSServiceID: route53resolver.ServiceID, ProviderNameUpper: "Route53Resolver", HCLKeys: []string{"route53resolver"}} - serviceData[S3] = &ServiceDatum{AWSClientName: "S3", AWSServiceName: s3.ServiceName, AWSEndpointsID: s3.EndpointsID, AWSServiceID: s3.ServiceID, ProviderNameUpper: "S3", HCLKeys: []string{"s3"}} - serviceData[S3Control] = &ServiceDatum{AWSClientName: "S3Control", AWSServiceName: s3control.ServiceName, AWSEndpointsID: s3control.EndpointsID, AWSServiceID: s3control.ServiceID, ProviderNameUpper: "S3Control", HCLKeys: []string{"s3control"}} - serviceData[S3Outposts] = &ServiceDatum{AWSClientName: "S3Outposts", AWSServiceName: s3outposts.ServiceName, AWSEndpointsID: s3outposts.EndpointsID, AWSServiceID: s3outposts.ServiceID, ProviderNameUpper: "S3Outposts", HCLKeys: []string{"s3outposts"}} - serviceData[SageMaker] = &ServiceDatum{AWSClientName: "SageMaker", AWSServiceName: sagemaker.ServiceName, AWSEndpointsID: sagemaker.EndpointsID, AWSServiceID: sagemaker.ServiceID, ProviderNameUpper: "SageMaker", HCLKeys: []string{"sagemaker"}} - serviceData[SageMakerEdgeManager] = &ServiceDatum{AWSClientName: "SagemakerEdgeManager", AWSServiceName: sagemakeredgemanager.ServiceName, AWSEndpointsID: sagemakeredgemanager.EndpointsID, AWSServiceID: sagemakeredgemanager.ServiceID, ProviderNameUpper: "SageMakerEdgeManager", HCLKeys: []string{"sagemakeredgemanager"}} - serviceData[SageMakerFeatureStoreRuntime] = &ServiceDatum{AWSClientName: "SageMakerFeatureStoreRuntime", AWSServiceName: sagemakerfeaturestoreruntime.ServiceName, AWSEndpointsID: sagemakerfeaturestoreruntime.EndpointsID, AWSServiceID: sagemakerfeaturestoreruntime.ServiceID, ProviderNameUpper: "SageMakerFeatureStoreRuntime", HCLKeys: []string{"sagemakerfeaturestoreruntime"}} - serviceData[SageMakerRuntime] = &ServiceDatum{AWSClientName: "SageMakerRuntime", AWSServiceName: sagemakerruntime.ServiceName, AWSEndpointsID: sagemakerruntime.EndpointsID, AWSServiceID: sagemakerruntime.ServiceID, ProviderNameUpper: "SageMakerRuntime", HCLKeys: []string{"sagemakerruntime"}} - serviceData[SavingsPlans] = &ServiceDatum{AWSClientName: "SavingsPlans", AWSServiceName: savingsplans.ServiceName, AWSEndpointsID: savingsplans.EndpointsID, AWSServiceID: savingsplans.ServiceID, ProviderNameUpper: "SavingsPlans", HCLKeys: []string{"savingsplans"}} - serviceData[Schemas] = &ServiceDatum{AWSClientName: "Schemas", AWSServiceName: schemas.ServiceName, AWSEndpointsID: schemas.EndpointsID, AWSServiceID: schemas.ServiceID, ProviderNameUpper: "Schemas", HCLKeys: []string{"schemas"}} - serviceData[SecretsManager] = &ServiceDatum{AWSClientName: "SecretsManager", AWSServiceName: secretsmanager.ServiceName, AWSEndpointsID: secretsmanager.EndpointsID, AWSServiceID: secretsmanager.ServiceID, ProviderNameUpper: "SecretsManager", HCLKeys: []string{"secretsmanager"}} - serviceData[SecurityHub] = &ServiceDatum{AWSClientName: "SecurityHub", AWSServiceName: securityhub.ServiceName, AWSEndpointsID: securityhub.EndpointsID, AWSServiceID: securityhub.ServiceID, ProviderNameUpper: "SecurityHub", HCLKeys: []string{"securityhub"}} - serviceData[ServerlessAppRepo] = &ServiceDatum{AWSClientName: "ServerlessApplicationRepository", AWSServiceName: serverlessapplicationrepository.ServiceName, AWSEndpointsID: serverlessapplicationrepository.EndpointsID, AWSServiceID: serverlessapplicationrepository.ServiceID, ProviderNameUpper: "ServerlessAppRepo", HCLKeys: []string{"serverlessrepo", "serverlessapprepo", "serverlessapplicationrepository"}} - serviceData[ServiceCatalog] = &ServiceDatum{AWSClientName: "ServiceCatalog", AWSServiceName: servicecatalog.ServiceName, AWSEndpointsID: servicecatalog.EndpointsID, AWSServiceID: servicecatalog.ServiceID, ProviderNameUpper: "ServiceCatalog", HCLKeys: []string{"servicecatalog"}} - serviceData[ServiceDiscovery] = &ServiceDatum{AWSClientName: "ServiceDiscovery", AWSServiceName: servicediscovery.ServiceName, AWSEndpointsID: servicediscovery.EndpointsID, AWSServiceID: servicediscovery.ServiceID, ProviderNameUpper: "ServiceDiscovery", HCLKeys: []string{"servicediscovery"}} - serviceData[ServiceQuotas] = &ServiceDatum{AWSClientName: "ServiceQuotas", AWSServiceName: servicequotas.ServiceName, AWSEndpointsID: servicequotas.EndpointsID, AWSServiceID: servicequotas.ServiceID, ProviderNameUpper: "ServiceQuotas", HCLKeys: []string{"servicequotas"}} - serviceData[SES] = &ServiceDatum{AWSClientName: "SES", AWSServiceName: ses.ServiceName, AWSEndpointsID: ses.EndpointsID, AWSServiceID: ses.ServiceID, ProviderNameUpper: "SES", HCLKeys: []string{"ses"}} - serviceData[SESV2] = &ServiceDatum{AWSClientName: "SESV2", AWSServiceName: sesv2.ServiceName, AWSEndpointsID: sesv2.EndpointsID, AWSServiceID: sesv2.ServiceID, ProviderNameUpper: "SESV2", HCLKeys: []string{"sesv2"}} - serviceData[SFN] = &ServiceDatum{AWSClientName: "SFN", AWSServiceName: sfn.ServiceName, AWSEndpointsID: sfn.EndpointsID, AWSServiceID: sfn.ServiceID, ProviderNameUpper: "SFN", HCLKeys: []string{"stepfunctions", "sfn"}} - serviceData[Shield] = &ServiceDatum{AWSClientName: "Shield", AWSServiceName: shield.ServiceName, AWSEndpointsID: shield.EndpointsID, AWSServiceID: shield.ServiceID, ProviderNameUpper: "Shield", HCLKeys: []string{"shield"}} - serviceData[Signer] = &ServiceDatum{AWSClientName: "Signer", AWSServiceName: signer.ServiceName, AWSEndpointsID: signer.EndpointsID, AWSServiceID: signer.ServiceID, ProviderNameUpper: "Signer", HCLKeys: []string{"signer"}} - serviceData[SimpleDB] = &ServiceDatum{AWSClientName: "SimpleDB", AWSServiceName: simpledb.ServiceName, AWSEndpointsID: simpledb.EndpointsID, AWSServiceID: simpledb.ServiceID, ProviderNameUpper: "SimpleDB", HCLKeys: []string{"sdb", "simpledb"}} - serviceData[SMS] = &ServiceDatum{AWSClientName: "SMS", AWSServiceName: sms.ServiceName, AWSEndpointsID: sms.EndpointsID, AWSServiceID: sms.ServiceID, ProviderNameUpper: "SMS", HCLKeys: []string{"sms"}} - serviceData[Snowball] = &ServiceDatum{AWSClientName: "Snowball", AWSServiceName: snowball.ServiceName, AWSEndpointsID: snowball.EndpointsID, AWSServiceID: snowball.ServiceID, ProviderNameUpper: "Snowball", HCLKeys: []string{"snowball"}} - serviceData[SNS] = &ServiceDatum{AWSClientName: "SNS", AWSServiceName: sns.ServiceName, AWSEndpointsID: sns.EndpointsID, AWSServiceID: sns.ServiceID, ProviderNameUpper: "SNS", HCLKeys: []string{"sns"}} - serviceData[SQS] = &ServiceDatum{AWSClientName: "SQS", AWSServiceName: sqs.ServiceName, AWSEndpointsID: sqs.EndpointsID, AWSServiceID: sqs.ServiceID, ProviderNameUpper: "SQS", HCLKeys: []string{"sqs"}} - serviceData[SSM] = &ServiceDatum{AWSClientName: "SSM", AWSServiceName: ssm.ServiceName, AWSEndpointsID: ssm.EndpointsID, AWSServiceID: ssm.ServiceID, ProviderNameUpper: "SSM", HCLKeys: []string{"ssm"}} - serviceData[SSMContacts] = &ServiceDatum{AWSClientName: "SSMContacts", AWSServiceName: ssmcontacts.ServiceName, AWSEndpointsID: ssmcontacts.EndpointsID, AWSServiceID: ssmcontacts.ServiceID, ProviderNameUpper: "SSMContacts", HCLKeys: []string{"ssmcontacts"}} - serviceData[SSMIncidents] = &ServiceDatum{AWSClientName: "SSMIncidents", AWSServiceName: ssmincidents.ServiceName, AWSEndpointsID: ssmincidents.EndpointsID, AWSServiceID: ssmincidents.ServiceID, ProviderNameUpper: "SSMIncidents", HCLKeys: []string{"ssmincidents"}} - serviceData[SSO] = &ServiceDatum{AWSClientName: "SSO", AWSServiceName: sso.ServiceName, AWSEndpointsID: sso.EndpointsID, AWSServiceID: sso.ServiceID, ProviderNameUpper: "SSO", HCLKeys: []string{"sso"}} - serviceData[SSOAdmin] = &ServiceDatum{AWSClientName: "SSOAdmin", AWSServiceName: ssoadmin.ServiceName, AWSEndpointsID: ssoadmin.EndpointsID, AWSServiceID: ssoadmin.ServiceID, ProviderNameUpper: "SSOAdmin", HCLKeys: []string{"ssoadmin"}} - serviceData[SSOOIDC] = &ServiceDatum{AWSClientName: "SSOOIDC", AWSServiceName: ssooidc.ServiceName, AWSEndpointsID: ssooidc.EndpointsID, AWSServiceID: ssooidc.ServiceID, ProviderNameUpper: "SSOOIDC", HCLKeys: []string{"ssooidc"}} - serviceData[StorageGateway] = &ServiceDatum{AWSClientName: "StorageGateway", AWSServiceName: storagegateway.ServiceName, AWSEndpointsID: storagegateway.EndpointsID, AWSServiceID: storagegateway.ServiceID, ProviderNameUpper: "StorageGateway", HCLKeys: []string{"storagegateway"}} - serviceData[STS] = &ServiceDatum{AWSClientName: "STS", AWSServiceName: sts.ServiceName, AWSEndpointsID: sts.EndpointsID, AWSServiceID: sts.ServiceID, ProviderNameUpper: "STS", HCLKeys: []string{"sts"}} - serviceData[Support] = &ServiceDatum{AWSClientName: "Support", AWSServiceName: support.ServiceName, AWSEndpointsID: support.EndpointsID, AWSServiceID: support.ServiceID, ProviderNameUpper: "Support", HCLKeys: []string{"support"}} - serviceData[SWF] = &ServiceDatum{AWSClientName: "SWF", AWSServiceName: swf.ServiceName, AWSEndpointsID: swf.EndpointsID, AWSServiceID: swf.ServiceID, ProviderNameUpper: "SWF", HCLKeys: []string{"swf"}} - serviceData[Synthetics] = &ServiceDatum{AWSClientName: "Synthetics", AWSServiceName: synthetics.ServiceName, AWSEndpointsID: synthetics.EndpointsID, AWSServiceID: synthetics.ServiceID, ProviderNameUpper: "Synthetics", HCLKeys: []string{"synthetics"}} - serviceData[Textract] = &ServiceDatum{AWSClientName: "Textract", AWSServiceName: textract.ServiceName, AWSEndpointsID: textract.EndpointsID, AWSServiceID: textract.ServiceID, ProviderNameUpper: "Textract", HCLKeys: []string{"textract"}} - serviceData[TimestreamQuery] = &ServiceDatum{AWSClientName: "TimestreamQuery", AWSServiceName: timestreamquery.ServiceName, AWSEndpointsID: timestreamquery.EndpointsID, AWSServiceID: timestreamquery.ServiceID, ProviderNameUpper: "TimestreamQuery", HCLKeys: []string{"timestreamquery"}} - serviceData[TimestreamWrite] = &ServiceDatum{AWSClientName: "TimestreamWrite", AWSServiceName: timestreamwrite.ServiceName, AWSEndpointsID: timestreamwrite.EndpointsID, AWSServiceID: timestreamwrite.ServiceID, ProviderNameUpper: "TimestreamWrite", HCLKeys: []string{"timestreamwrite"}} - serviceData[Transcribe] = &ServiceDatum{AWSClientName: "TranscribeService", AWSServiceName: transcribeservice.ServiceName, AWSEndpointsID: transcribeservice.EndpointsID, AWSServiceID: transcribeservice.ServiceID, ProviderNameUpper: "Transcribe", HCLKeys: []string{"transcribe", "transcribeservice"}} - serviceData[TranscribeStreaming] = &ServiceDatum{AWSClientName: "TranscribeStreamingService", AWSServiceName: transcribestreamingservice.ServiceName, AWSEndpointsID: transcribestreamingservice.EndpointsID, AWSServiceID: transcribestreamingservice.ServiceID, ProviderNameUpper: "TranscribeStreaming", HCLKeys: []string{"transcribestreaming", "transcribestreamingservice"}} - serviceData[Transfer] = &ServiceDatum{AWSClientName: "Transfer", AWSServiceName: transfer.ServiceName, AWSEndpointsID: transfer.EndpointsID, AWSServiceID: transfer.ServiceID, ProviderNameUpper: "Transfer", HCLKeys: []string{"transfer"}} - serviceData[Translate] = &ServiceDatum{AWSClientName: "Translate", AWSServiceName: translate.ServiceName, AWSEndpointsID: translate.EndpointsID, AWSServiceID: translate.ServiceID, ProviderNameUpper: "Translate", HCLKeys: []string{"translate"}} - serviceData[WAF] = &ServiceDatum{AWSClientName: "WAF", AWSServiceName: waf.ServiceName, AWSEndpointsID: waf.EndpointsID, AWSServiceID: waf.ServiceID, ProviderNameUpper: "WAF", HCLKeys: []string{"waf"}} - serviceData[WAFRegional] = &ServiceDatum{AWSClientName: "WAFRegional", AWSServiceName: wafregional.ServiceName, AWSEndpointsID: wafregional.EndpointsID, AWSServiceID: wafregional.ServiceID, ProviderNameUpper: "WAFRegional", HCLKeys: []string{"wafregional"}} - serviceData[WAFV2] = &ServiceDatum{AWSClientName: "WAFV2", AWSServiceName: wafv2.ServiceName, AWSEndpointsID: wafv2.EndpointsID, AWSServiceID: wafv2.ServiceID, ProviderNameUpper: "WAFV2", HCLKeys: []string{"wafv2"}} - serviceData[WellArchitected] = &ServiceDatum{AWSClientName: "WellArchitected", AWSServiceName: wellarchitected.ServiceName, AWSEndpointsID: wellarchitected.EndpointsID, AWSServiceID: wellarchitected.ServiceID, ProviderNameUpper: "WellArchitected", HCLKeys: []string{"wellarchitected"}} - serviceData[WorkDocs] = &ServiceDatum{AWSClientName: "WorkDocs", AWSServiceName: workdocs.ServiceName, AWSEndpointsID: workdocs.EndpointsID, AWSServiceID: workdocs.ServiceID, ProviderNameUpper: "WorkDocs", HCLKeys: []string{"workdocs"}} - serviceData[WorkLink] = &ServiceDatum{AWSClientName: "WorkLink", AWSServiceName: worklink.ServiceName, AWSEndpointsID: worklink.EndpointsID, AWSServiceID: worklink.ServiceID, ProviderNameUpper: "WorkLink", HCLKeys: []string{"worklink"}} - serviceData[WorkMail] = &ServiceDatum{AWSClientName: "WorkMail", AWSServiceName: workmail.ServiceName, AWSEndpointsID: workmail.EndpointsID, AWSServiceID: workmail.ServiceID, ProviderNameUpper: "WorkMail", HCLKeys: []string{"workmail"}} - serviceData[WorkMailMessageFlow] = &ServiceDatum{AWSClientName: "WorkMailMessageFlow", AWSServiceName: workmailmessageflow.ServiceName, AWSEndpointsID: workmailmessageflow.EndpointsID, AWSServiceID: workmailmessageflow.ServiceID, ProviderNameUpper: "WorkMailMessageFlow", HCLKeys: []string{"workmailmessageflow"}} - serviceData[WorkSpaces] = &ServiceDatum{AWSClientName: "WorkSpaces", AWSServiceName: workspaces.ServiceName, AWSEndpointsID: workspaces.EndpointsID, AWSServiceID: workspaces.ServiceID, ProviderNameUpper: "WorkSpaces", HCLKeys: []string{"workspaces"}} - serviceData[XRay] = &ServiceDatum{AWSClientName: "XRay", AWSServiceName: xray.ServiceName, AWSEndpointsID: xray.EndpointsID, AWSServiceID: xray.ServiceID, ProviderNameUpper: "XRay", HCLKeys: []string{"xray"}} -} - -type Config struct { - AccessKey string - SecretKey string - CredsFilename string - Profile string - Token string - Region string - MaxRetries int - - AssumeRoleARN string - AssumeRoleDurationSeconds int - AssumeRoleExternalID string - AssumeRolePolicy string - AssumeRolePolicyARNs []string - AssumeRoleSessionName string - AssumeRoleTags map[string]string - AssumeRoleTransitiveTagKeys []string - - AllowedAccountIds []string - ForbiddenAccountIds []string - - DefaultTagsConfig *tftags.DefaultConfig - Endpoints map[string]string - IgnoreTagsConfig *tftags.IgnoreConfig - Insecure bool - HTTPProxy string - - SkipCredsValidation bool - SkipGetEC2Platforms bool - SkipRegionValidation bool - SkipRequestingAccountId bool - SkipMetadataApiCheck bool - S3ForcePathStyle bool - - TerraformVersion string -} - -type AWSClient struct { - AccessAnalyzerConn *accessanalyzer.AccessAnalyzer - AccountID string - ACMConn *acm.ACM - ACMPCAConn *acmpca.ACMPCA - AlexaForBusinessConn *alexaforbusiness.AlexaForBusiness - AMPConn *prometheusservice.PrometheusService - AmplifyBackendConn *amplifybackend.AmplifyBackend - AmplifyConn *amplify.Amplify - APIGatewayConn *apigateway.APIGateway - APIGatewayV2Conn *apigatewayv2.ApiGatewayV2 - AppAutoScalingConn *applicationautoscaling.ApplicationAutoScaling - AppConfigConn *appconfig.AppConfig - AppFlowConn *appflow.Appflow - AppIntegrationsConn *appintegrationsservice.AppIntegrationsService - ApplicationCostProfilerConn *applicationcostprofiler.ApplicationCostProfiler - ApplicationDiscoveryConn *applicationdiscoveryservice.ApplicationDiscoveryService - ApplicationInsightsConn *applicationinsights.ApplicationInsights - AppMeshConn *appmesh.AppMesh - AppRegistryConn *appregistry.AppRegistry - AppRunnerConn *apprunner.AppRunner - AppStreamConn *appstream.AppStream - AppSyncConn *appsync.AppSync - AthenaConn *athena.Athena - AuditManagerConn *auditmanager.AuditManager - AugmentedAIRuntimeConn *augmentedairuntime.AugmentedAIRuntime - AutoScalingConn *autoscaling.AutoScaling - AutoScalingPlansConn *autoscalingplans.AutoScalingPlans - BackupConn *backup.Backup - BatchConn *batch.Batch - BraketConn *braket.Braket - BudgetsConn *budgets.Budgets - ChimeConn *chime.Chime - Cloud9Conn *cloud9.Cloud9 - CloudControlConn *cloudcontrolapi.CloudControlApi - CloudDirectoryConn *clouddirectory.CloudDirectory - CloudFormationConn *cloudformation.CloudFormation - CloudFrontConn *cloudfront.CloudFront - CloudHSMV2Conn *cloudhsmv2.CloudHSMV2 - CloudSearchConn *cloudsearch.CloudSearch - CloudSearchDomainConn *cloudsearchdomain.CloudSearchDomain - CloudTrailConn *cloudtrail.CloudTrail - CloudWatchConn *cloudwatch.CloudWatch - CloudWatchLogsConn *cloudwatchlogs.CloudWatchLogs - CodeArtifactConn *codeartifact.CodeArtifact - CodeBuildConn *codebuild.CodeBuild - CodeCommitConn *codecommit.CodeCommit - CodeDeployConn *codedeploy.CodeDeploy - CodeGuruProfilerConn *codeguruprofiler.CodeGuruProfiler - CodeGuruReviewerConn *codegurureviewer.CodeGuruReviewer - CodePipelineConn *codepipeline.CodePipeline - CodeStarConn *codestar.CodeStar - CodeStarConnectionsConn *codestarconnections.CodeStarConnections - CodeStarNotificationsConn *codestarnotifications.CodeStarNotifications - CognitoIdentityConn *cognitoidentity.CognitoIdentity - CognitoIDPConn *cognitoidentityprovider.CognitoIdentityProvider - CognitoSyncConn *cognitosync.CognitoSync - ComprehendConn *comprehend.Comprehend - ComprehendMedicalConn *comprehendmedical.ComprehendMedical - ConfigConn *configservice.ConfigService - ConnectConn *connect.Connect - ConnectContactLensConn *connectcontactlens.ConnectContactLens - ConnectParticipantConn *connectparticipant.ConnectParticipant - CostExplorerConn *costexplorer.CostExplorer - CURConn *costandusagereportservice.CostandUsageReportService - DataExchangeConn *dataexchange.DataExchange - DataPipelineConn *datapipeline.DataPipeline - DataSyncConn *datasync.DataSync - DAXConn *dax.DAX - DefaultTagsConfig *tftags.DefaultConfig - DetectiveConn *detective.Detective - DeviceFarmConn *devicefarm.DeviceFarm - DevOpsGuruConn *devopsguru.DevOpsGuru - DirectConnectConn *directconnect.DirectConnect - DLMConn *dlm.DLM - DMSConn *databasemigrationservice.DatabaseMigrationService - DNSSuffix string - DocDBConn *docdb.DocDB - DSConn *directoryservice.DirectoryService - DynamoDBConn *dynamodb.DynamoDB - DynamoDBStreamsConn *dynamodbstreams.DynamoDBStreams - EC2Conn *ec2.EC2 - EC2InstanceConnectConn *ec2instanceconnect.EC2InstanceConnect - ECRConn *ecr.ECR - ECRPublicConn *ecrpublic.ECRPublic - ECSConn *ecs.ECS - EFSConn *efs.EFS - EKSConn *eks.EKS - ElastiCacheConn *elasticache.ElastiCache - ElasticBeanstalkConn *elasticbeanstalk.ElasticBeanstalk - ElasticInferenceConn *elasticinference.ElasticInference - ElasticsearchConn *elasticsearch.ElasticsearchService - ElasticTranscoderConn *elastictranscoder.ElasticTranscoder - ELBConn *elb.ELB - ELBV2Conn *elbv2.ELBV2 - EMRConn *emr.EMR - EMRContainersConn *emrcontainers.EMRContainers - EventsConn *eventbridge.EventBridge - FinSpaceConn *finspace.Finspace - FinSpaceDataConn *finspacedata.FinSpaceData - FirehoseConn *firehose.Firehose - FISConn *fis.FIS - FMSConn *fms.FMS - ForecastConn *forecastservice.ForecastService - ForecastQueryConn *forecastqueryservice.ForecastQueryService - FraudDetectorConn *frauddetector.FraudDetector - FSxConn *fsx.FSx - GameLiftConn *gamelift.GameLift - GlacierConn *glacier.Glacier - GlobalAcceleratorConn *globalaccelerator.GlobalAccelerator - GlueConn *glue.Glue - GlueDataBrewConn *gluedatabrew.GlueDataBrew - GreengrassConn *greengrass.Greengrass - GreengrassV2Conn *greengrassv2.GreengrassV2 - GroundStationConn *groundstation.GroundStation - GuardDutyConn *guardduty.GuardDuty - HealthConn *health.Health - HealthLakeConn *healthlake.HealthLake - HoneycodeConn *honeycode.Honeycode - IAMConn *iam.IAM - IdentityStoreConn *identitystore.IdentityStore - IgnoreTagsConfig *tftags.IgnoreConfig - ImageBuilderConn *imagebuilder.Imagebuilder - InspectorConn *inspector.Inspector - IoT1ClickDevicesConn *iot1clickdevicesservice.IoT1ClickDevicesService - IoT1ClickProjectsConn *iot1clickprojects.IoT1ClickProjects - IoTAnalyticsConn *iotanalytics.IoTAnalytics - IoTConn *iot.IoT - IoTDataPlaneConn *iotdataplane.IoTDataPlane - IoTDeviceAdvisorConn *iotdeviceadvisor.IoTDeviceAdvisor - IoTEventsConn *iotevents.IoTEvents - IoTEventsDataConn *ioteventsdata.IoTEventsData - IoTFleetHubConn *iotfleethub.IoTFleetHub - IoTJobsDataPlaneConn *iotjobsdataplane.IoTJobsDataPlane - IoTSecureTunnelingConn *iotsecuretunneling.IoTSecureTunneling - IoTSiteWiseConn *iotsitewise.IoTSiteWise - IoTThingsGraphConn *iotthingsgraph.IoTThingsGraph - IoTWirelessConn *iotwireless.IoTWireless - KafkaConn *kafka.Kafka - KendraConn *kendra.Kendra - KinesisAnalyticsConn *kinesisanalytics.KinesisAnalytics - KinesisAnalyticsV2Conn *kinesisanalyticsv2.KinesisAnalyticsV2 - KinesisConn *kinesis.Kinesis - KinesisVideoArchivedMediaConn *kinesisvideoarchivedmedia.KinesisVideoArchivedMedia - KinesisVideoConn *kinesisvideo.KinesisVideo - KinesisVideoMediaConn *kinesisvideomedia.KinesisVideoMedia - KinesisVideoSignalingChannelsConn *kinesisvideosignalingchannels.KinesisVideoSignalingChannels - KMSConn *kms.KMS - LakeFormationConn *lakeformation.LakeFormation - LambdaConn *lambda.Lambda - LexModelBuildingConn *lexmodelbuildingservice.LexModelBuildingService - LexModelsV2Conn *lexmodelsv2.LexModelsV2 - LexRuntimeConn *lexruntimeservice.LexRuntimeService - LexRuntimeV2Conn *lexruntimev2.LexRuntimeV2 - LicenseManagerConn *licensemanager.LicenseManager - LightsailConn *lightsail.Lightsail - LocationConn *locationservice.LocationService - LookoutEquipmentConn *lookoutequipment.LookoutEquipment - LookoutForVisionConn *lookoutforvision.LookoutForVision - LookoutMetricsConn *lookoutmetrics.LookoutMetrics - MachineLearningConn *machinelearning.MachineLearning - Macie2Conn *macie2.Macie2 - MacieConn *macie.Macie - ManagedBlockchainConn *managedblockchain.ManagedBlockchain - MarketplaceCatalogConn *marketplacecatalog.MarketplaceCatalog - MarketplaceCommerceAnalyticsConn *marketplacecommerceanalytics.MarketplaceCommerceAnalytics - MarketplaceEntitlementConn *marketplaceentitlementservice.MarketplaceEntitlementService - MarketplaceMeteringConn *marketplacemetering.MarketplaceMetering - MediaConnectConn *mediaconnect.MediaConnect - MediaConvertAccountConn *mediaconvert.MediaConvert - MediaConvertConn *mediaconvert.MediaConvert - MediaLiveConn *medialive.MediaLive - MediaPackageConn *mediapackage.MediaPackage - MediaPackageVODConn *mediapackagevod.MediaPackageVod - MediaStoreConn *mediastore.MediaStore - MediaStoreDataConn *mediastoredata.MediaStoreData - MediaTailorConn *mediatailor.MediaTailor - MemoryDBConn *memorydb.MemoryDB - MgnConn *mgn.Mgn - MigrationHubConfigConn *migrationhubconfig.MigrationHubConfig - MigrationHubConn *migrationhub.MigrationHub - MobileAnalyticsConn *mobileanalytics.MobileAnalytics - MobileConn *mobile.Mobile - MQConn *mq.MQ - MTurkConn *mturk.MTurk - MWAAConn *mwaa.MWAA - NeptuneConn *neptune.Neptune - NetworkFirewallConn *networkfirewall.NetworkFirewall - NetworkManagerConn *networkmanager.NetworkManager - NimbleStudioConn *nimblestudio.NimbleStudio - OpsWorksCMConn *opsworkscm.OpsWorksCM - OpsWorksConn *opsworks.OpsWorks - OrganizationsConn *organizations.Organizations - OutpostsConn *outposts.Outposts - Partition string - PersonalizeConn *personalize.Personalize - PersonalizeEventsConn *personalizeevents.PersonalizeEvents - PersonalizeRuntimeConn *personalizeruntime.PersonalizeRuntime - PIConn *pi.PI - PinpointConn *pinpoint.Pinpoint - PinpointEmailConn *pinpointemail.PinpointEmail - PinpointSMSVoiceConn *pinpointsmsvoice.PinpointSMSVoice - PollyConn *polly.Polly - PricingConn *pricing.Pricing - ProtonConn *proton.Proton - QLDBConn *qldb.QLDB - QLDBSessionConn *qldbsession.QLDBSession - QuickSightConn *quicksight.QuickSight - RAMConn *ram.RAM - RDSConn *rds.RDS - RDSDataConn *rdsdataservice.RDSDataService - RedshiftConn *redshift.Redshift - RedshiftDataConn *redshiftdataapiservice.RedshiftDataAPIService - Region string - RekognitionConn *rekognition.Rekognition - ResourceGroupsConn *resourcegroups.ResourceGroups - ResourceGroupsTaggingAPIConn *resourcegroupstaggingapi.ResourceGroupsTaggingAPI - ReverseDNSPrefix string - RoboMakerConn *robomaker.RoboMaker - Route53Conn *route53.Route53 - Route53DomainsConn *route53domains.Route53Domains - Route53RecoveryControlConfigConn *route53recoverycontrolconfig.Route53RecoveryControlConfig - Route53RecoveryReadinessConn *route53recoveryreadiness.Route53RecoveryReadiness - Route53ResolverConn *route53resolver.Route53Resolver - S3Conn *s3.S3 - S3ConnURICleaningDisabled *s3.S3 - S3ControlConn *s3control.S3Control - S3OutpostsConn *s3outposts.S3Outposts - SageMakerConn *sagemaker.SageMaker - SageMakerEdgeManagerConn *sagemakeredgemanager.SagemakerEdgeManager - SageMakerFeatureStoreRuntimeConn *sagemakerfeaturestoreruntime.SageMakerFeatureStoreRuntime - SageMakerRuntimeConn *sagemakerruntime.SageMakerRuntime - SavingsPlansConn *savingsplans.SavingsPlans - SchemasConn *schemas.Schemas - SecretsManagerConn *secretsmanager.SecretsManager - SecurityHubConn *securityhub.SecurityHub - ServerlessAppRepoConn *serverlessapplicationrepository.ServerlessApplicationRepository - ServiceCatalogConn *servicecatalog.ServiceCatalog - ServiceDiscoveryConn *servicediscovery.ServiceDiscovery - ServiceQuotasConn *servicequotas.ServiceQuotas - SESConn *ses.SES - SESV2Conn *sesv2.SESV2 - SFNConn *sfn.SFN - ShieldConn *shield.Shield - SignerConn *signer.Signer - SimpleDBConn *simpledb.SimpleDB - SMSConn *sms.SMS - SnowballConn *snowball.Snowball - SNSConn *sns.SNS - SQSConn *sqs.SQS - SSMConn *ssm.SSM - SSMContactsConn *ssmcontacts.SSMContacts - SSMIncidentsConn *ssmincidents.SSMIncidents - SSOAdminConn *ssoadmin.SSOAdmin - SSOConn *sso.SSO - SSOOIDCConn *ssooidc.SSOOIDC - StorageGatewayConn *storagegateway.StorageGateway - STSConn *sts.STS - SupportConn *support.Support - SupportedPlatforms []string - SWFConn *swf.SWF - SyntheticsConn *synthetics.Synthetics - TerraformVersion string - TextractConn *textract.Textract - TimestreamQueryConn *timestreamquery.TimestreamQuery - TimestreamWriteConn *timestreamwrite.TimestreamWrite - TranscribeConn *transcribeservice.TranscribeService - TranscribeStreamingConn *transcribestreamingservice.TranscribeStreamingService - TransferConn *transfer.Transfer - TranslateConn *translate.Translate - WAFConn *waf.WAF - WAFRegionalConn *wafregional.WAFRegional - WAFV2Conn *wafv2.WAFV2 - WellArchitectedConn *wellarchitected.WellArchitected - WorkDocsConn *workdocs.WorkDocs - WorkLinkConn *worklink.WorkLink - WorkMailConn *workmail.WorkMail - WorkMailMessageFlowConn *workmailmessageflow.WorkMailMessageFlow - WorkSpacesConn *workspaces.WorkSpaces - XRayConn *xray.XRay -} - -// PartitionHostname returns a hostname with the provider domain suffix for the partition -// e.g. PREFIX.amazonaws.com -// The prefix should not contain a trailing period. -func (client *AWSClient) PartitionHostname(prefix string) string { - return fmt.Sprintf("%s.%s", prefix, client.DNSSuffix) -} - -// RegionalHostname returns a hostname with the provider domain suffix for the region and partition -// e.g. PREFIX.us-west-2.amazonaws.com -// The prefix should not contain a trailing period. -func (client *AWSClient) RegionalHostname(prefix string) string { - return fmt.Sprintf("%s.%s.%s", prefix, client.Region, client.DNSSuffix) -} - -// Client configures and returns a fully initialized AWSClient -func (c *Config) Client() (interface{}, error) { - // Get the auth and region. This can fail if keys/regions were not - // specified and we're attempting to use the environment. - if !c.SkipRegionValidation { - if err := awsbase.ValidateRegion(c.Region); err != nil { - return nil, err - } - } - - awsbaseConfig := &awsbase.Config{ - AccessKey: c.AccessKey, - AssumeRoleARN: c.AssumeRoleARN, - AssumeRoleDurationSeconds: c.AssumeRoleDurationSeconds, - AssumeRoleExternalID: c.AssumeRoleExternalID, - AssumeRolePolicy: c.AssumeRolePolicy, - AssumeRolePolicyARNs: c.AssumeRolePolicyARNs, - AssumeRoleSessionName: c.AssumeRoleSessionName, - AssumeRoleTags: c.AssumeRoleTags, - AssumeRoleTransitiveTagKeys: c.AssumeRoleTransitiveTagKeys, - CallerDocumentationURL: "https://registry.terraform.io/providers/hashicorp/aws", - CallerName: "Terraform AWS Provider", - CredsFilename: c.CredsFilename, - DebugLogging: logging.IsDebugOrHigher(), - IamEndpoint: c.Endpoints[IAM], - Insecure: c.Insecure, - HTTPProxy: c.HTTPProxy, - MaxRetries: c.MaxRetries, - Profile: c.Profile, - Region: c.Region, - SecretKey: c.SecretKey, - SkipCredsValidation: c.SkipCredsValidation, - SkipMetadataApiCheck: c.SkipMetadataApiCheck, - SkipRequestingAccountId: c.SkipRequestingAccountId, - StsEndpoint: c.Endpoints[STS], - Token: c.Token, - UserAgentProducts: StdUserAgentProducts(c.TerraformVersion), - } +func NewSessionForRegion(cfg *aws.Config, region, terraformVersion string) (*session.Session, error) { + session, err := session.NewSession(cfg) - sess, accountID, Partition, err := awsbase.GetSessionWithAccountIDAndPartition(awsbaseConfig) if err != nil { - return nil, fmt.Errorf("error configuring Terraform AWS Provider: %w", err) - } - - if accountID == "" { - log.Printf("[WARN] AWS account ID not found for provider. See https://www.terraform.io/docs/providers/aws/index.html#skip_requesting_account_id for implications.") - } - - if err := awsbase.ValidateAccountID(accountID, c.AllowedAccountIds, c.ForbiddenAccountIds); err != nil { return nil, err } - DNSSuffix := "amazonaws.com" - if p, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), c.Region); ok { - DNSSuffix = p.DNSSuffix() - } - - client := &AWSClient{ - AccessAnalyzerConn: accessanalyzer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AccessAnalyzer])})), - AccountID: accountID, - ACMConn: acm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ACM])})), - ACMPCAConn: acmpca.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ACMPCA])})), - AlexaForBusinessConn: alexaforbusiness.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AlexaForBusiness])})), - AMPConn: prometheusservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AMP])})), - AmplifyBackendConn: amplifybackend.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AmplifyBackend])})), - AmplifyConn: amplify.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Amplify])})), - APIGatewayConn: apigateway.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[APIGateway])})), - APIGatewayV2Conn: apigatewayv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[APIGatewayV2])})), - AppAutoScalingConn: applicationautoscaling.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppAutoScaling])})), - AppConfigConn: appconfig.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppConfig])})), - AppFlowConn: appflow.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppFlow])})), - AppIntegrationsConn: appintegrationsservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppIntegrations])})), - ApplicationCostProfilerConn: applicationcostprofiler.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ApplicationCostProfiler])})), - ApplicationDiscoveryConn: applicationdiscoveryservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ApplicationDiscovery])})), - ApplicationInsightsConn: applicationinsights.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ApplicationInsights])})), - AppMeshConn: appmesh.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppMesh])})), - AppRegistryConn: appregistry.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppRegistry])})), - AppRunnerConn: apprunner.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppRunner])})), - AppStreamConn: appstream.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppStream])})), - AppSyncConn: appsync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AppSync])})), - AthenaConn: athena.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Athena])})), - AuditManagerConn: auditmanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AuditManager])})), - AugmentedAIRuntimeConn: augmentedairuntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AugmentedAIRuntime])})), - AutoScalingConn: autoscaling.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AutoScaling])})), - AutoScalingPlansConn: autoscalingplans.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[AutoScalingPlans])})), - BackupConn: backup.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Backup])})), - BatchConn: batch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Batch])})), - BraketConn: braket.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Braket])})), - BudgetsConn: budgets.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Budgets])})), - ChimeConn: chime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Chime])})), - Cloud9Conn: cloud9.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Cloud9])})), - CloudControlConn: cloudcontrolapi.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudControl])})), - CloudDirectoryConn: clouddirectory.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudDirectory])})), - CloudFormationConn: cloudformation.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudFormation])})), - CloudFrontConn: cloudfront.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudFront])})), - CloudHSMV2Conn: cloudhsmv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudHSMV2])})), - CloudSearchConn: cloudsearch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudSearch])})), - CloudSearchDomainConn: cloudsearchdomain.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudSearchDomain])})), - CloudTrailConn: cloudtrail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudTrail])})), - CloudWatchConn: cloudwatch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudWatch])})), - CloudWatchLogsConn: cloudwatchlogs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CloudWatchLogs])})), - CodeArtifactConn: codeartifact.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeArtifact])})), - CodeBuildConn: codebuild.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeBuild])})), - CodeCommitConn: codecommit.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeCommit])})), - CodeDeployConn: codedeploy.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeDeploy])})), - CodeGuruProfilerConn: codeguruprofiler.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeGuruProfiler])})), - CodeGuruReviewerConn: codegurureviewer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeGuruReviewer])})), - CodePipelineConn: codepipeline.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodePipeline])})), - CodeStarConn: codestar.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeStar])})), - CodeStarConnectionsConn: codestarconnections.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeStarConnections])})), - CodeStarNotificationsConn: codestarnotifications.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CodeStarNotifications])})), - CognitoIdentityConn: cognitoidentity.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CognitoIdentity])})), - CognitoIDPConn: cognitoidentityprovider.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CognitoIDP])})), - CognitoSyncConn: cognitosync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CognitoSync])})), - ComprehendConn: comprehend.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Comprehend])})), - ComprehendMedicalConn: comprehendmedical.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ComprehendMedical])})), - ConfigConn: configservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ConfigService])})), - ConnectConn: connect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Connect])})), - ConnectContactLensConn: connectcontactlens.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ConnectContactLens])})), - ConnectParticipantConn: connectparticipant.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ConnectParticipant])})), - CostExplorerConn: costexplorer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CostExplorer])})), - CURConn: costandusagereportservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[CUR])})), - DataExchangeConn: dataexchange.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DataExchange])})), - DataPipelineConn: datapipeline.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DataPipeline])})), - DataSyncConn: datasync.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DataSync])})), - DAXConn: dax.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DAX])})), - DefaultTagsConfig: c.DefaultTagsConfig, - DetectiveConn: detective.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Detective])})), - DeviceFarmConn: devicefarm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DeviceFarm])})), - DevOpsGuruConn: devopsguru.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DevOpsGuru])})), - DirectConnectConn: directconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DirectConnect])})), - DLMConn: dlm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DLM])})), - DMSConn: databasemigrationservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DMS])})), - DNSSuffix: DNSSuffix, - DocDBConn: docdb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DocDB])})), - DSConn: directoryservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DS])})), - DynamoDBConn: dynamodb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DynamoDB])})), - DynamoDBStreamsConn: dynamodbstreams.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[DynamoDBStreams])})), - EC2Conn: ec2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[EC2])})), - EC2InstanceConnectConn: ec2instanceconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[EC2InstanceConnect])})), - ECRConn: ecr.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ECR])})), - ECRPublicConn: ecrpublic.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ECRPublic])})), - ECSConn: ecs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ECS])})), - EFSConn: efs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[EFS])})), - EKSConn: eks.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[EKS])})), - ElastiCacheConn: elasticache.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ElastiCache])})), - ElasticBeanstalkConn: elasticbeanstalk.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ElasticBeanstalk])})), - ElasticInferenceConn: elasticinference.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ElasticInference])})), - ElasticsearchConn: elasticsearch.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Elasticsearch])})), - ElasticTranscoderConn: elastictranscoder.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ElasticTranscoder])})), - ELBConn: elb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ELB])})), - ELBV2Conn: elbv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ELBV2])})), - EMRConn: emr.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[EMR])})), - EMRContainersConn: emrcontainers.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[EMRContainers])})), - EventsConn: eventbridge.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Events])})), - FinSpaceConn: finspace.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[FinSpace])})), - FinSpaceDataConn: finspacedata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[FinSpaceData])})), - FirehoseConn: firehose.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Firehose])})), - FISConn: fis.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[FIS])})), - FMSConn: fms.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[FMS])})), - ForecastConn: forecastservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Forecast])})), - ForecastQueryConn: forecastqueryservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ForecastQuery])})), - FraudDetectorConn: frauddetector.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[FraudDetector])})), - FSxConn: fsx.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[FSx])})), - GameLiftConn: gamelift.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[GameLift])})), - GlacierConn: glacier.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Glacier])})), - GlueConn: glue.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Glue])})), - GlueDataBrewConn: gluedatabrew.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[GlueDataBrew])})), - GreengrassConn: greengrass.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Greengrass])})), - GreengrassV2Conn: greengrassv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[GreengrassV2])})), - GroundStationConn: groundstation.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[GroundStation])})), - GuardDutyConn: guardduty.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[GuardDuty])})), - HealthConn: health.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Health])})), - HealthLakeConn: healthlake.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[HealthLake])})), - HoneycodeConn: honeycode.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Honeycode])})), - IAMConn: iam.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IAM])})), - IdentityStoreConn: identitystore.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IdentityStore])})), - IgnoreTagsConfig: c.IgnoreTagsConfig, - ImageBuilderConn: imagebuilder.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ImageBuilder])})), - InspectorConn: inspector.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Inspector])})), - IoT1ClickDevicesConn: iot1clickdevicesservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoT1ClickDevices])})), - IoT1ClickProjectsConn: iot1clickprojects.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoT1ClickProjects])})), - IoTAnalyticsConn: iotanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTAnalytics])})), - IoTConn: iot.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoT])})), - IoTDataPlaneConn: iotdataplane.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTDataPlane])})), - IoTDeviceAdvisorConn: iotdeviceadvisor.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTDeviceAdvisor])})), - IoTEventsConn: iotevents.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTEvents])})), - IoTEventsDataConn: ioteventsdata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTEventsData])})), - IoTFleetHubConn: iotfleethub.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTFleetHub])})), - IoTJobsDataPlaneConn: iotjobsdataplane.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTJobsDataPlane])})), - IoTSecureTunnelingConn: iotsecuretunneling.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTSecureTunneling])})), - IoTSiteWiseConn: iotsitewise.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTSiteWise])})), - IoTThingsGraphConn: iotthingsgraph.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTThingsGraph])})), - IoTWirelessConn: iotwireless.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[IoTWireless])})), - KafkaConn: kafka.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Kafka])})), - KendraConn: kendra.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Kendra])})), - KinesisAnalyticsConn: kinesisanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisAnalytics])})), - KinesisAnalyticsV2Conn: kinesisanalyticsv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisAnalyticsV2])})), - KinesisConn: kinesis.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Kinesis])})), - KinesisVideoArchivedMediaConn: kinesisvideoarchivedmedia.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisVideoArchivedMedia])})), - KinesisVideoConn: kinesisvideo.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisVideo])})), - KinesisVideoMediaConn: kinesisvideomedia.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisVideoMedia])})), - KinesisVideoSignalingChannelsConn: kinesisvideosignalingchannels.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KinesisVideoSignalingChannels])})), - KMSConn: kms.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[KMS])})), - LakeFormationConn: lakeformation.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LakeFormation])})), - LambdaConn: lambda.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Lambda])})), - LexModelBuildingConn: lexmodelbuildingservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LexModelBuilding])})), - LexModelsV2Conn: lexmodelsv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LexModelsV2])})), - LexRuntimeConn: lexruntimeservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LexRuntime])})), - LexRuntimeV2Conn: lexruntimev2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LexRuntimeV2])})), - LicenseManagerConn: licensemanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LicenseManager])})), - LightsailConn: lightsail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Lightsail])})), - LocationConn: locationservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Location])})), - LookoutEquipmentConn: lookoutequipment.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LookoutEquipment])})), - LookoutForVisionConn: lookoutforvision.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LookoutForVision])})), - LookoutMetricsConn: lookoutmetrics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[LookoutMetrics])})), - MachineLearningConn: machinelearning.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MachineLearning])})), - Macie2Conn: macie2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Macie2])})), - MacieConn: macie.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Macie])})), - ManagedBlockchainConn: managedblockchain.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ManagedBlockchain])})), - MarketplaceCatalogConn: marketplacecatalog.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MarketplaceCatalog])})), - MarketplaceCommerceAnalyticsConn: marketplacecommerceanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MarketplaceCommerceAnalytics])})), - MarketplaceEntitlementConn: marketplaceentitlementservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MarketplaceEntitlement])})), - MarketplaceMeteringConn: marketplacemetering.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MarketplaceMetering])})), - MediaConnectConn: mediaconnect.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaConnect])})), - MediaConvertConn: mediaconvert.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaConvert])})), - MediaLiveConn: medialive.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaLive])})), - MediaPackageConn: mediapackage.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaPackage])})), - MediaPackageVODConn: mediapackagevod.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaPackageVOD])})), - MediaStoreConn: mediastore.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaStore])})), - MediaStoreDataConn: mediastoredata.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaStoreData])})), - MediaTailorConn: mediatailor.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MediaTailor])})), - MemoryDBConn: memorydb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MemoryDB])})), - MgnConn: mgn.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Mgn])})), - MigrationHubConfigConn: migrationhubconfig.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MigrationHubConfig])})), - MigrationHubConn: migrationhub.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MigrationHub])})), - MobileAnalyticsConn: mobileanalytics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MobileAnalytics])})), - MobileConn: mobile.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Mobile])})), - MQConn: mq.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MQ])})), - MTurkConn: mturk.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MTurk])})), - MWAAConn: mwaa.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[MWAA])})), - NeptuneConn: neptune.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Neptune])})), - NetworkFirewallConn: networkfirewall.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[NetworkFirewall])})), - NetworkManagerConn: networkmanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[NetworkManager])})), - NimbleStudioConn: nimblestudio.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[NimbleStudio])})), - OpsWorksCMConn: opsworkscm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[OpsWorksCM])})), - OpsWorksConn: opsworks.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[OpsWorks])})), - OrganizationsConn: organizations.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Organizations])})), - OutpostsConn: outposts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Outposts])})), - Partition: Partition, - PersonalizeConn: personalize.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Personalize])})), - PersonalizeEventsConn: personalizeevents.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[PersonalizeEvents])})), - PersonalizeRuntimeConn: personalizeruntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[PersonalizeRuntime])})), - PIConn: pi.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[PI])})), - PinpointConn: pinpoint.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Pinpoint])})), - PinpointEmailConn: pinpointemail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[PinpointEmail])})), - PinpointSMSVoiceConn: pinpointsmsvoice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[PinpointSMSVoice])})), - PollyConn: polly.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Polly])})), - PricingConn: pricing.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Pricing])})), - ProtonConn: proton.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Proton])})), - QLDBConn: qldb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[QLDB])})), - QLDBSessionConn: qldbsession.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[QLDBSession])})), - QuickSightConn: quicksight.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[QuickSight])})), - RAMConn: ram.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[RAM])})), - RDSConn: rds.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[RDS])})), - RDSDataConn: rdsdataservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[RDSData])})), - RedshiftConn: redshift.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Redshift])})), - RedshiftDataConn: redshiftdataapiservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[RedshiftData])})), - Region: c.Region, - RekognitionConn: rekognition.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Rekognition])})), - ResourceGroupsConn: resourcegroups.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ResourceGroups])})), - ResourceGroupsTaggingAPIConn: resourcegroupstaggingapi.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ResourceGroupsTaggingAPI])})), - ReverseDNSPrefix: ReverseDNS(DNSSuffix), - RoboMakerConn: robomaker.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[RoboMaker])})), - Route53DomainsConn: route53domains.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Route53Domains])})), - Route53RecoveryControlConfigConn: route53recoverycontrolconfig.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Route53RecoveryControlConfig])})), - Route53RecoveryReadinessConn: route53recoveryreadiness.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Route53RecoveryReadiness])})), - Route53ResolverConn: route53resolver.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Route53Resolver])})), - S3ControlConn: s3control.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[S3Control])})), - S3OutpostsConn: s3outposts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[S3Outposts])})), - SageMakerConn: sagemaker.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SageMaker])})), - SageMakerEdgeManagerConn: sagemakeredgemanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SageMakerEdgeManager])})), - SageMakerFeatureStoreRuntimeConn: sagemakerfeaturestoreruntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SageMakerFeatureStoreRuntime])})), - SageMakerRuntimeConn: sagemakerruntime.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SageMakerRuntime])})), - SavingsPlansConn: savingsplans.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SavingsPlans])})), - SchemasConn: schemas.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Schemas])})), - SecretsManagerConn: secretsmanager.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SecretsManager])})), - SecurityHubConn: securityhub.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SecurityHub])})), - ServerlessAppRepoConn: serverlessapplicationrepository.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ServerlessAppRepo])})), - ServiceCatalogConn: servicecatalog.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ServiceCatalog])})), - ServiceDiscoveryConn: servicediscovery.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ServiceDiscovery])})), - ServiceQuotasConn: servicequotas.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[ServiceQuotas])})), - SESConn: ses.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SES])})), - SESV2Conn: sesv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SESV2])})), - SFNConn: sfn.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SFN])})), - SignerConn: signer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Signer])})), - SimpleDBConn: simpledb.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SimpleDB])})), - SMSConn: sms.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SMS])})), - SnowballConn: snowball.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Snowball])})), - SNSConn: sns.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SNS])})), - SQSConn: sqs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SQS])})), - SSMConn: ssm.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SSM])})), - SSMContactsConn: ssmcontacts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SSMContacts])})), - SSMIncidentsConn: ssmincidents.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SSMIncidents])})), - SSOAdminConn: ssoadmin.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SSOAdmin])})), - SSOConn: sso.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SSO])})), - SSOOIDCConn: ssooidc.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SSOOIDC])})), - StorageGatewayConn: storagegateway.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[StorageGateway])})), - STSConn: sts.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[STS])})), - SupportConn: support.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Support])})), - SWFConn: swf.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[SWF])})), - SyntheticsConn: synthetics.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Synthetics])})), - TerraformVersion: c.TerraformVersion, - TextractConn: textract.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Textract])})), - TimestreamQueryConn: timestreamquery.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[TimestreamQuery])})), - TimestreamWriteConn: timestreamwrite.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[TimestreamWrite])})), - TranscribeConn: transcribeservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Transcribe])})), - TranscribeStreamingConn: transcribestreamingservice.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[TranscribeStreaming])})), - TransferConn: transfer.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Transfer])})), - TranslateConn: translate.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[Translate])})), - WAFConn: waf.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WAF])})), - WAFRegionalConn: wafregional.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WAFRegional])})), - WAFV2Conn: wafv2.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WAFV2])})), - WellArchitectedConn: wellarchitected.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WellArchitected])})), - WorkDocsConn: workdocs.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WorkDocs])})), - WorkLinkConn: worklink.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WorkLink])})), - WorkMailConn: workmail.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WorkMail])})), - WorkMailMessageFlowConn: workmailmessageflow.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WorkMailMessageFlow])})), - WorkSpacesConn: workspaces.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[WorkSpaces])})), - XRayConn: xray.New(sess.Copy(&aws.Config{Endpoint: aws.String(c.Endpoints[XRay])})), - } - - // "Global" services that require customizations - globalAcceleratorConfig := &aws.Config{ - Endpoint: aws.String(c.Endpoints[GlobalAccelerator]), - } - route53Config := &aws.Config{ - Endpoint: aws.String(c.Endpoints[Route53]), - } - route53RecoveryControlConfigConfig := &aws.Config{ - Endpoint: aws.String(c.Endpoints[Route53RecoveryControlConfig]), - } - route53RecoveryReadinessConfig := &aws.Config{ - Endpoint: aws.String(c.Endpoints[Route53RecoveryReadiness]), - } - shieldConfig := &aws.Config{ - Endpoint: aws.String(c.Endpoints[Shield]), - } - - // Services that require multiple client configurations - s3Config := &aws.Config{ - Endpoint: aws.String(c.Endpoints[S3]), - S3ForcePathStyle: aws.Bool(c.S3ForcePathStyle), - } - - client.S3Conn = s3.New(sess.Copy(s3Config)) - - s3Config.DisableRestProtocolURICleaning = aws.Bool(true) - client.S3ConnURICleaningDisabled = s3.New(sess.Copy(s3Config)) - - // Force "global" services to correct regions - switch Partition { - case endpoints.AwsPartitionID: - globalAcceleratorConfig.Region = aws.String(endpoints.UsWest2RegionID) - route53Config.Region = aws.String(endpoints.UsEast1RegionID) - route53RecoveryControlConfigConfig.Region = aws.String(endpoints.UsWest2RegionID) - route53RecoveryReadinessConfig.Region = aws.String(endpoints.UsWest2RegionID) - shieldConfig.Region = aws.String(endpoints.UsEast1RegionID) - case endpoints.AwsCnPartitionID: - // The AWS Go SDK is missing endpoint information for Route 53 in the AWS China partition. - // This can likely be removed in the future. - if aws.StringValue(route53Config.Endpoint) == "" { - route53Config.Endpoint = aws.String("https://api.route53.cn") - } - route53Config.Region = aws.String(endpoints.CnNorthwest1RegionID) - case endpoints.AwsUsGovPartitionID: - route53Config.Region = aws.String(endpoints.UsGovWest1RegionID) - } - - client.GlobalAcceleratorConn = globalaccelerator.New(sess.Copy(globalAcceleratorConfig)) - client.Route53Conn = route53.New(sess.Copy(route53Config)) - client.Route53RecoveryControlConfigConn = route53recoverycontrolconfig.New(sess.Copy(route53RecoveryControlConfigConfig)) - client.Route53RecoveryReadinessConn = route53recoveryreadiness.New(sess.Copy(route53RecoveryReadinessConfig)) - client.ShieldConn = shield.New(sess.Copy(shieldConfig)) - - client.APIGatewayConn.Handlers.Retry.PushBack(func(r *request.Request) { - // Many operations can return an error such as: - // ConflictException: Unable to complete operation due to concurrent modification. Please try again later. - // Handle them all globally for the service client. - if tfawserr.ErrMessageContains(r.Error, apigateway.ErrCodeConflictException, "try again later") { - r.Retryable = aws.Bool(true) - } - }) - - // Workaround for https://github.com/aws/aws-sdk-go/issues/1472 - client.AppAutoScalingConn.Handlers.Retry.PushBack(func(r *request.Request) { - if !strings.HasPrefix(r.Operation.Name, "Describe") && !strings.HasPrefix(r.Operation.Name, "List") { - return - } - if tfawserr.ErrCodeEquals(r.Error, applicationautoscaling.ErrCodeFailedResourceAccessException) { - r.Retryable = aws.Bool(true) - } - }) - - // StartDeployment operations can return a ConflictException - // if ongoing deployments are in-progress, thus we handle them - // here for the service client. - client.AppConfigConn.Handlers.Retry.PushBack(func(r *request.Request) { - if r.Operation.Name == "StartDeployment" { - if tfawserr.ErrCodeEquals(r.Error, appconfig.ErrCodeConflictException) { - r.Retryable = aws.Bool(true) - } - } - }) - - client.AppSyncConn.Handlers.Retry.PushBack(func(r *request.Request) { - if r.Operation.Name == "CreateGraphqlApi" { - if tfawserr.ErrMessageContains(r.Error, appsync.ErrCodeConcurrentModificationException, "a GraphQL API creation is already in progress") { - r.Retryable = aws.Bool(true) - } - } - }) - - client.ChimeConn.Handlers.Retry.PushBack(func(r *request.Request) { - // When calling CreateVoiceConnector across multiple resources, - // the API can randomly return a BadRequestException without explanation - if r.Operation.Name == "CreateVoiceConnector" { - if tfawserr.ErrMessageContains(r.Error, chime.ErrCodeBadRequestException, "Service received a bad request") { - r.Retryable = aws.Bool(true) - } - } - }) - - client.CloudHSMV2Conn.Handlers.Retry.PushBack(func(r *request.Request) { - if tfawserr.ErrMessageContains(r.Error, cloudhsmv2.ErrCodeCloudHsmInternalFailureException, "request was rejected because of an AWS CloudHSM internal failure") { - r.Retryable = aws.Bool(true) - } - }) - - client.ConfigConn.Handlers.Retry.PushBack(func(r *request.Request) { - // When calling Config Organization Rules API actions immediately - // after Organization creation, the API can randomly return the - // OrganizationAccessDeniedException error for a few minutes, even - // after succeeding a few requests. - switch r.Operation.Name { - case "DeleteOrganizationConfigRule", "DescribeOrganizationConfigRules", "DescribeOrganizationConfigRuleStatuses", "PutOrganizationConfigRule": - if !tfawserr.ErrMessageContains(r.Error, configservice.ErrCodeOrganizationAccessDeniedException, "This action can be only made by AWS Organization's master account.") { - return - } - - // We only want to retry briefly as the default max retry count would - // excessively retry when the error could be legitimate. - // We currently depend on the DefaultRetryer exponential backoff here. - // ~10 retries gives a fair backoff of a few seconds. - if r.RetryCount < 9 { - r.Retryable = aws.Bool(true) - } else { - r.Retryable = aws.Bool(false) - } - case "DeleteOrganizationConformancePack", "DescribeOrganizationConformancePacks", "DescribeOrganizationConformancePackStatuses", "PutOrganizationConformancePack": - if !tfawserr.ErrCodeEquals(r.Error, configservice.ErrCodeOrganizationAccessDeniedException) { - if r.Operation.Name == "DeleteOrganizationConformancePack" && tfawserr.ErrCodeEquals(err, configservice.ErrCodeResourceInUseException) { - r.Retryable = aws.Bool(true) - } - return - } - - // We only want to retry briefly as the default max retry count would - // excessively retry when the error could be legitimate. - // We currently depend on the DefaultRetryer exponential backoff here. - // ~10 retries gives a fair backoff of a few seconds. - if r.RetryCount < 9 { - r.Retryable = aws.Bool(true) - } else { - r.Retryable = aws.Bool(false) - } - } - }) - - client.CloudFormationConn.Handlers.Retry.PushBack(func(r *request.Request) { - if tfawserr.ErrMessageContains(r.Error, cloudformation.ErrCodeOperationInProgressException, "Another Operation on StackSet") { - r.Retryable = aws.Bool(true) - } - }) - - // See https://github.com/aws/aws-sdk-go/pull/1276 - client.DynamoDBConn.Handlers.Retry.PushBack(func(r *request.Request) { - if r.Operation.Name != "PutItem" && r.Operation.Name != "UpdateItem" && r.Operation.Name != "DeleteItem" { - return - } - if tfawserr.ErrMessageContains(r.Error, dynamodb.ErrCodeLimitExceededException, "Subscriber limit exceeded:") { - r.Retryable = aws.Bool(true) - } - }) - - client.EC2Conn.Handlers.Retry.PushBack(func(r *request.Request) { - if r.Operation.Name == "CreateClientVpnEndpoint" { - if tfawserr.ErrMessageContains(r.Error, "OperationNotPermitted", "Endpoint cannot be created while another endpoint is being created") { - r.Retryable = aws.Bool(true) - } - } - - if r.Operation.Name == "CreateVpnConnection" { - if tfawserr.ErrMessageContains(r.Error, "VpnConnectionLimitExceeded", "maximum number of mutating objects has been reached") { - r.Retryable = aws.Bool(true) - } - } - - if r.Operation.Name == "CreateVpnGateway" { - if tfawserr.ErrMessageContains(r.Error, "VpnGatewayLimitExceeded", "maximum number of mutating objects has been reached") { - r.Retryable = aws.Bool(true) - } - } - - if r.Operation.Name == "AttachVpnGateway" || r.Operation.Name == "DetachVpnGateway" { - if tfawserr.ErrMessageContains(r.Error, "InvalidParameterValue", "This call cannot be completed because there are pending VPNs or Virtual Interfaces") { - r.Retryable = aws.Bool(true) - } - } - }) - - client.FMSConn.Handlers.Retry.PushBack(func(r *request.Request) { - // Acceptance testing creates and deletes resources in quick succession. - // The FMS onboarding process into Organizations is opaque to consumers. - // Since we cannot reasonably check this status before receiving the error, - // set the operation as retryable. - switch r.Operation.Name { - case "AssociateAdminAccount": - if tfawserr.ErrMessageContains(r.Error, fms.ErrCodeInvalidOperationException, "Your AWS Organization is currently offboarding with AWS Firewall Manager. Please submit onboard request after offboarded.") { - r.Retryable = aws.Bool(true) - } - case "DisassociateAdminAccount": - if tfawserr.ErrMessageContains(r.Error, fms.ErrCodeInvalidOperationException, "Your AWS Organization is currently onboarding with AWS Firewall Manager and cannot be offboarded.") { - r.Retryable = aws.Bool(true) - } - } - }) + apnInfo := StdUserAgentProducts(terraformVersion) - client.KafkaConn.Handlers.Retry.PushBack(func(r *request.Request) { - if tfawserr.ErrMessageContains(r.Error, kafka.ErrCodeTooManyRequestsException, "Too Many Requests") { - r.Retryable = aws.Bool(true) - } - }) + awsbasev1.SetSessionUserAgent(session, apnInfo, awsbase.UserAgentProducts{}) - client.KinesisConn.Handlers.Retry.PushBack(func(r *request.Request) { - if r.Operation.Name == "CreateStream" { - if tfawserr.ErrMessageContains(r.Error, kinesis.ErrCodeLimitExceededException, "simultaneously be in CREATING or DELETING") { - r.Retryable = aws.Bool(true) - } - } - if r.Operation.Name == "CreateStream" || r.Operation.Name == "DeleteStream" { - if tfawserr.ErrMessageContains(r.Error, kinesis.ErrCodeLimitExceededException, "Rate exceeded for stream") { - r.Retryable = aws.Bool(true) - } - } - }) - - client.OrganizationsConn.Handlers.Retry.PushBack(func(r *request.Request) { - // Retry on the following error: - // ConcurrentModificationException: AWS Organizations can't complete your request because it conflicts with another attempt to modify the same entity. Try again later. - if tfawserr.ErrMessageContains(r.Error, organizations.ErrCodeConcurrentModificationException, "Try again later") { - r.Retryable = aws.Bool(true) - } - }) - - // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/17996 - client.SecurityHubConn.Handlers.Retry.PushBack(func(r *request.Request) { - switch r.Operation.Name { - case "EnableOrganizationAdminAccount": - if tfawserr.ErrCodeEquals(r.Error, securityhub.ErrCodeResourceConflictException) { - r.Retryable = aws.Bool(true) - } - } - }) - - // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/19215 - client.SSOAdminConn.Handlers.Retry.PushBack(func(r *request.Request) { - if r.Operation.Name == "AttachManagedPolicyToPermissionSet" || r.Operation.Name == "DetachManagedPolicyFromPermissionSet" { - if tfawserr.ErrCodeEquals(r.Error, ssoadmin.ErrCodeConflictException) { - r.Retryable = aws.Bool(true) - } - } - }) - - client.StorageGatewayConn.Handlers.Retry.PushBack(func(r *request.Request) { - // InvalidGatewayRequestException: The specified gateway proxy network connection is busy. - if tfawserr.ErrMessageContains(r.Error, storagegateway.ErrCodeInvalidGatewayRequestException, "The specified gateway proxy network connection is busy") { - r.Retryable = aws.Bool(true) - } - }) - - client.WAFV2Conn.Handlers.Retry.PushBack(func(r *request.Request) { - if tfawserr.ErrMessageContains(r.Error, wafv2.ErrCodeWAFInternalErrorException, "Retry your request") { - r.Retryable = aws.Bool(true) - } - - if tfawserr.ErrMessageContains(r.Error, wafv2.ErrCodeWAFServiceLinkedRoleErrorException, "Retry") { - r.Retryable = aws.Bool(true) - } - - if r.Operation.Name == "CreateIPSet" || r.Operation.Name == "CreateRegexPatternSet" || - r.Operation.Name == "CreateRuleGroup" || r.Operation.Name == "CreateWebACL" { - // WAFv2 supports tag on create which can result in the below error codes according to the documentation - if tfawserr.ErrMessageContains(r.Error, wafv2.ErrCodeWAFTagOperationException, "Retry your request") { - r.Retryable = aws.Bool(true) - } - if tfawserr.ErrMessageContains(err, wafv2.ErrCodeWAFTagOperationInternalErrorException, "Retry your request") { - r.Retryable = aws.Bool(true) - } - } - }) - - if !c.SkipGetEC2Platforms { - supportedPlatforms, err := GetSupportedEC2Platforms(client.EC2Conn) - if err != nil { - // We intentionally fail *silently* because there's a chance - // user just doesn't have ec2:DescribeAccountAttributes permissions - log.Printf("[WARN] Unable to get supported EC2 platforms: %s", err) - } else { - client.SupportedPlatforms = supportedPlatforms - } - } - - return client, nil + return session.Copy(&aws.Config{Region: aws.String(region)}), nil } -func StdUserAgentProducts(terraformVersion string) []*awsbase.UserAgentProduct { - return []*awsbase.UserAgentProduct{ - {Name: "APN", Version: "1.0"}, - {Name: "HashiCorp", Version: "1.0"}, - {Name: "Terraform", Version: terraformVersion, Extra: []string{"+https://www.terraform.io"}}, - {Name: "terraform-provider-aws", Version: version.ProviderVersion, Extra: []string{"+https://registry.terraform.io/providers/hashicorp/aws"}}, +func StdUserAgentProducts(terraformVersion string) *awsbase.APNInfo { + return &awsbase.APNInfo{ + PartnerName: "HashiCorp", + Products: []awsbase.UserAgentProduct{ + {Name: "Terraform", Version: terraformVersion, Comment: "+https://www.terraform.io"}, + {Name: "terraform-provider-aws", Version: version.ProviderVersion, Comment: "+https://registry.terraform.io/providers/hashicorp/aws"}, + }, } } -func NewSessionForRegion(cfg *aws.Config, region, terraformVersion string) (*session.Session, error) { - session, err := session.NewSession(cfg) - - if err != nil { - return nil, err - } - - userAgentProducts := StdUserAgentProducts(terraformVersion) - // Copied from github.com/hashicorp/aws-sdk-go-base@v1.0.0/session.go: - for i := len(userAgentProducts) - 1; i >= 0; i-- { - product := userAgentProducts[i] - session.Handlers.Build.PushFront(request.MakeAddToUserAgentHandler(product.Name, product.Version, product.Extra...)) - } - - return session.Copy(&aws.Config{Region: aws.String(region)}), nil -} - func HasEC2Classic(platforms []string) bool { for _, p := range platforms { if p == "EC2" { @@ -1858,7 +67,7 @@ func GetSupportedEC2Platforms(conn *ec2.EC2) ([]string, error) { } if len(platforms) == 0 { - return nil, fmt.Errorf("No EC2 platforms detected") + return nil, fmt.Errorf("no EC2 platforms detected") } return platforms, nil @@ -1874,48 +83,3 @@ func ReverseDNS(hostname string) string { return strings.Join(parts, ".") } - -// This is a global MutexKV for use within this plugin. -var GlobalMutexKV = NewMutexKV() - -func ServiceForHCLKey(s string) (string, error) { - for k, v := range serviceData { - for _, hclKey := range v.HCLKeys { - if s == hclKey { - return k, nil - } - } - } - - return "", fmt.Errorf("unable to find service for HCL key %s", s) -} - -func ServiceKeys() []string { - keys := make([]string, len(serviceData)) - - i := 0 - for k := range serviceData { - keys[i] = k - i++ - } - - return keys -} - -func HCLKeys() []string { - keys := make([]string, 0) - - for _, v := range serviceData { - keys = append(keys, v.HCLKeys...) - } - - return keys -} - -func ServiceProviderNameUpper(key string) (string, error) { - if v, ok := serviceData[key]; ok { - return v.ProviderNameUpper, nil - } - - return "", fmt.Errorf("no service data found for %s", key) -} diff --git a/internal/conns/conns_test.go b/internal/conns/conns_test.go deleted file mode 100644 index 2b403cf..0000000 --- a/internal/conns/conns_test.go +++ /dev/null @@ -1,132 +0,0 @@ -package conns - -import ( - "reflect" - "testing" - - "github.com/aws/aws-sdk-go/service/ec2" - awsbase "github.com/hashicorp/aws-sdk-go-base" -) - -func TestAWSClientPartitionHostname(t *testing.T) { - testCases := []struct { - Name string - AWSClient *AWSClient - Prefix string - Expected string - }{ - { - Name: "AWS Commercial", - AWSClient: &AWSClient{ - DNSSuffix: "amazonaws.com", - }, - Prefix: "test", - Expected: "test.amazonaws.com", - }, - { - Name: "AWS China", - AWSClient: &AWSClient{ - DNSSuffix: "amazonaws.com.cn", - }, - Prefix: "test", - Expected: "test.amazonaws.com.cn", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - got := testCase.AWSClient.PartitionHostname(testCase.Prefix) - - if got != testCase.Expected { - t.Errorf("got %s, expected %s", got, testCase.Expected) - } - }) - } -} - -func TestAWSClientRegionalHostname(t *testing.T) { - testCases := []struct { - Name string - AWSClient *AWSClient - Prefix string - Expected string - }{ - { - Name: "AWS Commercial", - AWSClient: &AWSClient{ - DNSSuffix: "amazonaws.com", - Region: "us-west-2", //lintignore:AWSAT003 - }, - Prefix: "test", - Expected: "test.us-west-2.amazonaws.com", //lintignore:AWSAT003 - }, - { - Name: "AWS China", - AWSClient: &AWSClient{ - DNSSuffix: "amazonaws.com.cn", - Region: "cn-northwest-1", //lintignore:AWSAT003 - }, - Prefix: "test", - Expected: "test.cn-northwest-1.amazonaws.com.cn", //lintignore:AWSAT003 - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - got := testCase.AWSClient.RegionalHostname(testCase.Prefix) - - if got != testCase.Expected { - t.Errorf("got %s, expected %s", got, testCase.Expected) - } - }) - } -} - -func TestGetSupportedEC2Platforms(t *testing.T) { - ec2Endpoints := []*awsbase.MockEndpoint{ - { - Request: &awsbase.MockRequest{ - Method: "POST", - Uri: "/", - Body: "Action=DescribeAccountAttributes&AttributeName.1=supported-platforms&Version=2016-11-15", - }, - Response: &awsbase.MockResponse{ - StatusCode: 200, - Body: test_ec2_describeAccountAttributes_response, - ContentType: "text/xml", - }, - }, - } - closeFunc, sess, err := awsbase.GetMockedAwsApiSession("EC2", ec2Endpoints) - if err != nil { - t.Fatal(err) - } - defer closeFunc() - conn := ec2.New(sess) - - platforms, err := GetSupportedEC2Platforms(conn) - if err != nil { - t.Fatalf("Expected no error, received: %s", err) - } - expectedPlatforms := []string{"VPC", "EC2"} - if !reflect.DeepEqual(platforms, expectedPlatforms) { - t.Fatalf("Received platforms: %q\nExpected: %q\n", platforms, expectedPlatforms) - } -} - -var test_ec2_describeAccountAttributes_response = ` - 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE - - - supported-platforms - - - VPC - - - EC2 - - - - -` \ No newline at end of file diff --git a/internal/conns/envvar.go b/internal/conns/envvar.go index 1449505..7d07d95 100644 --- a/internal/conns/envvar.go +++ b/internal/conns/envvar.go @@ -16,7 +16,7 @@ const ( // Container credentials endpoint // See also AWS_ACCESS_KEY_ID and AWS_PROFILE - EnvVarContainerCredentialsFullUri = "AWS_CONTAINER_CREDENTIALS_FULL_URI" + EnvVarContainerCredentialsFullURI = "AWS_CONTAINER_CREDENTIALS_FULL_URI" // Default AWS region for tests (AWS Go SDK does not provide this as constant) EnvVarDefaultRegion = "AWS_DEFAULT_REGION" @@ -85,7 +85,7 @@ func GetEnvVarWithDefault(variable string, defaultValue string) string { // RequireOneOfEnvVar verifies that at least one environment variable is non-empty or returns an error. // -// If at lease one environment variable is non-empty, returns the first name and value. +// If at least one environment variable is non-empty, returns the first name and value. func RequireOneOfEnvVar(names []string, usageMessage string) (string, string, error) { for _, variable := range names { value := os.Getenv(variable) @@ -111,7 +111,7 @@ func RequireEnvVar(name string, usageMessage string) (string, error) { // FailIfAllEnvVarEmpty verifies that at least one environment variable is non-empty or fails the test. // -// If at lease one environment variable is non-empty, returns the first name and value. +// If at least one environment variable is non-empty, returns the first name and value. func FailIfAllEnvVarEmpty(t testing.T, names []string, usageMessage string) (string, string) { t.Helper() @@ -156,7 +156,7 @@ func SkipIfEnvVarEmpty(t testing.T, name string, usageMessage string) string { // SkipIfAllEnvVarEmpty verifies that at least one environment variable is non-empty or skips the test. // -// If at lease one environment variable is non-empty, returns the first name and value. +// If at least one environment variable is non-empty, returns the first name and value. func SkipIfAllEnvVarEmpty(t testing.T, names []string, usageMessage string) (string, string) { t.Helper() @@ -167,4 +167,4 @@ func SkipIfAllEnvVarEmpty(t testing.T, names []string, usageMessage string) (str } return name, value -} \ No newline at end of file +} diff --git a/internal/conns/envvar_test.go b/internal/conns/envvar_test.go deleted file mode 100644 index ae2d9b2..0000000 --- a/internal/conns/envvar_test.go +++ /dev/null @@ -1,327 +0,0 @@ -package conns - -import ( - "os" - "testing" - - testingiface "github.com/mitchellh/go-testing-interface" -) - -func TestGetWithDefault(t *testing.T) { - envVar := "TESTENVVAR_GETWITHDEFAULT" - - t.Run("missing", func(t *testing.T) { - want := "default" - - os.Unsetenv(envVar) - - got := GetEnvVarWithDefault(envVar, want) - - if got != want { - t.Fatalf("expected %s, got %s", want, got) - } - }) - - t.Run("empty", func(t *testing.T) { - want := "default" - - os.Setenv(envVar, "") - defer os.Unsetenv(envVar) - - got := GetEnvVarWithDefault(envVar, want) - - if got != want { - t.Fatalf("expected %s, got %s", want, got) - } - }) - - t.Run("not empty", func(t *testing.T) { - want := "notempty" - - os.Setenv(envVar, want) - defer os.Unsetenv(envVar) - - got := GetEnvVarWithDefault(envVar, "default") - - if got != want { - t.Fatalf("expected %s, got %s", want, got) - } - }) -} - -func TestRequireOneOf(t *testing.T) { - envVar1 := "TESTENVVAR_REQUIREONEOF1" - envVar2 := "TESTENVVAR_REQUIREONEOF2" - envVars := []string{envVar1, envVar2} - - t.Run("missing", func(t *testing.T) { - for _, envVar := range envVars { - os.Unsetenv(envVar) - } - - _, _, err := RequireOneOfEnvVar(envVars, "usage") - - if err == nil { - t.Fatal("expected error") - } - }) - - t.Run("all empty", func(t *testing.T) { - os.Setenv(envVar1, "") - os.Setenv(envVar2, "") - defer unsetEnvVars(envVars) - - _, _, err := RequireOneOfEnvVar(envVars, "usage") - - if err == nil { - t.Fatal("expected error") - } - }) - - t.Run("some empty", func(t *testing.T) { - wantValue := "pickme" - - os.Setenv(envVar1, "") - os.Setenv(envVar2, wantValue) - defer unsetEnvVars(envVars) - - gotName, gotValue, err := RequireOneOfEnvVar(envVars, "usage") - - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if gotName != envVar2 { - t.Fatalf("expected name: %s, got: %s", envVar2, gotName) - } - - if gotValue != wantValue { - t.Fatalf("expected value: %s, got: %s", wantValue, gotValue) - } - }) - - t.Run("all not empty", func(t *testing.T) { - wantValue := "pickme" - - os.Setenv(envVar1, wantValue) - os.Setenv(envVar2, "other") - defer unsetEnvVars(envVars) - - gotName, gotValue, err := RequireOneOfEnvVar(envVars, "usage") - - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if gotName != envVar1 { - t.Fatalf("expected name: %s, got: %s", envVar1, gotName) - } - - if gotValue != wantValue { - t.Fatalf("expected value: %s, got: %s", wantValue, gotValue) - } - }) -} - -func TestRequire(t *testing.T) { - envVar := "TESTENVVAR_REQUIRE" - - t.Run("missing", func(t *testing.T) { - os.Unsetenv(envVar) - - _, err := RequireEnvVar(envVar, "usage") - - if err == nil { - t.Fatal("expected error") - } - }) - - t.Run("empty", func(t *testing.T) { - os.Setenv(envVar, "") - defer os.Unsetenv(envVar) - - _, err := RequireEnvVar(envVar, "usage") - - if err == nil { - t.Fatal("expected error") - } - }) - - t.Run("not empty", func(t *testing.T) { - want := "notempty" - - os.Setenv(envVar, want) - defer os.Unsetenv(envVar) - - got, err := RequireEnvVar(envVar, "usage") - - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - - if got != want { - t.Fatalf("expected value: %s, got: %s", want, got) - } - }) -} - -func TestTestFailIfAllEmpty(t *testing.T) { - envVar1 := "TESTENVVAR_FAILIFALLEMPTY1" - envVar2 := "TESTENVVAR_FAILIFALLEMPTY2" - envVars := []string{envVar1, envVar2} - - t.Run("missing", func(t *testing.T) { - defer testingifaceRecover() - - for _, envVar := range envVars { - os.Unsetenv(envVar) - } - - FailIfAllEnvVarEmpty(&testingiface.RuntimeT{}, envVars, "usage") - - t.Fatal("expected to fail previously") - }) - - t.Run("all empty", func(t *testing.T) { - defer testingifaceRecover() - - os.Setenv(envVar1, "") - os.Setenv(envVar2, "") - defer unsetEnvVars(envVars) - - FailIfAllEnvVarEmpty(&testingiface.RuntimeT{}, envVars, "usage") - - t.Fatal("expected to fail previously") - }) - - t.Run("some empty", func(t *testing.T) { - wantValue := "pickme" - - os.Setenv(envVar1, "") - os.Setenv(envVar2, wantValue) - defer unsetEnvVars(envVars) - - gotName, gotValue := FailIfAllEnvVarEmpty(&testingiface.RuntimeT{}, envVars, "usage") - - if gotName != envVar2 { - t.Fatalf("expected name: %s, got: %s", envVar2, gotName) - } - - if gotValue != wantValue { - t.Fatalf("expected value: %s, got: %s", wantValue, gotValue) - } - }) - - t.Run("all not empty", func(t *testing.T) { - wantValue := "pickme" - - os.Setenv(envVar1, wantValue) - os.Setenv(envVar2, "other") - defer unsetEnvVars(envVars) - - gotName, gotValue := FailIfAllEnvVarEmpty(&testingiface.RuntimeT{}, envVars, "usage") - - if gotName != envVar1 { - t.Fatalf("expected name: %s, got: %s", envVar1, gotName) - } - - if gotValue != wantValue { - t.Fatalf("expected value: %s, got: %s", wantValue, gotValue) - } - }) -} - -func TestTestFailIfEmpty(t *testing.T) { - envVar := "TESTENVVAR_FAILIFEMPTY" - - t.Run("missing", func(t *testing.T) { - defer testingifaceRecover() - - os.Unsetenv(envVar) - - FailIfEnvVarEmpty(&testingiface.RuntimeT{}, envVar, "usage") - - t.Fatal("expected to fail previously") - }) - - t.Run("empty", func(t *testing.T) { - defer testingifaceRecover() - - os.Setenv(envVar, "") - defer os.Unsetenv(envVar) - - FailIfEnvVarEmpty(&testingiface.RuntimeT{}, envVar, "usage") - - t.Fatal("expected to fail previously") - }) - - t.Run("not empty", func(t *testing.T) { - want := "notempty" - - os.Setenv(envVar, want) - defer os.Unsetenv(envVar) - - got := FailIfEnvVarEmpty(&testingiface.RuntimeT{}, envVar, "usage") - - if got != want { - t.Fatalf("expected value: %s, got: %s", want, got) - } - }) -} - -func TestTestSkipIfEmpty(t *testing.T) { - envVar := "TESTENVVAR_SKIPIFEMPTY" - - t.Run("missing", func(t *testing.T) { - mockT := &testingiface.RuntimeT{} - - os.Unsetenv(envVar) - - SkipIfEnvVarEmpty(mockT, envVar, "usage") - - if !mockT.Skipped() { - t.Fatal("expected to skip previously") - } - }) - - t.Run("empty", func(t *testing.T) { - mockT := &testingiface.RuntimeT{} - - os.Setenv(envVar, "") - defer os.Unsetenv(envVar) - - SkipIfEnvVarEmpty(mockT, envVar, "usage") - - if !mockT.Skipped() { - t.Fatal("expected to skip previously") - } - }) - - t.Run("not empty", func(t *testing.T) { - want := "notempty" - - os.Setenv(envVar, want) - defer os.Unsetenv(envVar) - - got := SkipIfEnvVarEmpty(&testingiface.RuntimeT{}, envVar, "usage") - - if got != want { - t.Fatalf("expected value: %s, got: %s", want, got) - } - }) -} - -func testingifaceRecover() { - r := recover() - - // this string is hardcoded in github.com/mitchellh/go-testing-interface - if s, ok := r.(string); !ok || s != "testing.T failed, see logs for output (if any)" { - panic(r) - } -} - -func unsetEnvVars(envVars []string) { - for _, envVar := range envVars { - os.Unsetenv(envVar) - } -} diff --git a/internal/conns/generate.go b/internal/conns/generate.go new file mode 100644 index 0000000..dfbd9c4 --- /dev/null +++ b/internal/conns/generate.go @@ -0,0 +1,5 @@ +//go:generate go run ../generate/awsclient/main.go +//go:generate go run ../generate/clientconfig/main.go +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package conns diff --git a/internal/conns/mutexkv.go b/internal/conns/mutexkv.go index a3ce219..e33c626 100644 --- a/internal/conns/mutexkv.go +++ b/internal/conns/mutexkv.go @@ -5,6 +5,9 @@ import ( "sync" ) +// GlobalMutexKV is a global MutexKV for use within this plugin. +var GlobalMutexKV = NewMutexKV() + // MutexKV is a simple key/value store for arbitrary mutexes. It can be used to // serialize changes across arbitrary collaborators that share knowledge of the // keys they must serialize on. @@ -45,4 +48,4 @@ func NewMutexKV() *MutexKV { return &MutexKV{ store: make(map[string]*sync.Mutex), } -} \ No newline at end of file +} diff --git a/internal/conns/mutexkv_test.go b/internal/conns/mutexkv_test.go deleted file mode 100644 index eda912d..0000000 --- a/internal/conns/mutexkv_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package conns - -import ( - "testing" - "time" -) - -func TestMutexKVLock(t *testing.T) { - mkv := NewMutexKV() - - mkv.Lock("foo") - - doneCh := make(chan struct{}) - - go func() { - mkv.Lock("foo") - close(doneCh) - }() - - select { - case <-doneCh: - t.Fatal("Second lock was able to be taken. This shouldn't happen.") - case <-time.After(50 * time.Millisecond): - // pass - } -} - -func TestMutexKVUnlock(t *testing.T) { - mkv := NewMutexKV() - - mkv.Lock("foo") - mkv.Unlock("foo") - - doneCh := make(chan struct{}) - - go func() { - mkv.Lock("foo") - close(doneCh) - }() - - select { - case <-doneCh: - // pass - case <-time.After(50 * time.Millisecond): - t.Fatal("Second lock blocked after unlock. This shouldn't happen.") - } -} - -func TestMutexKVDifferentKeys(t *testing.T) { - mkv := NewMutexKV() - - mkv.Lock("foo") - - doneCh := make(chan struct{}) - - go func() { - mkv.Lock("bar") - close(doneCh) - }() - - select { - case <-doneCh: - // pass - case <-time.After(50 * time.Millisecond): - t.Fatal("Second lock on a different key blocked. This shouldn't happen.") - } -} diff --git a/internal/experimental/nullable/bool.go b/internal/experimental/nullable/bool.go new file mode 100644 index 0000000..7bbcc80 --- /dev/null +++ b/internal/experimental/nullable/bool.go @@ -0,0 +1,66 @@ +package nullable + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + TypeNullableBool = schema.TypeString +) + +type Bool string + +func (b Bool) IsNull() bool { + return b == "" +} + +func (b Bool) Value() (bool, bool, error) { + if b.IsNull() { + return false, true, nil + } + + value, err := strconv.ParseBool(string(b)) + if err != nil { + return false, false, err + } + return value, false, nil +} + +func NewBool(v bool) Bool { + return Bool(strconv.FormatBool(v)) +} + +// ValidateTypeStringNullableBool provides custom error messaging for TypeString booleans +// Some arguments require a boolean value or unspecified, empty field. +func ValidateTypeStringNullableBool(v interface{}, k string) (ws []string, es []error) { + value, ok := v.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if value == "" { + return + } + + if _, err := strconv.ParseBool(value); err != nil { + es = append(es, fmt.Errorf("%s: cannot parse '%s' as boolean: %w", k, value, err)) + } + + return +} + +// DiffSuppressNullableBoolFalseAsNull allows false to be treated equivalently to null. +// This can be used to allow a practitioner to set false when the API requires a null value, +// as a convenience. +func DiffSuppressNullableBoolFalseAsNull(k, o, n string, d *schema.ResourceData) bool { + ov, onull, _ := Bool(o).Value() + nv, nnull, _ := Bool(n).Value() + if !ov && nnull || onull && !nv { + return true + } + return false +} \ No newline at end of file diff --git a/internal/experimental/nullable/bool_test.go b/internal/experimental/nullable/bool_test.go new file mode 100644 index 0000000..39f4d05 --- /dev/null +++ b/internal/experimental/nullable/bool_test.go @@ -0,0 +1,82 @@ +package nullable + +import ( + "errors" + "regexp" + "strconv" + "testing" +) + +func TestNullableBool(t *testing.T) { + cases := []struct { + val string + expectNull bool + expectedValue bool + expectedErr error + }{ + { + val: "true", + expectNull: false, + expectedValue: true, + }, + { + val: "false", + expectNull: false, + expectedValue: false, + }, + { + val: "", + expectNull: true, + expectedValue: false, + }, + { + val: "A", + expectNull: false, + expectedValue: false, + expectedErr: strconv.ErrSyntax, + }, + } + + for i, tc := range cases { + v := Bool(tc.val) + + if null := v.IsNull(); null != tc.expectNull { + t.Fatalf("expected test case %d IsNull to return %t, got %t", i, null, tc.expectNull) + } + + value, null, err := v.Value() + if value != tc.expectedValue { + t.Fatalf("expected test case %d Value to be %t, got %t", i, tc.expectedValue, value) + } + if null != tc.expectNull { + t.Fatalf("expected test case %d Value null flag to be %t, got %t", i, tc.expectNull, null) + } + if tc.expectedErr == nil && err != nil { + t.Fatalf("expected test case %d to succeed, got error %s", i, err) + } + if tc.expectedErr != nil { + if !errors.Is(err, tc.expectedErr) { + t.Fatalf("expected test case %d to have error matching \"%s\", got %s", i, tc.expectedErr, err) + } + } + } +} + +func TestValidationBool(t *testing.T) { + runTestCases(t, []testCase{ + { + val: "true", + f: ValidateTypeStringNullableBool, + }, + { + val: "A", + f: ValidateTypeStringNullableBool, + expectedErr: regexp.MustCompile(`[\w]+: cannot parse 'A' as boolean: .*`), + }, + { + val: 1, + f: ValidateTypeStringNullableBool, + expectedErr: regexp.MustCompile(`expected type of [\w]+ to be string`), + }, + }) +} diff --git a/internal/experimental/nullable/int.go b/internal/experimental/nullable/int.go new file mode 100644 index 0000000..25feb81 --- /dev/null +++ b/internal/experimental/nullable/int.go @@ -0,0 +1,106 @@ +package nullable + +import ( + "fmt" + "strconv" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +const ( + TypeNullableInt = schema.TypeString +) + +type Int string + +func (i Int) IsNull() bool { + return i == "" +} + +func (i Int) Value() (int64, bool, error) { + if i.IsNull() { + return 0, true, nil + } + + value, err := strconv.ParseInt(string(i), 10, 64) + if err != nil { + return 0, false, err + } + return value, false, nil +} + +// ValidateTypeStringNullableInt provides custom error messaging for TypeString ints +// Some arguments require an int value or unspecified, empty field. +func ValidateTypeStringNullableInt(v interface{}, k string) (ws []string, es []error) { + value, ok := v.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if value == "" { + return + } + + if _, err := strconv.ParseInt(value, 10, 64); err != nil { + es = append(es, fmt.Errorf("%s: cannot parse '%s' as int: %w", k, value, err)) + } + + return +} + +// ValidateTypeStringNullableIntAtLeast provides custom error messaging for TypeString ints +// Some arguments require an int value or unspecified, empty field. +func ValidateTypeStringNullableIntAtLeast(min int) schema.SchemaValidateFunc { + return func(i interface{}, k string) (ws []string, es []error) { + value, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if value == "" { + return + } + + v, err := strconv.ParseInt(value, 10, 64) + if err != nil { + es = append(es, fmt.Errorf("%s: cannot parse '%s' as int: %w", k, value, err)) + return + } + + if v < int64(min) { + es = append(es, fmt.Errorf("expected %s to be at least (%d), got %d", k, min, v)) + } + + return + } +} + +// ValidateTypeStringNullableIntBetween provides custom error messaging for TypeString ints +// Some arguments require an int value or unspecified, empty field. +func ValidateTypeStringNullableIntBetween(min int, max int) schema.SchemaValidateFunc { + return func(i interface{}, k string) (ws []string, es []error) { + value, ok := i.(string) + if !ok { + es = append(es, fmt.Errorf("expected type of %s to be string", k)) + return + } + + if value == "" { + return + } + + v, err := strconv.ParseInt(value, 10, 64) + if err != nil { + es = append(es, fmt.Errorf("%s: cannot parse '%s' as int: %w", k, value, err)) + return + } + + if v < int64(min) || v > int64(max) { + es = append(es, fmt.Errorf("expected %s to be at between (%d) and (%d), got %d", k, min, max, v)) + } + + return + } +} diff --git a/internal/experimental/nullable/int_test.go b/internal/experimental/nullable/int_test.go new file mode 100644 index 0000000..7db8ee5 --- /dev/null +++ b/internal/experimental/nullable/int_test.go @@ -0,0 +1,100 @@ +package nullable + +import ( + "errors" + "regexp" + "strconv" + "testing" +) + +func TestNullableInt(t *testing.T) { + cases := []struct { + val string + expectNull bool + expectedValue int64 + expectedErr error + }{ + { + val: "1", + expectNull: false, + expectedValue: 1, + }, + { + val: "", + expectNull: true, + expectedValue: 0, + }, + { + val: "A", + expectNull: false, + expectedValue: 0, + expectedErr: strconv.ErrSyntax, + }, + } + + for i, tc := range cases { + v := Int(tc.val) + + if null := v.IsNull(); null != tc.expectNull { + t.Fatalf("expected test case %d IsNull to return %t, got %t", i, null, tc.expectNull) + } + + value, null, err := v.Value() + if value != tc.expectedValue { + t.Fatalf("expected test case %d Value to be %d, got %d", i, tc.expectedValue, value) + } + if null != tc.expectNull { + t.Fatalf("expected test case %d Value null flag to be %t, got %t", i, tc.expectNull, null) + } + if tc.expectedErr == nil && err != nil { + t.Fatalf("expected test case %d to succeed, got error %s", i, err) + } + if tc.expectedErr != nil { + if !errors.Is(err, tc.expectedErr) { + t.Fatalf("expected test case %d to have error matching \"%s\", got %s", i, tc.expectedErr, err) + } + } + } +} + +func TestValidationInt(t *testing.T) { + runTestCases(t, []testCase{ + { + val: "1", + f: ValidateTypeStringNullableInt, + }, + { + val: "A", + f: ValidateTypeStringNullableInt, + expectedErr: regexp.MustCompile(`[\w]+: cannot parse 'A' as int: .*`), + }, + { + val: 1, + f: ValidateTypeStringNullableInt, + expectedErr: regexp.MustCompile(`expected type of [\w]+ to be string`), + }, + }) +} + +func TestValidationIntAtLeast(t *testing.T) { + runTestCases(t, []testCase{ + { + val: "1", + f: ValidateTypeStringNullableIntAtLeast(1), + }, + { + val: "1", + f: ValidateTypeStringNullableIntAtLeast(0), + }, + { + val: "1", + f: ValidateTypeStringNullableIntAtLeast(2), + expectedErr: regexp.MustCompile(`expected [\w]+ to be at least \(2\), got 1`), + }, + { + val: 1, + f: ValidateTypeStringNullableIntAtLeast(2), + expectedErr: regexp.MustCompile(`expected type of [\w]+ to be string`), + }, + }) +} diff --git a/internal/experimental/nullable/testing.go b/internal/experimental/nullable/testing.go new file mode 100644 index 0000000..c94cba9 --- /dev/null +++ b/internal/experimental/nullable/testing.go @@ -0,0 +1,45 @@ +package nullable + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type testCase struct { + val interface{} + f schema.SchemaValidateFunc + expectedErr *regexp.Regexp +} + +func runTestCases(t *testing.T, cases []testCase) { + t.Helper() + + matchErr := func(errs []error, r *regexp.Regexp) bool { + // err must match one provided + for _, err := range errs { + if r.MatchString(err.Error()) { + return true + } + } + + return false + } + + for i, tc := range cases { + _, errs := tc.f(tc.val, "test_property") + + if len(errs) == 0 && tc.expectedErr == nil { + continue + } + + if len(errs) != 0 && tc.expectedErr == nil { + t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) + } + + if !matchErr(errs, tc.expectedErr) { + t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) + } + } +} diff --git a/internal/provider/factory.go b/internal/provider/factory.go new file mode 100644 index 0000000..576211d --- /dev/null +++ b/internal/provider/factory.go @@ -0,0 +1,33 @@ +package provider + +import ( + "context" + + "github.com/cloudposse/terraform-provider-awsutils/internal/provider/fwprovider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" +) + +// ProtoV5ProviderServerFactory returns a muxed terraform-plugin-go protocol v5 provider factory function. +// This factory function is suitable for use with the terraform-plugin-go Serve function. +func ProtoV5ProviderServerFactory(ctx context.Context) (func() tfprotov5.ProviderServer, error) { + primary, err := New(ctx) + + if err != nil { + return nil, err + } + + servers := []func() tfprotov5.ProviderServer{ + primary.GRPCProvider, + providerserver.NewProtocol5(fwprovider.New(primary)), + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, servers...) + + if err != nil { + return nil, err + } + + return muxServer.ProviderServer, nil +} diff --git a/internal/provider/fwprovider/duration.go b/internal/provider/fwprovider/duration.go new file mode 100644 index 0000000..bdbfb9e --- /dev/null +++ b/internal/provider/fwprovider/duration.go @@ -0,0 +1,190 @@ +// TODO: Move this to a shared 'types' package. +package fwprovider + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type durationType uint8 + +const ( + DurationType durationType = iota +) + +var ( + _ xattr.TypeWithValidate = DurationType +) + +func (d durationType) TerraformType(_ context.Context) tftypes.Type { + return tftypes.String +} + +func (d durationType) ValueFromTerraform(_ context.Context, in tftypes.Value) (attr.Value, error) { + if !in.IsKnown() { + return Duration{Unknown: true}, nil + } + if in.IsNull() { + return Duration{Null: true}, nil + } + var s string + err := in.As(&s) + if err != nil { + return nil, err + } + dur, err := time.ParseDuration(s) + if err != nil { + return nil, err + } + return Duration{Value: dur}, nil +} + +// Equal returns true if `o` is also a DurationType. +func (d durationType) Equal(o attr.Type) bool { + _, ok := o.(durationType) + return ok +} + +// ApplyTerraform5AttributePathStep applies the given AttributePathStep to the +// type. +func (d durationType) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return nil, fmt.Errorf("cannot apply AttributePathStep %T to %s", step, d.String()) +} + +// String returns a human-friendly description of the DurationType. +func (d durationType) String() string { + return "types.DurationType" +} + +// Validate implements type validation. +func (d durationType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + if !in.Type().Is(tftypes.String) { + diags.AddAttributeError( + path, + "Duration Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Expected String value, received %T with value: %v", in, in), + ) + return diags + } + + if !in.IsKnown() || in.IsNull() { + return diags + } + + var value string + err := in.As(&value) + if err != nil { + diags.AddAttributeError( + path, + "Duration Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Cannot convert value to time.Duration: %s", err), + ) + return diags + } + + _, err = time.ParseDuration(value) + if err != nil { + diags.AddAttributeError( + path, + "Duration Type Validation Error", + fmt.Sprintf("Value %q cannot be parsed as a Duration.", value), + ) + return diags + } + + return diags +} + +func (d durationType) Description() string { + return `A sequence of numbers with a unit suffix, "h" for hour, "m" for minute, and "s" for second.` +} + +type Duration struct { + // Unknown will be true if the value is not yet known. + Unknown bool + + // Null will be true if the value was not set, or was explicitly set to + // null. + Null bool + + // Value contains the set value, as long as Unknown and Null are both + // false. + Value time.Duration +} + +// Type returns a DurationType. +func (d Duration) Type(_ context.Context) attr.Type { + return DurationType +} + +// ToTerraformValue returns the data contained in the *String as a string. If +// Unknown is true, it returns a tftypes.UnknownValue. If Null is true, it +// returns nil. +func (d Duration) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + t := DurationType.TerraformType(ctx) + if d.Null { + return tftypes.NewValue(t, nil), nil + } + if d.Unknown { + return tftypes.NewValue(t, tftypes.UnknownValue), nil + } + if err := tftypes.ValidateValue(tftypes.Number, d.Value); err != nil { + return tftypes.NewValue(t, tftypes.UnknownValue), err + } + return tftypes.NewValue(t, d.Value), nil +} + +// Equal returns true if `other` is a *Duration and has the same value as `d`. +func (d Duration) Equal(other attr.Value) bool { + o, ok := other.(Duration) + if !ok { + return false + } + if d.Unknown != o.Unknown { + return false + } + if d.Null != o.Null { + return false + } + return d.Value == o.Value +} + +// IsNull returns true if the Value is not set, or is explicitly set to null. +func (d Duration) IsNull() bool { + return d.Null +} + +// IsUnknown returns true if the Value is not yet known. +func (d Duration) IsUnknown() bool { + return d.Unknown +} + +// String returns a summary representation of either the underlying Value, +// or UnknownValueString (``) when IsUnknown() returns true, +// or NullValueString (``) when IsNull() return true. +// +// This is an intentionally lossy representation, that are best suited for +// logging and error reporting, as they are not protected by +// compatibility guarantees within the framework. +func (d Duration) String() string { + if d.IsUnknown() { + return attr.UnknownValueString + } + + if d.IsNull() { + return attr.NullValueString + } + + return d.Value.String() +} \ No newline at end of file diff --git a/internal/provider/fwprovider/duration_test.go b/internal/provider/fwprovider/duration_test.go new file mode 100644 index 0000000..fcec614 --- /dev/null +++ b/internal/provider/fwprovider/duration_test.go @@ -0,0 +1,105 @@ +// TODO: Move this to a shared 'types' package. +package fwprovider_test + +import ( + "context" + "testing" + "time" + + "github.com/cloudposse/terraform-provider-awsutils/internal/provider/fwprovider" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestDurationTypeValueFromTerraform(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + val tftypes.Value + expected attr.Value + expectError bool + }{ + "null value": { + val: tftypes.NewValue(tftypes.String, nil), + expected: fwprovider.Duration{Null: true}, + }, + "unknown value": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + expected: fwprovider.Duration{Unknown: true}, + }, + "valid duration": { + val: tftypes.NewValue(tftypes.String, "2h"), + expected: fwprovider.Duration{Value: 2 * time.Hour}, + }, + "invalid duration": { + val: tftypes.NewValue(tftypes.String, "not ok"), + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := fwprovider.DurationType.ValueFromTerraform(ctx, test.val) + + if err == nil && test.expectError { + t.Fatal("expected error, got no error") + } + if err != nil && !test.expectError { + t.Fatalf("got unexpected error: %s", err) + } + + if diff := cmp.Diff(val, test.expected); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } + }) + } +} + +func TestDurationTypeValidate(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + expectError bool + } + tests := map[string]testCase{ + "not a string": { + val: tftypes.NewValue(tftypes.Bool, true), + expectError: true, + }, + "unknown string": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }, + "null string": { + val: tftypes.NewValue(tftypes.String, nil), + }, + "valid string": { + val: tftypes.NewValue(tftypes.String, "2h"), + }, + "invalid string": { + val: tftypes.NewValue(tftypes.String, "not ok"), + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + + diags := fwprovider.DurationType.Validate(ctx, test.val, path.Root("test")) + + if !diags.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if diags.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %#v", diags) + } + }) + } +} diff --git a/internal/provider/fwprovider/provider.go b/internal/provider/fwprovider/provider.go new file mode 100644 index 0000000..fa772e3 --- /dev/null +++ b/internal/provider/fwprovider/provider.go @@ -0,0 +1,341 @@ +package fwprovider + +import ( + "context" + + "github.com/cloudposse/terraform-provider-awsutils/internal/service/meta" + "github.com/cloudposse/terraform-provider-awsutils/names" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// New returns a new, initialized Terraform Plugin Framework-style provider instance. +// The provider instance is fully configured once the `Configure` method has been called. +func New(primary interface{ Meta() interface{} }) tfsdk.Provider { + return &provider{ + Primary: primary, + } +} + +type provider struct { + Primary interface{ Meta() interface{} } +} + +// GetSchema returns the schema for this provider's configuration. +func (p *provider) GetSchema(ctx context.Context) (tfsdk.Schema, diag.Diagnostics) { + var diags diag.Diagnostics + + // This schema must match exactly the Terraform Protocol v5 (Terraform Plugin SDK v2) provider's schema. + schema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "access_key": { + Type: types.StringType, + Optional: true, + Description: "The access key for API operations. You can retrieve this\nfrom the 'Security & Credentials' section of the AWS console.", + }, + "allowed_account_ids": { + Type: types.SetType{ElemType: types.StringType}, + Optional: true, + }, + "custom_ca_bundle": { + Type: types.StringType, + Optional: true, + Description: "File containing custom root and intermediate certificates. Can also be configured using the `AWS_CA_BUNDLE` environment variable. (Setting `ca_bundle` in the shared config file is not supported.)", + }, + "ec2_metadata_service_endpoint": { + Type: types.StringType, + Optional: true, + Description: "Address of the EC2 metadata service endpoint to use. Can also be configured using the `AWS_EC2_METADATA_SERVICE_ENDPOINT` environment variable.", + }, + "ec2_metadata_service_endpoint_mode": { + Type: types.StringType, + Optional: true, + Description: "Protocol to use with EC2 metadata service endpoint.Valid values are `IPv4` and `IPv6`. Can also be configured using the `AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE` environment variable.", + }, + "forbidden_account_ids": { + Type: types.SetType{ElemType: types.StringType}, + Optional: true, + }, + "http_proxy": { + Type: types.StringType, + Optional: true, + Description: "The address of an HTTP proxy to use when accessing the AWS API. Can also be configured using the `HTTP_PROXY` or `HTTPS_PROXY` environment variables.", + }, + "insecure": { + Type: types.BoolType, + Optional: true, + Description: "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted, default value is `false`", + }, + "max_retries": { + Type: types.Int64Type, + Optional: true, + Description: "The maximum number of times an AWS API request is\nbeing executed. If the API request still fails, an error is\nthrown.", + }, + "profile": { + Type: types.StringType, + Optional: true, + Description: "The profile for API operations. If not set, the default profile\ncreated with `aws configure` will be used.", + }, + "region": { + Type: types.StringType, + Optional: true, + Description: "The region where AWS operations will take place. Examples\nare us-east-1, us-west-2, etc.", // lintignore:AWSAT003 + }, + "s3_force_path_style": { + Type: types.BoolType, + Optional: true, + Description: "Set this to true to enable the request to use path-style addressing,\ni.e., https://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\nuse virtual hosted bucket addressing when possible\n(https://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", + DeprecationMessage: "Use s3_use_path_style instead.", + }, + "s3_use_path_style": { + Type: types.BoolType, + Optional: true, + Description: "Set this to true to enable the request to use path-style addressing,\ni.e., https://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\nuse virtual hosted bucket addressing when possible\n(https://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", + }, + "secret_key": { + Type: types.StringType, + Optional: true, + Description: "The secret key for API operations. You can retrieve this\nfrom the 'Security & Credentials' section of the AWS console.", + }, + "shared_config_files": { + Type: types.ListType{ElemType: types.StringType}, + Optional: true, + Description: "List of paths to shared config files. If not set, defaults to [~/.aws/config].", + }, + "shared_credentials_file": { + Type: types.StringType, + Optional: true, + Description: "The path to the shared credentials file. If not set, defaults to ~/.aws/credentials.", + DeprecationMessage: "Use shared_credentials_files instead.", + }, + "shared_credentials_files": { + Type: types.ListType{ElemType: types.StringType}, + Optional: true, + Description: "List of paths to shared credentials files. If not set, defaults to [~/.aws/credentials].", + }, + "skip_credentials_validation": { + Type: types.BoolType, + Optional: true, + Description: "Skip the credentials validation via STS API. Used for AWS API implementations that do not have STS available/implemented.", + }, + "skip_get_ec2_platforms": { + Type: types.BoolType, + Optional: true, + Description: "Skip getting the supported EC2 platforms. Used by users that don't have ec2:DescribeAccountAttributes permissions.", + }, + "skip_metadata_api_check": { + Type: types.StringType, + Optional: true, + Description: "Skip the AWS Metadata API check. Used for AWS API implementations that do not have a metadata api endpoint.", + }, + "skip_region_validation": { + Type: types.BoolType, + Optional: true, + Description: "Skip static validation of region name. Used by users of alternative AWS-like APIs or users w/ access to regions that are not public (yet).", + }, + "skip_requesting_account_id": { + Type: types.BoolType, + Optional: true, + Description: "Skip requesting the account ID. Used for AWS API implementations that do not have IAM/STS API and/or metadata API.", + }, + "sts_region": { + Type: types.StringType, + Optional: true, + Description: "The region where AWS STS operations will take place. Examples\nare us-east-1 and us-west-2.", // lintignore:AWSAT003 + }, + "token": { + Type: types.StringType, + Optional: true, + Description: "session token. A session token is only required if you are\nusing temporary security credentials.", + }, + "use_dualstack_endpoint": { + Type: types.BoolType, + Optional: true, + Description: "Resolve an endpoint with DualStack capability", + }, + "use_fips_endpoint": { + Type: types.BoolType, + Optional: true, + Description: "Resolve an endpoint with FIPS capability", + }, + }, + Blocks: map[string]tfsdk.Block{ + "assume_role": { + Attributes: map[string]tfsdk.Attribute{ + "duration": { + Type: DurationType, + Optional: true, + Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", + }, + "duration_seconds": { + Type: types.Int64Type, + Optional: true, + Description: "The duration, in seconds, of the role session.", + DeprecationMessage: "Use assume_role.duration instead", + }, + "external_id": { + Type: types.StringType, + Optional: true, + Description: "A unique identifier that might be required when you assume a role in another account.", + }, + "policy": { + Type: types.StringType, + Optional: true, + Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", + }, + "policy_arns": { + Type: types.SetType{ElemType: types.StringType}, + Optional: true, + Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", + }, + "role_arn": { + Type: types.StringType, + Optional: true, + Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", + }, + "session_name": { + Type: types.StringType, + Optional: true, + Description: "An identifier for the assumed role session.", + }, + "tags": { + Type: types.MapType{ElemType: types.StringType}, + Optional: true, + Description: "Assume role session tags.", + }, + "transitive_tag_keys": { + Type: types.SetType{ElemType: types.StringType}, + Optional: true, + Description: "Assume role session tag keys to pass to any subsequent sessions.", + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + MaxItems: 1, + }, + "assume_role_with_web_identity": { + Attributes: map[string]tfsdk.Attribute{ + "duration": { + Type: DurationType, + Optional: true, + Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", + }, + "policy": { + Type: types.StringType, + Optional: true, + Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", + }, + "policy_arns": { + Type: types.SetType{ElemType: types.StringType}, + Optional: true, + Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", + }, + "role_arn": { + Type: types.StringType, + Optional: true, + Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", + }, + "session_name": { + Type: types.StringType, + Optional: true, + Description: "An identifier for the assumed role session.", + }, + "web_identity_token": { + Type: types.StringType, + Optional: true, + }, + "web_identity_token_file": { + Type: types.StringType, + Optional: true, + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + MaxItems: 1, + }, + "default_tags": { + Attributes: map[string]tfsdk.Attribute{ + "tags": { + Type: types.MapType{ElemType: types.StringType}, + Optional: true, + Description: "Resource tags to default across all resources", + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + MaxItems: 1, + Description: "Configuration block with settings to default resource tags across all resources.", + }, + "endpoints": endpointsBlock(), + "ignore_tags": { + Attributes: map[string]tfsdk.Attribute{ + "key_prefixes": { + Type: types.SetType{ElemType: types.StringType}, + Optional: true, + Description: "Resource tag key prefixes to ignore across all resources.", + }, + "keys": { + Type: types.SetType{ElemType: types.StringType}, + Optional: true, + Description: "Resource tag keys to ignore across all resources.", + }, + }, + NestingMode: tfsdk.BlockNestingModeList, + MaxItems: 1, + Description: "Configuration block with settings to ignore resource tags across all resources.", + }, + }, + } + + return schema, diags +} + +// Configure is called at the beginning of the provider lifecycle, when +// Terraform sends to the provider the values the user specified in the +// provider configuration block. +func (p *provider) Configure(ctx context.Context, request tfsdk.ConfigureProviderRequest, response *tfsdk.ConfigureProviderResponse) { + // Provider's parsed configuration (its instance state) is available through the primary provider's Meta() method. +} + +// GetResources returns a mapping of resource names to type +// implementations. +func (p *provider) GetResources(ctx context.Context) (map[string]tfsdk.ResourceType, diag.Diagnostics) { + var diags diag.Diagnostics + resources := make(map[string]tfsdk.ResourceType) + + return resources, diags +} + +// GetDataSources returns a mapping of data source name to types +// implementations. +func (p *provider) GetDataSources(ctx context.Context) (map[string]tfsdk.DataSourceType, diag.Diagnostics) { + var diags diag.Diagnostics + dataSources := make(map[string]tfsdk.DataSourceType) + + // TODO: This should be done via service-level self-registration and initializatin in the primary provider. + t, err := meta.NewDataSourceARNType(ctx) + + if err != nil { + diags.AddError("UhOh", err.Error()) + return nil, diags + } + + dataSources["aws_arn"] = t + + return dataSources, diags +} + +func endpointsBlock() tfsdk.Block { + endpointsAttributes := make(map[string]tfsdk.Attribute) + + for _, serviceKey := range names.Aliases() { + endpointsAttributes[serviceKey] = tfsdk.Attribute{ + Type: types.StringType, + Optional: true, + Description: "Use this to override the default service endpoint URL", + } + } + + return tfsdk.Block{ + Attributes: endpointsAttributes, + NestingMode: tfsdk.BlockNestingModeSet, + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index aa2079e..424d102 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -1,84 +1,43 @@ package provider import ( + "context" "fmt" "log" + "os" + "regexp" + "time" + "github.com/aws/aws-sdk-go-v2/feature/ec2/imds" "github.com/cloudposse/terraform-provider-awsutils/internal/conns" - tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" - "github.com/cloudposse/terraform-provider-awsutils/internal/verify" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" - + "github.com/cloudposse/terraform-provider-awsutils/internal/experimental/nullable" "github.com/cloudposse/terraform-provider-awsutils/internal/service/ec2" "github.com/cloudposse/terraform-provider-awsutils/internal/service/guardduty" "github.com/cloudposse/terraform-provider-awsutils/internal/service/iam" "github.com/cloudposse/terraform-provider-awsutils/internal/service/securityhub" + tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/cloudposse/terraform-provider-awsutils/names" + awsbase "github.com/hashicorp/aws-sdk-go-base/v2" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) -// Provider returns a *schema.Provider. -func Provider() *schema.Provider { - // TODO: Move the validation to this, requires conditional schemas - // TODO: Move the configuration to this, requires validation - +// New returns a new, initialized Terraform Plugin SDK v2-style provider instance. +// The provider instance is fully configured once the `ConfigureContextFunc` has been called. +func New(_ context.Context) (*schema.Provider, error) { // The actual provider provider := &schema.Provider{ + // This schema must match exactly the Terraform Protocol v6 (Terraform Plugin Framework) provider's schema. + // Notably the attributes can have no Default values. Schema: map[string]*schema.Schema{ "access_key": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: descriptions["access_key"], - }, - - "secret_key": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: descriptions["secret_key"], - }, - - "profile": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: descriptions["profile"], - }, - - "assume_role": assumeRoleSchema(), - - "shared_credentials_file": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: descriptions["shared_credentials_file"], - }, - - "token": { - Type: schema.TypeString, - Optional: true, - Default: "", - Description: descriptions["token"], - }, - - "region": { Type: schema.TypeString, - Required: true, - DefaultFunc: schema.MultiEnvDefaultFunc([]string{ - "AWS_REGION", - "AWS_DEFAULT_REGION", - }, nil), - Description: descriptions["region"], - InputDefault: "us-east-1", // lintignore:AWSAT003 + Optional: true, + Description: "The access key for API operations. You can retrieve this\n" + + "from the 'Security & Credentials' section of the AWS console.", }, - - "max_retries": { - Type: schema.TypeInt, - Optional: true, - Default: 25, - Description: descriptions["max_retries"], - }, - "allowed_account_ids": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, @@ -86,15 +45,15 @@ func Provider() *schema.Provider { ConflictsWith: []string{"forbidden_account_ids"}, Set: schema.HashString, }, - - "forbidden_account_ids": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - ConflictsWith: []string{"allowed_account_ids"}, - Set: schema.HashString, + "assume_role": assumeRoleSchema(), + "assume_role_with_web_identity": assumeRoleWithWebIdentitySchema(), + "custom_ca_bundle": { + Type: schema.TypeString, + Optional: true, + Description: "File containing custom root and intermediate certificates. " + + "Can also be configured using the `AWS_CA_BUNDLE` environment variable. " + + "(Setting `ca_bundle` in the shared config file is not supported.)", }, - "default_tags": { Type: schema.TypeList, Optional: true, @@ -111,15 +70,32 @@ func Provider() *schema.Provider { }, }, }, - - "http_proxy": { - Type: schema.TypeString, - Optional: true, - Description: descriptions["http_proxy"], + "ec2_metadata_service_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Address of the EC2 metadata service endpoint to use. " + + "Can also be configured using the `AWS_EC2_METADATA_SERVICE_ENDPOINT` environment variable.", + }, + "ec2_metadata_service_endpoint_mode": { + Type: schema.TypeString, + Optional: true, + Description: "Protocol to use with EC2 metadata service endpoint." + + "Valid values are `IPv4` and `IPv6`. Can also be configured using the `AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE` environment variable.", }, - "endpoints": endpointsSchema(), - + "forbidden_account_ids": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + ConflictsWith: []string{"allowed_account_ids"}, + Set: schema.HashString, + }, + "http_proxy": { + Type: schema.TypeString, + Optional: true, + Description: "The address of an HTTP proxy to use when accessing the AWS API. " + + "Can also be configured using the `HTTP_PROXY` or `HTTPS_PROXY` environment variables.", + }, "ignore_tags": { Type: schema.TypeList, Optional: true, @@ -144,54 +120,126 @@ func Provider() *schema.Provider { }, }, }, - "insecure": { - Type: schema.TypeBool, + Type: schema.TypeBool, + Optional: true, + Description: "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted, " + + "default value is `false`", + }, + "max_retries": { + Type: schema.TypeInt, + Optional: true, + Description: "The maximum number of times an AWS API request is\n" + + "being executed. If the API request still fails, an error is\n" + + "thrown.", + }, + "profile": { + Type: schema.TypeString, + Optional: true, + Description: "The profile for API operations. If not set, the default profile\n" + + "created with `aws configure` will be used.", + }, + "region": { + Type: schema.TypeString, + Optional: true, + Description: "The region where AWS operations will take place. Examples\n" + + "are us-east-1, us-west-2, etc.", // lintignore:AWSAT003, + }, + "s3_force_path_style": { + Type: schema.TypeBool, + Optional: true, + Deprecated: "Use s3_use_path_style instead.", + Description: "Set this to true to enable the request to use path-style addressing,\n" + + "i.e., https://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\n" + + "use virtual hosted bucket addressing when possible\n" + + "(https://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", + }, + "s3_use_path_style": { + Type: schema.TypeBool, + Optional: true, + Description: "Set this to true to enable the request to use path-style addressing,\n" + + "i.e., https://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\n" + + "use virtual hosted bucket addressing when possible\n" + + "(https://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", + }, + "secret_key": { + Type: schema.TypeString, + Optional: true, + Description: "The secret key for API operations. You can retrieve this\n" + + "from the 'Security & Credentials' section of the AWS console.", + }, + "shared_config_files": { + Type: schema.TypeList, Optional: true, - Default: false, - Description: descriptions["insecure"], + Description: "List of paths to shared config files. If not set, defaults to [~/.aws/config].", + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "shared_credentials_file": { + Type: schema.TypeString, + Optional: true, + Deprecated: "Use shared_credentials_files instead.", + ConflictsWith: []string{"shared_credentials_files"}, + Description: "The path to the shared credentials file. If not set, defaults to ~/.aws/credentials.", + }, + "shared_credentials_files": { + Type: schema.TypeList, + Optional: true, + ConflictsWith: []string{"shared_credentials_file"}, + Description: "List of paths to shared credentials files. If not set, defaults to [~/.aws/credentials].", + Elem: &schema.Schema{Type: schema.TypeString}, }, - "skip_credentials_validation": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: descriptions["skip_credentials_validation"], + Type: schema.TypeBool, + Optional: true, + Description: "Skip the credentials validation via STS API. " + + "Used for AWS API implementations that do not have STS available/implemented.", }, - "skip_get_ec2_platforms": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: descriptions["skip_get_ec2_platforms"], + Type: schema.TypeBool, + Optional: true, + Description: "Skip getting the supported EC2 platforms. " + + "Used by users that don't have ec2:DescribeAccountAttributes permissions.", + }, + "skip_metadata_api_check": { + Type: nullable.TypeNullableBool, + Optional: true, + ValidateFunc: nullable.ValidateTypeStringNullableBool, + Description: "Skip the AWS Metadata API check. " + + "Used for AWS API implementations that do not have a metadata api endpoint.", }, - "skip_region_validation": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: descriptions["skip_region_validation"], + Type: schema.TypeBool, + Optional: true, + Description: "Skip static validation of region name. " + + "Used by users of alternative AWS-like APIs or users w/ access to regions that are not public (yet).", }, - "skip_requesting_account_id": { - Type: schema.TypeBool, - Optional: true, - Default: false, - Description: descriptions["skip_requesting_account_id"], + Type: schema.TypeBool, + Optional: true, + Description: "Skip requesting the account ID. " + + "Used for AWS API implementations that do not have IAM/STS API and/or metadata API.", }, - - "skip_metadata_api_check": { + "sts_region": { + Type: schema.TypeString, + Optional: true, + Description: "The region where AWS STS operations will take place. Examples\n" + + "are us-east-1 and us-west-2.", // lintignore:AWSAT003, + }, + "token": { + Type: schema.TypeString, + Optional: true, + Description: "session token. A session token is only required if you are\n" + + "using temporary security credentials.", + }, + "use_dualstack_endpoint": { Type: schema.TypeBool, Optional: true, - Default: false, - Description: descriptions["skip_metadata_api_check"], + Description: "Resolve an endpoint with DualStack capability", }, - - "s3_force_path_style": { + "use_fips_endpoint": { Type: schema.TypeBool, Optional: true, - Default: false, - Description: descriptions["s3_force_path_style"], + Description: "Resolve an endpoint with FIPS capability", }, }, @@ -208,178 +256,82 @@ func Provider() *schema.Provider { }, } - provider.ConfigureFunc = func(d *schema.ResourceData) (interface{}, error) { + provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { terraformVersion := provider.TerraformVersion if terraformVersion == "" { // Terraform 0.12 introduced this field to the protocol // We can therefore assume that if it's missing it's 0.10 or 0.11 terraformVersion = "0.11+compatible" } - return providerConfigure(d, terraformVersion) + return providerConfigure(ctx, d, terraformVersion) } - return provider -} - -var descriptions map[string]string - -func init() { - descriptions = map[string]string{ - "region": "The region where AWS operations will take place. Examples\n" + - "are us-east-1, us-west-2, etc.", // lintignore:AWSAT003 - - "access_key": "The access key for API operations. You can retrieve this\n" + - "from the 'Security & Credentials' section of the AWS console.", - - "secret_key": "The secret key for API operations. You can retrieve this\n" + - "from the 'Security & Credentials' section of the AWS console.", - - "profile": "The profile for API operations. If not set, the default profile\n" + - "created with `aws configure` will be used.", - - "shared_credentials_file": "The path to the shared credentials file. If not set\n" + - "this defaults to ~/.aws/credentials.", - - "token": "session token. A session token is only required if you are\n" + - "using temporary security credentials.", - - "max_retries": "The maximum number of times an AWS API request is\n" + - "being executed. If the API request still fails, an error is\n" + - "thrown.", - - "http_proxy": "The address of an HTTP proxy to use when accessing the AWS API. " + - "Can also be configured using the `HTTP_PROXY` or `HTTPS_PROXY` environment variables.", - - "endpoint": "Use this to override the default service endpoint URL", - - "insecure": "Explicitly allow the provider to perform \"insecure\" SSL requests. If omitted, " + - "default value is `false`", - - "skip_credentials_validation": "Skip the credentials validation via STS API. " + - "Used for AWS API implementations that do not have STS available/implemented.", - - "skip_get_ec2_platforms": "Skip getting the supported EC2 platforms. " + - "Used by users that don't have ec2:DescribeAccountAttributes permissions.", - - "skip_region_validation": "Skip static validation of region name. " + - "Used by users of alternative AWS-like APIs or users w/ access to regions that are not public (yet).", - - "skip_requesting_account_id": "Skip requesting the account ID. " + - "Used for AWS API implementations that do not have IAM/STS API and/or metadata API.", - - "skip_medatadata_api_check": "Skip the AWS Metadata API check. " + - "Used for AWS API implementations that do not have a metadata api endpoint.", - - "s3_force_path_style": "Set this to true to force the request to use path-style addressing,\n" + - "i.e., http://s3.amazonaws.com/BUCKET/KEY. By default, the S3 client will\n" + - "use virtual hosted bucket addressing when possible\n" + - "(http://BUCKET.s3.amazonaws.com/KEY). Specific to the Amazon S3 service.", - } + return provider, nil } -func providerConfigure(d *schema.ResourceData, terraformVersion string) (interface{}, error) { +func providerConfigure(ctx context.Context, d *schema.ResourceData, terraformVersion string) (interface{}, diag.Diagnostics) { config := conns.Config{ - AccessKey: d.Get("access_key").(string), - SecretKey: d.Get("secret_key").(string), - Profile: d.Get("profile").(string), - Token: d.Get("token").(string), - Region: d.Get("region").(string), - CredsFilename: d.Get("shared_credentials_file").(string), - DefaultTagsConfig: expandProviderDefaultTags(d.Get("default_tags").([]interface{})), - Endpoints: make(map[string]string), - MaxRetries: d.Get("max_retries").(int), - IgnoreTagsConfig: expandProviderIgnoreTags(d.Get("ignore_tags").([]interface{})), - Insecure: d.Get("insecure").(bool), - HTTPProxy: d.Get("http_proxy").(string), - SkipCredsValidation: d.Get("skip_credentials_validation").(bool), - SkipGetEC2Platforms: d.Get("skip_get_ec2_platforms").(bool), - SkipRegionValidation: d.Get("skip_region_validation").(bool), - SkipRequestingAccountId: d.Get("skip_requesting_account_id").(bool), - SkipMetadataApiCheck: d.Get("skip_metadata_api_check").(bool), - S3ForcePathStyle: d.Get("s3_force_path_style").(bool), - TerraformVersion: terraformVersion, + AccessKey: d.Get("access_key").(string), + DefaultTagsConfig: expandProviderDefaultTags(d.Get("default_tags").([]interface{})), + CustomCABundle: d.Get("custom_ca_bundle").(string), + EC2MetadataServiceEndpoint: d.Get("ec2_metadata_service_endpoint").(string), + EC2MetadataServiceEndpointMode: d.Get("ec2_metadata_service_endpoint_mode").(string), + Endpoints: make(map[string]string), + HTTPProxy: d.Get("http_proxy").(string), + IgnoreTagsConfig: expandProviderIgnoreTags(d.Get("ignore_tags").([]interface{})), + Insecure: d.Get("insecure").(bool), + MaxRetries: 25, // Set default here, not in schema (muxing with v6 provider). + Profile: d.Get("profile").(string), + Region: d.Get("region").(string), + S3UsePathStyle: d.Get("s3_use_path_style").(bool) || d.Get("s3_force_path_style").(bool), + SecretKey: d.Get("secret_key").(string), + SkipCredsValidation: d.Get("skip_credentials_validation").(bool), + SkipGetEC2Platforms: d.Get("skip_get_ec2_platforms").(bool), + SkipRegionValidation: d.Get("skip_region_validation").(bool), + SkipRequestingAccountId: d.Get("skip_requesting_account_id").(bool), + STSRegion: d.Get("sts_region").(string), + TerraformVersion: terraformVersion, + Token: d.Get("token").(string), + UseDualStackEndpoint: d.Get("use_dualstack_endpoint").(bool), + UseFIPSEndpoint: d.Get("use_fips_endpoint").(bool), } - if l, ok := d.Get("assume_role").([]interface{}); ok && len(l) > 0 && l[0] != nil { - m := l[0].(map[string]interface{}) - - if v, ok := m["duration_seconds"].(int); ok && v != 0 { - config.AssumeRoleDurationSeconds = v - } - - if v, ok := m["external_id"].(string); ok && v != "" { - config.AssumeRoleExternalID = v - } - - if v, ok := m["policy"].(string); ok && v != "" { - config.AssumeRolePolicy = v - } - - if policyARNSet, ok := m["policy_arns"].(*schema.Set); ok && policyARNSet.Len() > 0 { - for _, policyARNRaw := range policyARNSet.List() { - policyARN, ok := policyARNRaw.(string) - - if !ok { - continue - } - - config.AssumeRolePolicyARNs = append(config.AssumeRolePolicyARNs, policyARN) - } - } - - if v, ok := m["role_arn"].(string); ok && v != "" { - config.AssumeRoleARN = v - } - - if v, ok := m["session_name"].(string); ok && v != "" { - config.AssumeRoleSessionName = v - } - - if tagMapRaw, ok := m["tags"].(map[string]interface{}); ok && len(tagMapRaw) > 0 { - config.AssumeRoleTags = make(map[string]string) - - for k, vRaw := range tagMapRaw { - v, ok := vRaw.(string) - - if !ok { - continue - } + if v, ok := d.GetOk("max_retries"); ok { + config.MaxRetries = v.(int) + } - config.AssumeRoleTags[k] = v - } + if raw := d.Get("shared_config_files").([]interface{}); len(raw) != 0 { + l := make([]string, len(raw)) + for i, v := range raw { + l[i] = v.(string) } + config.SharedConfigFiles = l + } - if transitiveTagKeySet, ok := m["transitive_tag_keys"].(*schema.Set); ok && transitiveTagKeySet.Len() > 0 { - for _, transitiveTagKeyRaw := range transitiveTagKeySet.List() { - transitiveTagKey, ok := transitiveTagKeyRaw.(string) - - if !ok { - continue - } + if v := d.Get("shared_credentials_file").(string); v != "" { + config.SharedCredentialsFiles = []string{v} + } - config.AssumeRoleTransitiveTagKeys = append(config.AssumeRoleTransitiveTagKeys, transitiveTagKey) - } + if raw := d.Get("shared_credentials_files").([]interface{}); len(raw) != 0 { + l := make([]string, len(raw)) + for i, v := range raw { + l[i] = v.(string) } - - log.Printf("[INFO] assume_role configuration set: (ARN: %q, SessionID: %q, ExternalID: %q)", config.AssumeRoleARN, config.AssumeRoleSessionName, config.AssumeRoleExternalID) + config.SharedCredentialsFiles = l } - endpointsSet := d.Get("endpoints").(*schema.Set) - - for _, endpointsSetI := range endpointsSet.List() { - endpoints := endpointsSetI.(map[string]interface{}) + if l, ok := d.Get("assume_role").([]interface{}); ok && len(l) > 0 && l[0] != nil { + config.AssumeRole = expandAssumeRole(l[0].(map[string]interface{})) + log.Printf("[INFO] assume_role configuration set: (ARN: %q, SessionID: %q, ExternalID: %q)", config.AssumeRole.RoleARN, config.AssumeRole.SessionName, config.AssumeRole.ExternalID) + } - for _, hclKey := range conns.HCLKeys() { - var serviceKey string - var err error - if serviceKey, err = conns.ServiceForHCLKey(hclKey); err != nil { - return nil, fmt.Errorf("failed to assign endpoint (%s): %w", hclKey, err) - } + if l, ok := d.Get("assume_role_with_web_identity").([]interface{}); ok && len(l) > 0 && l[0] != nil { + config.AssumeRoleWithWebIdentity = expandAssumeRoleWithWebIdentity(l[0].(map[string]interface{})) + log.Printf("[INFO] assume_role_with_web_identity configuration set: (ARN: %q, SessionID: %q)", config.AssumeRoleWithWebIdentity.RoleARN, config.AssumeRoleWithWebIdentity.SessionName) + } - if config.Endpoints[serviceKey] == "" && endpoints[hclKey].(string) != "" { - config.Endpoints[serviceKey] = endpoints[hclKey].(string) - } - } + if err := expandEndpoints(d.Get("endpoints").(*schema.Set).List(), config.Endpoints); err != nil { + return nil, diag.FromErr(err) } if v, ok := d.GetOk("allowed_account_ids"); ok { @@ -394,7 +346,15 @@ func providerConfigure(d *schema.ResourceData, terraformVersion string) (interfa } } - return config.Client() + if v, null, _ := nullable.Bool(d.Get("skip_metadata_api_check").(string)).Value(); !null { + if v { + config.EC2MetadataServiceEnableState = imds.ClientDisabled + } else { + config.EC2MetadataServiceEnableState = imds.ClientEnabled + } + } + + return config.Client(ctx) } func assumeRoleSchema() *schema.Schema { @@ -404,15 +364,29 @@ func assumeRoleSchema() *schema.Schema { MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "duration": { + Type: schema.TypeString, + Optional: true, + Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", + ValidateFunc: validAssumeRoleDuration, + ConflictsWith: []string{"assume_role.0.duration_seconds"}, + }, "duration_seconds": { - Type: schema.TypeInt, - Optional: true, - Description: "Seconds to restrict the assume role session duration.", + Type: schema.TypeInt, + Optional: true, + Deprecated: "Use assume_role.duration instead", + Description: "The duration, in seconds, of the role session.", + ValidateFunc: validation.IntBetween(900, 43200), + ConflictsWith: []string{"assume_role.0.duration"}, }, "external_id": { Type: schema.TypeString, Optional: true, - Description: "Unique identifier that might be required for assuming a role in another account.", + Description: "A unique identifier that might be required when you assume a role in another account.", + ValidateFunc: validation.All( + validation.StringLenBetween(2, 1224), + validation.StringMatch(regexp.MustCompile(`[\w+=,.@:\/\-]*`), ""), + ), }, "policy": { Type: schema.TypeString, @@ -432,13 +406,14 @@ func assumeRoleSchema() *schema.Schema { "role_arn": { Type: schema.TypeString, Optional: true, - Description: "Amazon Resource Name of an IAM Role to assume prior to making API calls.", + Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", ValidateFunc: verify.ValidARN, }, "session_name": { - Type: schema.TypeString, - Optional: true, - Description: "Identifier for the assumed role session.", + Type: schema.TypeString, + Optional: true, + Description: "An identifier for the assumed role session.", + ValidateFunc: validAssumeRoleSessionName, }, "tags": { Type: schema.TypeMap, @@ -457,15 +432,71 @@ func assumeRoleSchema() *schema.Schema { } } +func assumeRoleWithWebIdentitySchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "duration": { + Type: schema.TypeString, + Optional: true, + Description: "The duration, between 15 minutes and 12 hours, of the role session. Valid time units are ns, us (or µs), ms, s, h, or m.", + ValidateFunc: validAssumeRoleDuration, + }, + "policy": { + Type: schema.TypeString, + Optional: true, + Description: "IAM Policy JSON describing further restricting permissions for the IAM Role being assumed.", + ValidateFunc: validation.StringIsJSON, + }, + "policy_arns": { + Type: schema.TypeSet, + Optional: true, + Description: "Amazon Resource Names (ARNs) of IAM Policies describing further restricting permissions for the IAM Role being assumed.", + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: verify.ValidARN, + }, + }, + "role_arn": { + Type: schema.TypeString, + Optional: true, + Description: "Amazon Resource Name (ARN) of an IAM Role to assume prior to making API calls.", + ValidateFunc: verify.ValidARN, + }, + "session_name": { + Type: schema.TypeString, + Optional: true, + Description: "An identifier for the assumed role session.", + ValidateFunc: validAssumeRoleSessionName, + }, + "web_identity_token": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(4, 20000), + ExactlyOneOf: []string{"assume_role_with_web_identity.0.web_identity_token", "assume_role_with_web_identity.0.web_identity_token_file"}, + }, + "web_identity_token_file": { + Type: schema.TypeString, + Optional: true, + ExactlyOneOf: []string{"assume_role_with_web_identity.0.web_identity_token", "assume_role_with_web_identity.0.web_identity_token_file"}, + }, + }, + }, + } +} + func endpointsSchema() *schema.Schema { endpointsAttributes := make(map[string]*schema.Schema) - for _, serviceKey := range conns.HCLKeys() { + for _, serviceKey := range names.Aliases() { endpointsAttributes[serviceKey] = &schema.Schema{ Type: schema.TypeString, Optional: true, Default: "", - Description: descriptions["endpoint"], + Description: "Use this to override the default service endpoint URL", } } @@ -478,6 +509,122 @@ func endpointsSchema() *schema.Schema { } } +func expandAssumeRole(m map[string]interface{}) *awsbase.AssumeRole { + assumeRole := awsbase.AssumeRole{} + + if v, ok := m["duration"].(string); ok && v != "" { + duration, _ := time.ParseDuration(v) + assumeRole.Duration = duration + } + + if v, ok := m["duration_seconds"].(int); ok && v != 0 { + assumeRole.Duration = time.Duration(v) * time.Second + } + + if v, ok := m["external_id"].(string); ok && v != "" { + assumeRole.ExternalID = v + } + + if v, ok := m["policy"].(string); ok && v != "" { + assumeRole.Policy = v + } + + if policyARNSet, ok := m["policy_arns"].(*schema.Set); ok && policyARNSet.Len() > 0 { + for _, policyARNRaw := range policyARNSet.List() { + policyARN, ok := policyARNRaw.(string) + + if !ok { + continue + } + + assumeRole.PolicyARNs = append(assumeRole.PolicyARNs, policyARN) + } + } + + if v, ok := m["role_arn"].(string); ok && v != "" { + assumeRole.RoleARN = v + } + + if v, ok := m["session_name"].(string); ok && v != "" { + assumeRole.SessionName = v + } + + if tagMapRaw, ok := m["tags"].(map[string]interface{}); ok && len(tagMapRaw) > 0 { + assumeRole.Tags = make(map[string]string) + + for k, vRaw := range tagMapRaw { + v, ok := vRaw.(string) + + if !ok { + continue + } + + assumeRole.Tags[k] = v + } + } + + if transitiveTagKeySet, ok := m["transitive_tag_keys"].(*schema.Set); ok && transitiveTagKeySet.Len() > 0 { + for _, transitiveTagKeyRaw := range transitiveTagKeySet.List() { + transitiveTagKey, ok := transitiveTagKeyRaw.(string) + + if !ok { + continue + } + + assumeRole.TransitiveTagKeys = append(assumeRole.TransitiveTagKeys, transitiveTagKey) + } + } + + return &assumeRole +} + +func expandAssumeRoleWithWebIdentity(m map[string]interface{}) *awsbase.AssumeRoleWithWebIdentity { + assumeRole := awsbase.AssumeRoleWithWebIdentity{} + + if v, ok := m["duration"].(string); ok && v != "" { + duration, _ := time.ParseDuration(v) + assumeRole.Duration = duration + } + + if v, ok := m["duration_seconds"].(int); ok && v != 0 { + assumeRole.Duration = time.Duration(v) * time.Second + } + + if v, ok := m["policy"].(string); ok && v != "" { + assumeRole.Policy = v + } + + if policyARNSet, ok := m["policy_arns"].(*schema.Set); ok && policyARNSet.Len() > 0 { + for _, policyARNRaw := range policyARNSet.List() { + policyARN, ok := policyARNRaw.(string) + + if !ok { + continue + } + + assumeRole.PolicyARNs = append(assumeRole.PolicyARNs, policyARN) + } + } + + if v, ok := m["role_arn"].(string); ok && v != "" { + assumeRole.RoleARN = v + } + + if v, ok := m["session_name"].(string); ok && v != "" { + assumeRole.SessionName = v + } + + if v, ok := m["web_identity_token"].(string); ok && v != "" { + assumeRole.WebIdentityToken = v + } + + if v, ok := m["web_identity_token_file"].(string); ok && v != "" { + assumeRole.WebIdentityTokenFile = v + } + + return &assumeRole +} + func expandProviderDefaultTags(l []interface{}) *tftags.DefaultConfig { if len(l) == 0 || l[0] == nil { return nil @@ -510,3 +657,43 @@ func expandProviderIgnoreTags(l []interface{}) *tftags.IgnoreConfig { return ignoreConfig } + +func expandEndpoints(endpointsSetList []interface{}, out map[string]string) error { + for _, endpointsSetI := range endpointsSetList { + endpoints := endpointsSetI.(map[string]interface{}) + + for _, hclKey := range names.Aliases() { + var serviceKey string + var err error + if serviceKey, err = names.ProviderPackageForAlias(hclKey); err != nil { + return fmt.Errorf("failed to assign endpoint (%s): %w", hclKey, err) + } + + if out[serviceKey] == "" && endpoints[hclKey].(string) != "" { + out[serviceKey] = endpoints[hclKey].(string) + } + } + } + + for _, service := range names.ProviderPackages() { + if out[service] != "" { + continue + } + + envvar := names.EnvVar(service) + if envvar != "" { + if v := os.Getenv(envvar); v != "" { + out[service] = v + continue + } + } + if envvarDeprecated := names.DeprecatedEnvVar(service); envvarDeprecated != "" { + if v := os.Getenv(envvarDeprecated); v != "" { + log.Printf("[WARN] The environment variable %q is deprecated. Use %q instead.", envvarDeprecated, envvar) + out[service] = v + } + } + } + + return nil +} diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go new file mode 100644 index 0000000..f95bcc3 --- /dev/null +++ b/internal/provider/provider_test.go @@ -0,0 +1,190 @@ +package provider + +import ( + "os" + "strings" + "testing" + + "github.com/cloudposse/terraform-provider-awsutils/names" +) + +func TestExpandEndpoints(t *testing.T) { + oldEnv := stashEnv() + defer popEnv(oldEnv) + + endpoints := make(map[string]interface{}) + for _, serviceKey := range names.Aliases() { + endpoints[serviceKey] = "" + } + endpoints["sts"] = "https://sts.fake.test" + + results := make(map[string]string) + + err := expandEndpoints([]interface{}{endpoints}, results) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if len(results) != 1 { + t.Errorf("Expected 1 endpoint, got %d", len(results)) + } + + if v := results["sts"]; v != "https://sts.fake.test" { + t.Errorf("Expected endpoint %q, got %v", "https://sts.fake.test", results) + } +} + +func TestEndpointMultipleKeys(t *testing.T) { + testcases := []struct { + endpoints map[string]string + expectedService string + expectedEndpoint string + }{ + { + endpoints: map[string]string{ + "transcribe": "https://transcribe.fake.test", + }, + expectedService: names.Transcribe, + expectedEndpoint: "https://transcribe.fake.test", + }, + { + endpoints: map[string]string{ + "transcribeservice": "https://transcribe.fake.test", + }, + expectedService: names.Transcribe, + expectedEndpoint: "https://transcribe.fake.test", + }, + { + endpoints: map[string]string{ + "transcribe": "https://transcribe.fake.test", + "transcribeservice": "https://transcribeservice.fake.test", + }, + expectedService: names.Transcribe, + expectedEndpoint: "https://transcribe.fake.test", + }, + } + + for _, testcase := range testcases { + oldEnv := stashEnv() + defer popEnv(oldEnv) + + endpoints := make(map[string]interface{}) + for _, serviceKey := range names.Aliases() { + endpoints[serviceKey] = "" + } + for k, v := range testcase.endpoints { + endpoints[k] = v + } + + results := make(map[string]string) + + err := expandEndpoints([]interface{}{endpoints}, results) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if a, e := len(results), 1; a != e { + t.Errorf("Expected 1 endpoint, got %d", len(results)) + } + + if v := results[testcase.expectedService]; v != testcase.expectedEndpoint { + t.Errorf("Expected endpoint[%s] to be %q, got %v", testcase.expectedService, testcase.expectedEndpoint, results) + } + } +} + +func TestEndpointEnvVarPrecedence(t *testing.T) { + testcases := []struct { + endpoints map[string]string + envvars map[string]string + expectedService string + expectedEndpoint string + }{ + { + endpoints: map[string]string{}, + envvars: map[string]string{ + "TF_AWS_STS_ENDPOINT": "https://sts.fake.test", + }, + expectedService: names.STS, + expectedEndpoint: "https://sts.fake.test", + }, + { + endpoints: map[string]string{}, + envvars: map[string]string{ + "AWS_STS_ENDPOINT": "https://sts-deprecated.fake.test", + }, + expectedService: names.STS, + expectedEndpoint: "https://sts-deprecated.fake.test", + }, + { + endpoints: map[string]string{}, + envvars: map[string]string{ + "TF_AWS_STS_ENDPOINT": "https://sts.fake.test", + "AWS_STS_ENDPOINT": "https://sts-deprecated.fake.test", + }, + expectedService: names.STS, + expectedEndpoint: "https://sts.fake.test", + }, + { + endpoints: map[string]string{ + "sts": "https://sts-config.fake.test", + }, + envvars: map[string]string{ + "TF_AWS_STS_ENDPOINT": "https://sts-env.fake.test", + }, + expectedService: names.STS, + expectedEndpoint: "https://sts-config.fake.test", + }, + } + + for _, testcase := range testcases { + oldEnv := stashEnv() + defer popEnv(oldEnv) + + for k, v := range testcase.envvars { + os.Setenv(k, v) + } + + endpoints := make(map[string]interface{}) + for _, serviceKey := range names.Aliases() { + endpoints[serviceKey] = "" + } + for k, v := range testcase.endpoints { + endpoints[k] = v + } + + results := make(map[string]string) + + err := expandEndpoints([]interface{}{endpoints}, results) + if err != nil { + t.Fatalf("Unexpected error: %s", err) + } + + if a, e := len(results), 1; a != e { + t.Errorf("Expected 1 endpoint, got %d", len(results)) + } + + if v := results[testcase.expectedService]; v != testcase.expectedEndpoint { + t.Errorf("Expected endpoint[%s] to be %q, got %v", testcase.expectedService, testcase.expectedEndpoint, results) + } + } +} + +func stashEnv() []string { + env := os.Environ() + os.Clearenv() + return env +} + +func popEnv(env []string) { + os.Clearenv() + + for _, e := range env { + p := strings.SplitN(e, "=", 2) + k, v := p[0], "" + if len(p) > 1 { + v = p[1] + } + os.Setenv(k, v) + } +} diff --git a/internal/provider/validate.go b/internal/provider/validate.go new file mode 100644 index 0000000..96ba349 --- /dev/null +++ b/internal/provider/validate.go @@ -0,0 +1,31 @@ +package provider + +import ( + "fmt" + "regexp" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +// validAssumeRoleDuration validates a string can be parsed as a valid time.Duration +// and is within a minimum of 15 minutes and maximum of 12 hours +func validAssumeRoleDuration(v interface{}, k string) (ws []string, errors []error) { + duration, err := time.ParseDuration(v.(string)) + + if err != nil { + errors = append(errors, fmt.Errorf("%q cannot be parsed as a duration: %w", k, err)) + return + } + + if duration.Minutes() < 15 || duration.Hours() > 12 { + errors = append(errors, fmt.Errorf("duration %q must be between 15 minutes (15m) and 12 hours (12h), inclusive", k)) + } + + return +} + +var validAssumeRoleSessionName = validation.All( + validation.StringLenBetween(2, 64), + validation.StringMatch(regexp.MustCompile(`[\w+=,.@\-]*`), ""), +) diff --git a/internal/provider/validate_test.go b/internal/provider/validate_test.go new file mode 100644 index 0000000..dcad3cd --- /dev/null +++ b/internal/provider/validate_test.go @@ -0,0 +1,68 @@ +package provider + +import ( + "regexp" + "testing" +) + +func TestValidAssumeRoleDuration(t *testing.T) { + testCases := []struct { + val interface{} + expectedErr *regexp.Regexp + }{ + { + val: "", + expectedErr: regexp.MustCompile(`cannot be parsed as a duration`), + }, + { + val: "1", + expectedErr: regexp.MustCompile(`cannot be parsed as a duration`), + }, + { + val: "10m", + expectedErr: regexp.MustCompile(`must be between 15 minutes \(15m\) and 12 hours \(12h\)`), + }, + { + val: "12h30m", + expectedErr: regexp.MustCompile(`must be between 15 minutes \(15m\) and 12 hours \(12h\)`), + }, + { + + val: "15m", + }, + { + val: "1h10m10s", + }, + { + + val: "12h", + }, + } + + matchErr := func(errs []error, r *regexp.Regexp) bool { + // err must match one provided + for _, err := range errs { + if r.MatchString(err.Error()) { + return true + } + } + + return false + } + + for i, tc := range testCases { + _, errs := validAssumeRoleDuration(tc.val, "test_property") + + if len(errs) == 0 && tc.expectedErr == nil { + continue + } + + if len(errs) != 0 && tc.expectedErr == nil { + t.Fatalf("expected test case %d to produce no errors, got %v", i, errs) + } + + if !matchErr(errs, tc.expectedErr) { + t.Fatalf("expected test case %d to produce error matching \"%s\", got %v", i, tc.expectedErr, errs) + } + } +} diff --git a/internal/service/ec2/common_schema_data_source.go b/internal/service/ec2/common_schema_data_source.go new file mode 100644 index 0000000..6c71048 --- /dev/null +++ b/internal/service/ec2/common_schema_data_source.go @@ -0,0 +1,44 @@ +package ec2 + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func BuildFiltersDataSource(set *schema.Set) []*ec2.Filter { + var filters []*ec2.Filter + for _, v := range set.List() { + m := v.(map[string]interface{}) + var filterValues []*string + for _, e := range m["values"].([]interface{}) { + filterValues = append(filterValues, aws.String(e.(string))) + } + filters = append(filters, &ec2.Filter{ + Name: aws.String(m["name"].(string)), + Values: filterValues, + }) + } + return filters +} + +func DataSourceFiltersSchema() *schema.Schema { + return &schema.Schema{ + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + }, + + "values": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + }, + } +} diff --git a/internal/service/ec2/consts.go b/internal/service/ec2/consts.go new file mode 100644 index 0000000..89e2738 --- /dev/null +++ b/internal/service/ec2/consts.go @@ -0,0 +1,263 @@ +package ec2 + +import ( + "github.com/aws/aws-sdk-go/service/ec2" +) + +const ( + // https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreditSpecificationRequest.html#API_CreditSpecificationRequest_Contents + CPUCreditsStandard = "standard" + CPUCreditsUnlimited = "unlimited" +) + +func CPUCredits_Values() []string { + return []string{ + CPUCreditsStandard, + CPUCreditsUnlimited, + } +} + +const ( + // The AWS SDK constant ec2.FleetOnDemandAllocationStrategyLowestPrice is incorrect. + FleetOnDemandAllocationStrategyLowestPrice = "lowestPrice" +) + +func FleetOnDemandAllocationStrategy_Values() []string { + return append( + removeFirstOccurrenceFromStringSlice(ec2.FleetOnDemandAllocationStrategy_Values(), ec2.FleetOnDemandAllocationStrategyLowestPrice), + FleetOnDemandAllocationStrategyLowestPrice, + ) +} + +const ( + // The AWS SDK constant ec2.SpotAllocationStrategyLowestPrice is incorrect. + SpotAllocationStrategyLowestPrice = "lowestPrice" +) + +func SpotAllocationStrategy_Values() []string { + return append( + removeFirstOccurrenceFromStringSlice(ec2.SpotAllocationStrategy_Values(), ec2.SpotAllocationStrategyLowestPrice), + SpotAllocationStrategyLowestPrice, + ) +} + +const ( + // https://docs.aws.amazon.com/vpc/latest/privatelink/vpce-interface.html#vpce-interface-lifecycle + vpcEndpointStateAvailable = "available" + vpcEndpointStateDeleted = "deleted" + vpcEndpointStateDeleting = "deleting" + vpcEndpointStateFailed = "failed" + vpcEndpointStatePending = "pending" + vpcEndpointStatePendingAcceptance = "pendingAcceptance" +) + +const ( + vpnStateModifying = "modifying" +) + +// See https://docs.aws.amazon.com/vm-import/latest/userguide/vmimport-image-import.html#check-import-task-status +const ( + EBSSnapshotImportStateActive = "active" + EBSSnapshotImportStateDeleting = "deleting" + EBSSnapshotImportStateDeleted = "deleted" + EBSSnapshotImportStateUpdating = "updating" + EBSSnapshotImportStateValidating = "validating" + EBSSnapshotImportStateValidated = "validated" + EBSSnapshotImportStateConverting = "converting" + EBSSnapshotImportStateCompleted = "completed" +) + +// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateNetworkInterface.html#API_CreateNetworkInterface_Example_2_Response. +const ( + NetworkInterfaceStatusPending = "pending" +) + +// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInternetGateways.html#API_DescribeInternetGateways_Example_1_Response. +const ( + InternetGatewayAttachmentStateAvailable = "available" +) + +// See https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CustomerGateway.html#API_CustomerGateway_Contents. +const ( + CustomerGatewayStateAvailable = "available" + CustomerGatewayStateDeleted = "deleted" + CustomerGatewayStateDeleting = "deleting" + CustomerGatewayStatePending = "pending" +) + +const ( + vpnTunnelOptionsDPDTimeoutActionClear = "clear" + vpnTunnelOptionsDPDTimeoutActionNone = "none" + vpnTunnelOptionsDPDTimeoutActionRestart = "restart" +) + +func vpnTunnelOptionsDPDTimeoutAction_Values() []string { + return []string{ + vpnTunnelOptionsDPDTimeoutActionClear, + vpnTunnelOptionsDPDTimeoutActionNone, + vpnTunnelOptionsDPDTimeoutActionRestart, + } +} + +const ( + vpnTunnelOptionsIKEVersion1 = "ikev1" + vpnTunnelOptionsIKEVersion2 = "ikev2" +) + +func vpnTunnelOptionsIKEVersion_Values() []string { + return []string{ + vpnTunnelOptionsIKEVersion1, + vpnTunnelOptionsIKEVersion2, + } +} + +const ( + vpnTunnelOptionsPhase1EncryptionAlgorithmAES128 = "AES128" + vpnTunnelOptionsPhase1EncryptionAlgorithmAES256 = "AES256" + vpnTunnelOptionsPhase1EncryptionAlgorithmAES128_GCM_16 = "AES128-GCM-16" + vpnTunnelOptionsPhase1EncryptionAlgorithmAES256_GCM_16 = "AES256-GCM-16" +) + +func vpnTunnelOptionsPhase1EncryptionAlgorithm_Values() []string { + return []string{ + vpnTunnelOptionsPhase1EncryptionAlgorithmAES128, + vpnTunnelOptionsPhase1EncryptionAlgorithmAES256, + vpnTunnelOptionsPhase1EncryptionAlgorithmAES128_GCM_16, + vpnTunnelOptionsPhase1EncryptionAlgorithmAES256_GCM_16, + } +} + +const ( + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA1 = "SHA1" + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA2_256 = "SHA2-256" + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA2_384 = "SHA2-384" + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA2_512 = "SHA2-512" +) + +func vpnTunnelOptionsPhase1IntegrityAlgorithm_Values() []string { + return []string{ + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA1, + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA2_256, + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA2_384, + vpnTunnelOptionsPhase1IntegrityAlgorithmSHA2_512, + } +} + +const ( + vpnTunnelOptionsPhase2EncryptionAlgorithmAES128 = "AES128" + vpnTunnelOptionsPhase2EncryptionAlgorithmAES256 = "AES256" + vpnTunnelOptionsPhase2EncryptionAlgorithmAES128_GCM_16 = "AES128-GCM-16" + vpnTunnelOptionsPhase2EncryptionAlgorithmAES256_GCM_16 = "AES256-GCM-16" +) + +func vpnTunnelOptionsPhase2EncryptionAlgorithm_Values() []string { + return []string{ + vpnTunnelOptionsPhase2EncryptionAlgorithmAES128, + vpnTunnelOptionsPhase2EncryptionAlgorithmAES256, + vpnTunnelOptionsPhase2EncryptionAlgorithmAES128_GCM_16, + vpnTunnelOptionsPhase2EncryptionAlgorithmAES256_GCM_16, + } +} + +const ( + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA1 = "SHA1" + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA2_256 = "SHA2-256" + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA2_384 = "SHA2-384" + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA2_512 = "SHA2-512" +) + +func vpnTunnelOptionsPhase2IntegrityAlgorithm_Values() []string { + return []string{ + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA1, + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA2_256, + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA2_384, + vpnTunnelOptionsPhase2IntegrityAlgorithmSHA2_512, + } +} + +const ( + vpnTunnelOptionsStartupActionAdd = "add" + vpnTunnelOptionsStartupActionStart = "start" +) + +func vpnTunnelOptionsStartupAction_Values() []string { + return []string{ + vpnTunnelOptionsStartupActionAdd, + vpnTunnelOptionsStartupActionStart, + } +} + +const ( + vpnConnectionTypeIPsec1 = "ipsec.1" + vpnConnectionTypeIPsec1_AES256 = "ipsec.1-aes256" // https://github.com/hashicorp/terraform-provider-aws/issues/23105. +) + +func vpnConnectionType_Values() []string { + return []string{ + vpnConnectionTypeIPsec1, + vpnConnectionTypeIPsec1_AES256, + } +} + +const ( + AmazonIPv6PoolID = "Amazon" +) + +const ( + DefaultDHCPOptionsID = "default" +) + +const ( + DefaultSecurityGroupName = "default" +) + +const ( + DefaultSnapshotImportRoleName = "vmimport" +) + +const ( + LaunchTemplateVersionDefault = "$Default" + LaunchTemplateVersionLatest = "$Latest" +) + +const ( + SriovNetSupportSimple = "simple" +) + +const ( + TargetStorageTierStandard = "standard" +) + +func removeFirstOccurrenceFromStringSlice(slice []string, s string) []string { + for i, v := range slice { + if v == s { + return append(slice[:i], slice[i+1:]...) + } + } + + return slice +} + +const ( + OutsideIPAddressTypePrivateIPv4 = "PrivateIpv4" + OutsideIPAddressTypePublicIPv4 = "PublicIpv4" +) + +func outsideIPAddressType_Values() []string { + return []string{ + OutsideIPAddressTypePrivateIPv4, + OutsideIPAddressTypePublicIPv4, + } +} + +const ( + securityGroupRuleTypeEgress = "egress" + securityGroupRuleTypeIngress = "ingress" +) + +func securityGroupRuleType_Values() []string { + return []string{ + securityGroupRuleTypeEgress, + securityGroupRuleTypeIngress, + } +} diff --git a/internal/service/ec2/errors.go b/internal/service/ec2/errors.go new file mode 100644 index 0000000..6b0abe6 --- /dev/null +++ b/internal/service/ec2/errors.go @@ -0,0 +1,161 @@ +package ec2 + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/ec2" + multierror "github.com/hashicorp/go-multierror" +) + +const ( + errCodeAuthFailure = "AuthFailure" + errCodeClientInvalidHostIDNotFound = "Client.InvalidHostID.NotFound" + ErrCodeDefaultSubnetAlreadyExistsInAvailabilityZone = "DefaultSubnetAlreadyExistsInAvailabilityZone" + errCodeDependencyViolation = "DependencyViolation" + errCodeGatewayNotAttached = "Gateway.NotAttached" + errCodeIncorrectState = "IncorrectState" + errCodeInvalidAMIIDNotFound = "InvalidAMIID.NotFound" + errCodeInvalidAMIIDUnavailable = "InvalidAMIID.Unavailable" + errCodeInvalidAddressNotFound = "InvalidAddress.NotFound" + errCodeInvalidAllocationIDNotFound = "InvalidAllocationID.NotFound" + errCodeInvalidAssociationIDNotFound = "InvalidAssociationID.NotFound" + errCodeInvalidAttachmentIDNotFound = "InvalidAttachmentID.NotFound" + errCodeInvalidCapacityReservationIdNotFound = "InvalidCapacityReservationId.NotFound'" + ErrCodeInvalidCarrierGatewayIDNotFound = "InvalidCarrierGatewayID.NotFound" + errCodeInvalidClientVPNActiveAssociationNotFound = "InvalidClientVpnActiveAssociationNotFound" + errCodeInvalidClientVPNAssociationIdNotFound = "InvalidClientVpnAssociationIdNotFound" + errCodeInvalidClientVPNAuthorizationRuleNotFound = "InvalidClientVpnEndpointAuthorizationRuleNotFound" + errCodeInvalidClientVPNEndpointIdNotFound = "InvalidClientVpnEndpointId.NotFound" + errCodeInvalidClientVPNRouteNotFound = "InvalidClientVpnRouteNotFound" + ErrCodeInvalidConnectionNotification = "InvalidConnectionNotification" + errCodeInvalidConversionTaskIdMalformed = "InvalidConversionTaskId.Malformed" + errCodeInvalidCustomerGatewayIDNotFound = "InvalidCustomerGatewayID.NotFound" + errCodeInvalidDHCPOptionIDNotFound = "InvalidDhcpOptionID.NotFound" + errCodeInvalidFleetIdNotFound = "InvalidFleetId.NotFound" + errCodeInvalidFlowLogIdNotFound = "InvalidFlowLogId.NotFound" + errCodeInvalidGatewayIDNotFound = "InvalidGatewayID.NotFound" + errCodeInvalidGroupInUse = "InvalidGroup.InUse" + errCodeInvalidGroupNotFound = "InvalidGroup.NotFound" + errCodeInvalidHostIDNotFound = "InvalidHostID.NotFound" + errCodeInvalidInstanceIDNotFound = "InvalidInstanceID.NotFound" + errCodeInvalidInternetGatewayIDNotFound = "InvalidInternetGatewayID.NotFound" + errCodeInvalidKeyPairNotFound = "InvalidKeyPair.NotFound" + errCodeInvalidLaunchTemplateIdMalformed = "InvalidLaunchTemplateId.Malformed" + errCodeInvalidLaunchTemplateIdNotFound = "InvalidLaunchTemplateId.NotFound" + errCodeInvalidLaunchTemplateIdVersionNotFound = "InvalidLaunchTemplateId.VersionNotFound" + errCodeInvalidLaunchTemplateNameNotFoundException = "InvalidLaunchTemplateName.NotFoundException" + errCodeInvalidNetworkACLEntryNotFound = "InvalidNetworkAclEntry.NotFound" + errCodeInvalidNetworkACLIDNotFound = "InvalidNetworkAclID.NotFound" + errCodeInvalidNetworkInterfaceIDNotFound = "InvalidNetworkInterfaceID.NotFound" + errCodeInvalidNetworkInsightsPathIdNotFound = "InvalidNetworkInsightsPathId.NotFound" + errCodeInvalidParameter = "InvalidParameter" + errCodeInvalidParameterException = "InvalidParameterException" + errCodeInvalidParameterValue = "InvalidParameterValue" + errCodeInvalidPermissionDuplicate = "InvalidPermission.Duplicate" + errCodeInvalidPermissionNotFound = "InvalidPermission.NotFound" + errCodeInvalidPlacementGroupUnknown = "InvalidPlacementGroup.Unknown" + errCodeInvalidPoolIDNotFound = "InvalidPoolID.NotFound" + errCodeInvalidPrefixListIDNotFound = "InvalidPrefixListID.NotFound" + errCodeInvalidPrefixListIdNotFound = "InvalidPrefixListId.NotFound" + errCodeInvalidRouteNotFound = "InvalidRoute.NotFound" + errCodeInvalidRouteTableIDNotFound = "InvalidRouteTableID.NotFound" + errCodeInvalidRouteTableIdNotFound = "InvalidRouteTableId.NotFound" + errCodeInvalidSecurityGroupIDNotFound = "InvalidSecurityGroupID.NotFound" + errCodeInvalidServiceName = "InvalidServiceName" + errCodeInvalidSnapshotInUse = "InvalidSnapshot.InUse" + errCodeInvalidSnapshotNotFound = "InvalidSnapshot.NotFound" + ErrCodeInvalidSpotDatafeedNotFound = "InvalidSpotDatafeed.NotFound" + errCodeInvalidSpotFleetRequestConfig = "InvalidSpotFleetRequestConfig" + errCodeInvalidSpotFleetRequestIdNotFound = "InvalidSpotFleetRequestId.NotFound" + errCodeInvalidSpotInstanceRequestIDNotFound = "InvalidSpotInstanceRequestID.NotFound" + errCodeInvalidSubnetCIDRReservationIDNotFound = "InvalidSubnetCidrReservationID.NotFound" + errCodeInvalidSubnetIDNotFound = "InvalidSubnetID.NotFound" + errCodeInvalidSubnetIdNotFound = "InvalidSubnetId.NotFound" + errCodeInvalidTransitGatewayAttachmentIDNotFound = "InvalidTransitGatewayAttachmentID.NotFound" + errCodeInvalidTransitGatewayConnectPeerIDNotFound = "InvalidTransitGatewayConnectPeerID.NotFound" + errCodeInvalidTransitGatewayIDNotFound = "InvalidTransitGatewayID.NotFound" + errCodeInvalidTransitGatewayMulticastDomainIdNotFound = "InvalidTransitGatewayMulticastDomainId.NotFound" + errCodeInvalidVolumeNotFound = "InvalidVolume.NotFound" + errCodeInvalidVPCCIDRBlockAssociationIDNotFound = "InvalidVpcCidrBlockAssociationID.NotFound" + errCodeInvalidVPCEndpointIdNotFound = "InvalidVpcEndpointId.NotFound" + errCodeInvalidVPCEndpointNotFound = "InvalidVpcEndpoint.NotFound" + errCodeInvalidVPCEndpointServiceIdNotFound = "InvalidVpcEndpointServiceId.NotFound" + errCodeInvalidVPCIDNotFound = "InvalidVpcID.NotFound" + errCodeInvalidVPCPeeringConnectionIDNotFound = "InvalidVpcPeeringConnectionID.NotFound" + errCodeInvalidVPNConnectionIDNotFound = "InvalidVpnConnectionID.NotFound" + errCodeInvalidVPNGatewayAttachmentNotFound = "InvalidVpnGatewayAttachment.NotFound" + errCodeInvalidVPNGatewayIDNotFound = "InvalidVpnGatewayID.NotFound" + errCodeNatGatewayNotFound = "NatGatewayNotFound" + errCodeResourceNotReady = "ResourceNotReady" + errCodeSnapshotCreationPerVolumeRateExceeded = "SnapshotCreationPerVolumeRateExceeded" + errCodeUnsupportedOperation = "UnsupportedOperation" + errCodeVolumeInUse = "VolumeInUse" +) + +func CancelSpotFleetRequestError(apiObject *ec2.CancelSpotFleetRequestsErrorItem) error { + if apiObject == nil || apiObject.Error == nil { + return nil + } + + return awserr.New(aws.StringValue(apiObject.Error.Code), aws.StringValue(apiObject.Error.Message), nil) +} + +func CancelSpotFleetRequestsError(apiObjects []*ec2.CancelSpotFleetRequestsErrorItem) error { + var errors *multierror.Error + + for _, apiObject := range apiObjects { + if err := CancelSpotFleetRequestError(apiObject); err != nil { + errors = multierror.Append(errors, fmt.Errorf("%s: %w", aws.StringValue(apiObject.SpotFleetRequestId), err)) + } + } + + return errors.ErrorOrNil() +} + +func DeleteFleetError(apiObject *ec2.DeleteFleetErrorItem) error { + if apiObject == nil || apiObject.Error == nil { + return nil + } + + return awserr.New(aws.StringValue(apiObject.Error.Code), aws.StringValue(apiObject.Error.Message), nil) +} + +func DeleteFleetsError(apiObjects []*ec2.DeleteFleetErrorItem) error { + var errors *multierror.Error + + for _, apiObject := range apiObjects { + if err := DeleteFleetError(apiObject); err != nil { + errors = multierror.Append(errors, fmt.Errorf("%s: %w", aws.StringValue(apiObject.FleetId), err)) + } + } + + return errors.ErrorOrNil() +} + +func UnsuccessfulItemError(apiObject *ec2.UnsuccessfulItemError) error { + if apiObject == nil { + return nil + } + + return awserr.New(aws.StringValue(apiObject.Code), aws.StringValue(apiObject.Message), nil) +} + +func UnsuccessfulItemsError(apiObjects []*ec2.UnsuccessfulItem) error { + var errors *multierror.Error + + for _, apiObject := range apiObjects { + if apiObject == nil { + continue + } + + err := UnsuccessfulItemError(apiObject.Error) + + if err != nil { + errors = multierror.Append(errors, fmt.Errorf("%s: %w", aws.StringValue(apiObject.ResourceId), err)) + } + } + + return errors.ErrorOrNil() +} diff --git a/internal/service/ec2/errors_test.go b/internal/service/ec2/errors_test.go new file mode 100644 index 0000000..91858cb --- /dev/null +++ b/internal/service/ec2/errors_test.go @@ -0,0 +1,133 @@ +package ec2_test + +import ( + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + tfec2 "github.com/cloudposse/terraform-provider-awsutils/internal/service/ec2" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" +) + +func TestUnsuccessfulItemError(t *testing.T) { + unsuccessfulItemError := &ec2.UnsuccessfulItemError{ + Code: aws.String("test code"), + Message: aws.String("test message"), + } + + err := tfec2.UnsuccessfulItemError(unsuccessfulItemError) + + if !tfawserr.ErrCodeEquals(err, "test code") { + t.Errorf("tfawserr.ErrCodeEquals failed: %s", err) + } + + if !tfawserr.ErrMessageContains(err, "test code", "est mess") { + t.Errorf("tfawserr.ErrMessageContains failed: %s", err) + } +} + +func TestUnsuccessfulItemsError(t *testing.T) { + testCases := []struct { + Name string + Items []*ec2.UnsuccessfulItem + Expected bool + }{ + { + Name: "no items", + }, + { + Name: "one item no error", + Items: []*ec2.UnsuccessfulItem{ + { + ResourceId: aws.String("test resource"), + }, + }, + }, + { + Name: "one item", + Items: []*ec2.UnsuccessfulItem{ + { + Error: &ec2.UnsuccessfulItemError{ + Code: aws.String("test code"), + Message: aws.String("test message"), + }, + ResourceId: aws.String("test resource"), + }, + }, + Expected: true, + }, + { + Name: "two items, first no error", + Items: []*ec2.UnsuccessfulItem{ + { + ResourceId: aws.String("test resource 1"), + }, + { + Error: &ec2.UnsuccessfulItemError{ + Code: aws.String("test code"), + Message: aws.String("test message"), + }, + ResourceId: aws.String("test resource 2"), + }, + }, + Expected: true, + }, + { + Name: "two items, first not as expected", + Items: []*ec2.UnsuccessfulItem{ + { + Error: &ec2.UnsuccessfulItemError{ + Code: aws.String("not what is required"), + Message: aws.String("not what is wanted"), + }, + ResourceId: aws.String("test resource 1"), + }, + { + Error: &ec2.UnsuccessfulItemError{ + Code: aws.String("test code"), + Message: aws.String("test message"), + }, + ResourceId: aws.String("test resource 2"), + }, + }, + }, + { + Name: "two items, first as expected", + Items: []*ec2.UnsuccessfulItem{ + { + Error: &ec2.UnsuccessfulItemError{ + Code: aws.String("test code"), + Message: aws.String("test message"), + }, + ResourceId: aws.String("test resource 1"), + }, + { + Error: &ec2.UnsuccessfulItemError{ + Code: aws.String("not what is required"), + Message: aws.String("not what is wanted"), + }, + ResourceId: aws.String("test resource 2"), + }, + }, + Expected: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + err := tfec2.UnsuccessfulItemsError(testCase.Items) + + got := tfawserr.ErrCodeEquals(err, "test code") + + if got != testCase.Expected { + t.Errorf("ErrCodeEquals got %t, expected %t", got, testCase.Expected) + } + + got = tfawserr.ErrMessageContains(err, "test code", "est mess") + + if got != testCase.Expected { + t.Errorf("ErrMessageContains got %t, expected %t", got, testCase.Expected) + } + }) + } +} diff --git a/internal/service/ec2/filter.go b/internal/service/ec2/filter.go new file mode 100644 index 0000000..3497a70 --- /dev/null +++ b/internal/service/ec2/filter.go @@ -0,0 +1,59 @@ +package ec2 + +import ( + "sort" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" +) + +// BuildAttributeFilterList takes a flat map of scalar attributes (most +// likely values extracted from a *schema.ResourceData on an EC2-querying +// data source) and produces a []*ec2.Filter representing an exact match +// for each of the given non-empty attributes. +// +// The keys of the given attributes map are the attribute names expected +// by the EC2 API, which are usually either in camelcase or with dash-separated +// words. We conventionally map these to underscore-separated identifiers +// with the same words when presenting these as data source query attributes +// in Terraform. +// +// It's the callers responsibility to transform any non-string values into +// the appropriate string serialization required by the AWS API when +// encoding the given filter. Any attributes given with empty string values +// are ignored, assuming that the user wishes to leave that attribute +// unconstrained while filtering. +// +// The purpose of this function is to create values to pass in +// for the "Filters" attribute on most of the "Describe..." API functions in +// the EC2 API, to aid in the implementation of Terraform data sources that +// retrieve data about EC2 objects. +func BuildAttributeFilterList(m map[string]string) []*ec2.Filter { + var filters []*ec2.Filter + + // sort the filters by name to make the output deterministic + var names []string + for k := range m { + names = append(names, k) + } + + sort.Strings(names) + + for _, name := range names { + value := m[name] + if value == "" { + continue + } + + filters = append(filters, NewFilter(name, []string{value})) + } + + return filters +} + +func NewFilter(name string, values []string) *ec2.Filter { + return &ec2.Filter{ + Name: aws.String(name), + Values: aws.StringSlice(values), + } +} diff --git a/internal/service/ec2/find.go b/internal/service/ec2/find.go index 34f83e6..5125726 100644 --- a/internal/service/ec2/find.go +++ b/internal/service/ec2/find.go @@ -1,100 +1,4969 @@ package ec2 import ( + "context" + "fmt" + "strconv" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/cloudposse/terraform-provider-awsutils/internal/tfresource" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -// VpcDefault looks up the Default Vpc. When not found, returns nil and potentially an API error. -func FindInternetGatewayForVPC(conn *ec2.EC2, vpcID string) (*ec2.InternetGateway, error) { - filters := []*ec2.Filter{ - { - Name: aws.String("attachment.vpc-id"), - Values: []*string{aws.String(vpcID)}, - }, +func FindAvailabilityZones(conn *ec2.EC2, input *ec2.DescribeAvailabilityZonesInput) ([]*ec2.AvailabilityZone, error) { + output, err := conn.DescribeAvailabilityZones(input) + + if err != nil { + return nil, err } - input := &ec2.DescribeInternetGatewaysInput{ - Filters: filters, + if output == nil { + return nil, tfresource.NewEmptyResultError(input) } - output, err := conn.DescribeInternetGateways(input) + return output.AvailabilityZones, nil +} + +func FindAvailabilityZone(conn *ec2.EC2, input *ec2.DescribeAvailabilityZonesInput) (*ec2.AvailabilityZone, error) { + output, err := FindAvailabilityZones(conn, input) if err != nil { return nil, err } - if output == nil { - return nil, nil + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) } - for _, ig := range output.InternetGateways { - if ig == nil { - continue + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindAvailabilityZoneGroupByName(conn *ec2.EC2, name string) (*ec2.AvailabilityZone, error) { + input := &ec2.DescribeAvailabilityZonesInput{ + AllAvailabilityZones: aws.Bool(true), + Filters: BuildAttributeFilterList(map[string]string{ + "group-name": name, + }), + } + + output, err := FindAvailabilityZones(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + // An AZ group may contain more than one AZ. + availabilityZone := output[0] + + // Eventual consistency check. + if aws.StringValue(availabilityZone.GroupName) != name { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return availabilityZone, nil +} + +func FindCapacityReservation(conn *ec2.EC2, input *ec2.DescribeCapacityReservationsInput) (*ec2.CapacityReservation, error) { + output, err := FindCapacityReservations(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindCapacityReservations(conn *ec2.EC2, input *ec2.DescribeCapacityReservationsInput) ([]*ec2.CapacityReservation, error) { + var output []*ec2.CapacityReservation + + err := conn.DescribeCapacityReservationsPages(input, func(page *ec2.DescribeCapacityReservationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.CapacityReservations { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidCapacityReservationIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, } + } - return ig, nil + if err != nil { + return nil, err } - return nil, nil + return output, nil } -// SubnetsForVPC looks up a the Subnets for a VPC. When not found, returns nil and potentially an API error. -func FindSubnetsForVPC(conn *ec2.EC2, vpcID string) ([]*ec2.Subnet, error) { - filters := []*ec2.Filter{ - { - Name: aws.String("vpc-id"), - Values: []*string{aws.String(vpcID)}, - }, +func FindCapacityReservationByID(conn *ec2.EC2, id string) (*ec2.CapacityReservation, error) { + input := &ec2.DescribeCapacityReservationsInput{ + CapacityReservationIds: aws.StringSlice([]string{id}), } - input := &ec2.DescribeSubnetsInput{ - Filters: filters, + output, err := FindCapacityReservation(conn, input) + + if err != nil { + return nil, err } - output, err := conn.DescribeSubnets(input) + // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/capacity-reservations-using.html#capacity-reservations-view. + if state := aws.StringValue(output.State); state == ec2.CapacityReservationStateCancelled || state == ec2.CapacityReservationStateExpired { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.CapacityReservationId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +// FindCarrierGatewayByID returns the carrier gateway corresponding to the specified identifier. +// Returns nil and potentially an error if no carrier gateway is found. +func FindCarrierGatewayByID(conn *ec2.EC2, id string) (*ec2.CarrierGateway, error) { + input := &ec2.DescribeCarrierGatewaysInput{ + CarrierGatewayIds: aws.StringSlice([]string{id}), + } + output, err := conn.DescribeCarrierGateways(input) if err != nil { return nil, err } - if output == nil || len(output.Subnets) == 0 || output.Subnets[0] == nil { + if output == nil || len(output.CarrierGateways) == 0 { return nil, nil } - return output.Subnets, nil + return output.CarrierGateways[0], nil } -// VpcDefault looks up the Default Vpc. When not found, returns nil and potentially an API error. -func FindDefaultVpc(conn *ec2.EC2) (*ec2.Vpc, error) { - filters := []*ec2.Filter{ - { - Name: aws.String("isDefault"), - Values: []*string{aws.String("true")}, - }, +func FindClientVPNEndpoint(conn *ec2.EC2, input *ec2.DescribeClientVpnEndpointsInput) (*ec2.ClientVpnEndpoint, error) { + output, err := FindClientVPNEndpoints(conn, input) + + if err != nil { + return nil, err } - input := &ec2.DescribeVpcsInput{ - Filters: filters, + if len(output) == 0 || output[0] == nil || output[0].Status == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindClientVPNEndpoints(conn *ec2.EC2, input *ec2.DescribeClientVpnEndpointsInput) ([]*ec2.ClientVpnEndpoint, error) { + var output []*ec2.ClientVpnEndpoint + + err := conn.DescribeClientVpnEndpointsPages(input, func(page *ec2.DescribeClientVpnEndpointsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.ClientVpnEndpoints { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err } - output, err := conn.DescribeVpcs(input) + return output, nil +} + +func FindClientVPNEndpointByID(conn *ec2.EC2, id string) (*ec2.ClientVpnEndpoint, error) { + input := &ec2.DescribeClientVpnEndpointsInput{ + ClientVpnEndpointIds: aws.StringSlice([]string{id}), + } + + output, err := FindClientVPNEndpoint(conn, input) if err != nil { return nil, err } - if output == nil { - return nil, nil + if state := aws.StringValue(output.Status.Code); state == ec2.ClientVpnEndpointStatusCodeDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } } - for _, vpc := range output.Vpcs { - if vpc == nil { - continue + // Eventual consistency check. + if aws.StringValue(output.ClientVpnEndpointId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindClientVPNEndpointClientConnectResponseOptionsByID(conn *ec2.EC2, id string) (*ec2.ClientConnectResponseOptions, error) { + output, err := FindClientVPNEndpointByID(conn, id) + + if err != nil { + return nil, err + } + + if output.ClientConnectOptions == nil || output.ClientConnectOptions.Status == nil { + return nil, tfresource.NewEmptyResultError(id) + } + + return output.ClientConnectOptions, nil +} + +func FindClientVPNAuthorizationRule(conn *ec2.EC2, input *ec2.DescribeClientVpnAuthorizationRulesInput) (*ec2.AuthorizationRule, error) { + output, err := FindClientVPNAuthorizationRules(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Status == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindClientVPNAuthorizationRules(conn *ec2.EC2, input *ec2.DescribeClientVpnAuthorizationRulesInput) ([]*ec2.AuthorizationRule, error) { + var output []*ec2.AuthorizationRule + + err := conn.DescribeClientVpnAuthorizationRulesPages(input, func(page *ec2.DescribeClientVpnAuthorizationRulesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.AuthorizationRules { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindClientVPNAuthorizationRuleByThreePartKey(conn *ec2.EC2, endpointID, targetNetworkCIDR, accessGroupID string) (*ec2.AuthorizationRule, error) { + filters := map[string]string{ + "destination-cidr": targetNetworkCIDR, + } + if accessGroupID != "" { + filters["group-id"] = accessGroupID + } + input := &ec2.DescribeClientVpnAuthorizationRulesInput{ + ClientVpnEndpointId: aws.String(endpointID), + Filters: BuildAttributeFilterList(filters), + } + + return FindClientVPNAuthorizationRule(conn, input) +} + +func FindClientVPNNetworkAssociation(conn *ec2.EC2, input *ec2.DescribeClientVpnTargetNetworksInput) (*ec2.TargetNetwork, error) { + output, err := FindClientVPNNetworkAssociations(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Status == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindClientVPNNetworkAssociations(conn *ec2.EC2, input *ec2.DescribeClientVpnTargetNetworksInput) ([]*ec2.TargetNetwork, error) { + var output []*ec2.TargetNetwork + + err := conn.DescribeClientVpnTargetNetworksPages(input, func(page *ec2.DescribeClientVpnTargetNetworksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.ClientVpnTargetNetworks { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound, errCodeInvalidClientVPNAssociationIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindClientVPNNetworkAssociationByIDs(conn *ec2.EC2, associationID, endpointID string) (*ec2.TargetNetwork, error) { + input := &ec2.DescribeClientVpnTargetNetworksInput{ + AssociationIds: aws.StringSlice([]string{associationID}), + ClientVpnEndpointId: aws.String(endpointID), + } + + output, err := FindClientVPNNetworkAssociation(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.Status.Code); state == ec2.AssociationStatusCodeDisassociated { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.ClientVpnEndpointId) != endpointID || aws.StringValue(output.AssociationId) != associationID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindClientVPNRoute(conn *ec2.EC2, input *ec2.DescribeClientVpnRoutesInput) (*ec2.ClientVpnRoute, error) { + output, err := FindClientVPNRoutes(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Status == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindClientVPNRoutes(conn *ec2.EC2, input *ec2.DescribeClientVpnRoutesInput) ([]*ec2.ClientVpnRoute, error) { + var output []*ec2.ClientVpnRoute + + err := conn.DescribeClientVpnRoutesPages(input, func(page *ec2.DescribeClientVpnRoutesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Routes { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidClientVPNEndpointIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindClientVPNRouteByThreePartKey(conn *ec2.EC2, endpointID, targetSubnetID, destinationCIDR string) (*ec2.ClientVpnRoute, error) { + input := &ec2.DescribeClientVpnRoutesInput{ + ClientVpnEndpointId: aws.String(endpointID), + Filters: BuildAttributeFilterList(map[string]string{ + "destination-cidr": destinationCIDR, + "target-subnet": targetSubnetID, + }), + } + + return FindClientVPNRoute(conn, input) +} + +func FindCOIPPools(conn *ec2.EC2, input *ec2.DescribeCoipPoolsInput) ([]*ec2.CoipPool, error) { + var output []*ec2.CoipPool + + err := conn.DescribeCoipPoolsPages(input, func(page *ec2.DescribeCoipPoolsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.CoipPools { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidPoolIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindCOIPPool(conn *ec2.EC2, input *ec2.DescribeCoipPoolsInput) (*ec2.CoipPool, error) { + output, err := FindCOIPPools(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindEBSVolumes(conn *ec2.EC2, input *ec2.DescribeVolumesInput) ([]*ec2.Volume, error) { + var output []*ec2.Volume + + err := conn.DescribeVolumesPages(input, func(page *ec2.DescribeVolumesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Volumes { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVolumeNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindEBSVolume(conn *ec2.EC2, input *ec2.DescribeVolumesInput) (*ec2.Volume, error) { + output, err := FindEBSVolumes(conn, input) - return vpc, nil + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindEBSVolumeByID(conn *ec2.EC2, id string) (*ec2.Volume, error) { + input := &ec2.DescribeVolumesInput{ + VolumeIds: aws.StringSlice([]string{id}), + } + + output, err := FindEBSVolume(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.VolumeStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.VolumeId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindEBSVolumeAttachment(conn *ec2.EC2, volumeID, instanceID, deviceName string) (*ec2.VolumeAttachment, error) { + input := &ec2.DescribeVolumesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "attachment.device": deviceName, + "attachment.instance-id": instanceID, + }), + VolumeIds: aws.StringSlice([]string{volumeID}), + } + + output, err := FindEBSVolume(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.VolumeStateAvailable || state == ec2.VolumeStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.VolumeId) != volumeID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + for _, v := range output.Attachments { + if aws.StringValue(v.State) == ec2.VolumeAttachmentStateDetached { + continue + } + + if aws.StringValue(v.Device) == deviceName && aws.StringValue(v.InstanceId) == instanceID { + return v, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindEIPs(conn *ec2.EC2, input *ec2.DescribeAddressesInput) ([]*ec2.Address, error) { + var addresses []*ec2.Address + + output, err := conn.DescribeAddresses(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidAddressNotFound, errCodeInvalidAllocationIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + for _, v := range output.Addresses { + if v != nil { + addresses = append(addresses, v) + } + } + + return addresses, nil +} + +func FindHostByID(conn *ec2.EC2, id string) (*ec2.Host, error) { + input := &ec2.DescribeHostsInput{ + HostIds: aws.StringSlice([]string{id}), + } + + return FindHost(conn, input) +} + +func FindHostByIDAndFilters(conn *ec2.EC2, id string, filters []*ec2.Filter) (*ec2.Host, error) { + input := &ec2.DescribeHostsInput{} + + if id != "" { + input.HostIds = aws.StringSlice([]string{id}) + } + + if len(filters) > 0 { + input.Filter = filters + } + + return FindHost(conn, input) +} + +func FindHost(conn *ec2.EC2, input *ec2.DescribeHostsInput) (*ec2.Host, error) { + output, err := conn.DescribeHosts(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidHostIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.Hosts) == 0 || output.Hosts[0] == nil || output.Hosts[0].HostProperties == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.Hosts); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + host := output.Hosts[0] + + if state := aws.StringValue(host.State); state == ec2.AllocationStateReleased || state == ec2.AllocationStateReleasedPermanentFailure { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + return host, nil +} + +func FindImage(conn *ec2.EC2, input *ec2.DescribeImagesInput) (*ec2.Image, error) { + output, err := conn.DescribeImages(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidAMIIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.Images) == 0 || output.Images[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.Images); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.Images[0], nil +} + +func FindImageByID(conn *ec2.EC2, id string) (*ec2.Image, error) { + input := &ec2.DescribeImagesInput{ + ImageIds: aws.StringSlice([]string{id}), + } + + output, err := FindImage(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.ImageStateDeregistered { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.ImageId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindImageAttribute(ctx context.Context, conn *ec2.EC2, input *ec2.DescribeImageAttributeInput) (*ec2.DescribeImageAttributeOutput, error) { + output, err := conn.DescribeImageAttributeWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidAMIIDNotFound, errCodeInvalidAMIIDUnavailable) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func FindImageLaunchPermissionsByID(ctx context.Context, conn *ec2.EC2, id string) ([]*ec2.LaunchPermission, error) { + input := &ec2.DescribeImageAttributeInput{ + Attribute: aws.String(ec2.ImageAttributeNameLaunchPermission), + ImageId: aws.String(id), + } + + output, err := FindImageAttribute(ctx, conn, input) + + if err != nil { + return nil, err + } + + if len(output.LaunchPermissions) == 0 { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.LaunchPermissions, nil +} + +func FindImageLaunchPermission(ctx context.Context, conn *ec2.EC2, imageID, accountID, group, organizationARN, organizationalUnitARN string) (*ec2.LaunchPermission, error) { + output, err := FindImageLaunchPermissionsByID(ctx, conn, imageID) + + if err != nil { + return nil, err + } + + for _, v := range output { + if (accountID != "" && aws.StringValue(v.UserId) == accountID) || + (group != "" && aws.StringValue(v.Group) == group) || + (organizationARN != "" && aws.StringValue(v.OrganizationArn) == organizationARN) || + (organizationalUnitARN != "" && aws.StringValue(v.OrganizationalUnitArn) == organizationalUnitARN) { + return v, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindInstances(conn *ec2.EC2, input *ec2.DescribeInstancesInput) ([]*ec2.Instance, error) { + var output []*ec2.Instance + + err := conn.DescribeInstancesPages(input, func(page *ec2.DescribeInstancesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Reservations { + if v != nil { + for _, v := range v.Instances { + if v != nil { + output = append(output, v) + } + } + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidInstanceIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindInstance(conn *ec2.EC2, input *ec2.DescribeInstancesInput) (*ec2.Instance, error) { + output, err := FindInstances(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].State == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindInstanceByID(conn *ec2.EC2, id string) (*ec2.Instance, error) { + input := &ec2.DescribeInstancesInput{ + InstanceIds: aws.StringSlice([]string{id}), + } + + output, err := FindInstance(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State.Name); state == ec2.InstanceStateNameTerminated { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.InstanceId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindInstanceCreditSpecifications(conn *ec2.EC2, input *ec2.DescribeInstanceCreditSpecificationsInput) ([]*ec2.InstanceCreditSpecification, error) { + var output []*ec2.InstanceCreditSpecification + + err := conn.DescribeInstanceCreditSpecificationsPages(input, func(page *ec2.DescribeInstanceCreditSpecificationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.InstanceCreditSpecifications { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidInstanceIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindInstanceCreditSpecification(conn *ec2.EC2, input *ec2.DescribeInstanceCreditSpecificationsInput) (*ec2.InstanceCreditSpecification, error) { + output, err := FindInstanceCreditSpecifications(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindInstanceCreditSpecificationByID(conn *ec2.EC2, id string) (*ec2.InstanceCreditSpecification, error) { + input := &ec2.DescribeInstanceCreditSpecificationsInput{ + InstanceIds: aws.StringSlice([]string{id}), + } + + output, err := FindInstanceCreditSpecification(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.InstanceId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindInstanceTypes(conn *ec2.EC2, input *ec2.DescribeInstanceTypesInput) ([]*ec2.InstanceTypeInfo, error) { + var output []*ec2.InstanceTypeInfo + + err := conn.DescribeInstanceTypesPages(input, func(page *ec2.DescribeInstanceTypesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.InstanceTypes { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindInstanceType(conn *ec2.EC2, input *ec2.DescribeInstanceTypesInput) (*ec2.InstanceTypeInfo, error) { + output, err := FindInstanceTypes(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindInstanceTypeByName(conn *ec2.EC2, name string) (*ec2.InstanceTypeInfo, error) { + input := &ec2.DescribeInstanceTypesInput{ + InstanceTypes: aws.StringSlice([]string{name}), + } + + output, err := FindInstanceType(conn, input) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindInstanceTypeOfferings(conn *ec2.EC2, input *ec2.DescribeInstanceTypeOfferingsInput) ([]*ec2.InstanceTypeOffering, error) { + var output []*ec2.InstanceTypeOffering + + err := conn.DescribeInstanceTypeOfferingsPages(input, func(page *ec2.DescribeInstanceTypeOfferingsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.InstanceTypeOfferings { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLocalGatewayRouteTables(conn *ec2.EC2, input *ec2.DescribeLocalGatewayRouteTablesInput) ([]*ec2.LocalGatewayRouteTable, error) { + var output []*ec2.LocalGatewayRouteTable + + err := conn.DescribeLocalGatewayRouteTablesPages(input, func(page *ec2.DescribeLocalGatewayRouteTablesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.LocalGatewayRouteTables { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLocalGatewayRouteTable(conn *ec2.EC2, input *ec2.DescribeLocalGatewayRouteTablesInput) (*ec2.LocalGatewayRouteTable, error) { + output, err := FindLocalGatewayRouteTables(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindLocalGatewayVirtualInterfaceGroups(conn *ec2.EC2, input *ec2.DescribeLocalGatewayVirtualInterfaceGroupsInput) ([]*ec2.LocalGatewayVirtualInterfaceGroup, error) { + var output []*ec2.LocalGatewayVirtualInterfaceGroup + + err := conn.DescribeLocalGatewayVirtualInterfaceGroupsPages(input, func(page *ec2.DescribeLocalGatewayVirtualInterfaceGroupsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.LocalGatewayVirtualInterfaceGroups { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLocalGatewayVirtualInterfaceGroup(conn *ec2.EC2, input *ec2.DescribeLocalGatewayVirtualInterfaceGroupsInput) (*ec2.LocalGatewayVirtualInterfaceGroup, error) { + output, err := FindLocalGatewayVirtualInterfaceGroups(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindLocalGateways(conn *ec2.EC2, input *ec2.DescribeLocalGatewaysInput) ([]*ec2.LocalGateway, error) { + var output []*ec2.LocalGateway + + err := conn.DescribeLocalGatewaysPages(input, func(page *ec2.DescribeLocalGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.LocalGateways { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLocalGateway(conn *ec2.EC2, input *ec2.DescribeLocalGatewaysInput) (*ec2.LocalGateway, error) { + output, err := FindLocalGateways(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindNetworkACL(conn *ec2.EC2, input *ec2.DescribeNetworkAclsInput) (*ec2.NetworkAcl, error) { + output, err := FindNetworkACLs(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindNetworkACLs(conn *ec2.EC2, input *ec2.DescribeNetworkAclsInput) ([]*ec2.NetworkAcl, error) { + var output []*ec2.NetworkAcl + + err := conn.DescribeNetworkAclsPages(input, func(page *ec2.DescribeNetworkAclsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.NetworkAcls { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidNetworkACLIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindNetworkACLByID(conn *ec2.EC2, id string) (*ec2.NetworkAcl, error) { + input := &ec2.DescribeNetworkAclsInput{ + NetworkAclIds: aws.StringSlice([]string{id}), + } + + output, err := FindNetworkACL(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.NetworkAclId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindNetworkACLAssociationByID(conn *ec2.EC2, associationID string) (*ec2.NetworkAclAssociation, error) { + input := &ec2.DescribeNetworkAclsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "association.association-id": associationID, + }), + } + + output, err := FindNetworkACL(conn, input) + + if err != nil { + return nil, err + } + + for _, v := range output.Associations { + if aws.StringValue(v.NetworkAclAssociationId) == associationID { + return v, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindNetworkACLAssociationBySubnetID(conn *ec2.EC2, subnetID string) (*ec2.NetworkAclAssociation, error) { + input := &ec2.DescribeNetworkAclsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "association.subnet-id": subnetID, + }), + } + + output, err := FindNetworkACL(conn, input) + + if err != nil { + return nil, err + } + + for _, v := range output.Associations { + if aws.StringValue(v.SubnetId) == subnetID { + return v, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindNetworkACLEntryByThreePartKey(conn *ec2.EC2, naclID string, egress bool, ruleNumber int) (*ec2.NetworkAclEntry, error) { + input := &ec2.DescribeNetworkAclsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "entry.egress": strconv.FormatBool(egress), + "entry.rule-number": strconv.Itoa(ruleNumber), + }), + NetworkAclIds: aws.StringSlice([]string{naclID}), + } + + output, err := FindNetworkACL(conn, input) + + if err != nil { + return nil, err + } + + for _, v := range output.Entries { + if aws.BoolValue(v.Egress) == egress && aws.Int64Value(v.RuleNumber) == int64(ruleNumber) { + return v, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindNetworkInterface(conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInput) (*ec2.NetworkInterface, error) { + output, err := FindNetworkInterfaces(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindNetworkInterfaces(conn *ec2.EC2, input *ec2.DescribeNetworkInterfacesInput) ([]*ec2.NetworkInterface, error) { + var output []*ec2.NetworkInterface + + err := conn.DescribeNetworkInterfacesPages(input, func(page *ec2.DescribeNetworkInterfacesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.NetworkInterfaces { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidNetworkInterfaceIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindNetworkInterfaceByID(conn *ec2.EC2, id string) (*ec2.NetworkInterface, error) { + input := &ec2.DescribeNetworkInterfacesInput{ + NetworkInterfaceIds: aws.StringSlice([]string{id}), + } + + output, err := FindNetworkInterface(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.NetworkInterfaceId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(conn *ec2.EC2, attachmentInstanceOwnerID, description string) ([]*ec2.NetworkInterface, error) { + input := &ec2.DescribeNetworkInterfacesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "attachment.instance-owner-id": attachmentInstanceOwnerID, + "description": description, + }), + } + + return FindNetworkInterfaces(conn, input) +} + +func FindNetworkInterfaceAttachmentByID(conn *ec2.EC2, id string) (*ec2.NetworkInterfaceAttachment, error) { + input := &ec2.DescribeNetworkInterfacesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "attachment.attachment-id": id, + }), + } + + networkInterface, err := FindNetworkInterface(conn, input) + + if err != nil { + return nil, err + } + + if networkInterface.Attachment == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return networkInterface.Attachment, nil +} + +func FindNetworkInterfaceSecurityGroup(conn *ec2.EC2, networkInterfaceID string, securityGroupID string) (*ec2.GroupIdentifier, error) { + networkInterface, err := FindNetworkInterfaceByID(conn, networkInterfaceID) + + if err != nil { + return nil, err + } + + for _, groupIdentifier := range networkInterface.Groups { + if aws.StringValue(groupIdentifier.GroupId) == securityGroupID { + return groupIdentifier, nil + } + } + + return nil, &resource.NotFoundError{ + LastError: fmt.Errorf("Network Interface (%s) Security Group (%s) not found", networkInterfaceID, securityGroupID), + } +} + +func FindNetworkInsightsPath(conn *ec2.EC2, input *ec2.DescribeNetworkInsightsPathsInput) (*ec2.NetworkInsightsPath, error) { + output, err := FindNetworkInsightsPaths(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindNetworkInsightsPaths(conn *ec2.EC2, input *ec2.DescribeNetworkInsightsPathsInput) ([]*ec2.NetworkInsightsPath, error) { + var output []*ec2.NetworkInsightsPath + + err := conn.DescribeNetworkInsightsPathsPages(input, func(page *ec2.DescribeNetworkInsightsPathsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.NetworkInsightsPaths { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidNetworkInsightsPathIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindNetworkInsightsPathByID(conn *ec2.EC2, id string) (*ec2.NetworkInsightsPath, error) { + input := &ec2.DescribeNetworkInsightsPathsInput{ + NetworkInsightsPathIds: aws.StringSlice([]string{id}), + } + + output, err := FindNetworkInsightsPath(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.NetworkInsightsPathId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +// FindMainRouteTableAssociationByID returns the main route table association corresponding to the specified identifier. +// Returns NotFoundError if no route table association is found. +func FindMainRouteTableAssociationByID(conn *ec2.EC2, associationID string) (*ec2.RouteTableAssociation, error) { + association, err := FindRouteTableAssociationByID(conn, associationID) + + if err != nil { + return nil, err + } + + if !aws.BoolValue(association.Main) { + return nil, &resource.NotFoundError{ + Message: fmt.Sprintf("%s is not the association with the main route table", associationID), + } + } + + return association, err +} + +// FindMainRouteTableAssociationByVPCID returns the main route table association for the specified VPC. +// Returns NotFoundError if no route table association is found. +func FindMainRouteTableAssociationByVPCID(conn *ec2.EC2, vpcID string) (*ec2.RouteTableAssociation, error) { + routeTable, err := FindMainRouteTableByVPCID(conn, vpcID) + + if err != nil { + return nil, err + } + + for _, association := range routeTable.Associations { + if aws.BoolValue(association.Main) { + if association.AssociationState != nil { + if state := aws.StringValue(association.AssociationState.State); state == ec2.RouteTableAssociationStateCodeDisassociated { + continue + } + } + + return association, nil + } + } + + return nil, &resource.NotFoundError{} +} + +// FindRouteTableAssociationByID returns the route table association corresponding to the specified identifier. +// Returns NotFoundError if no route table association is found. +func FindRouteTableAssociationByID(conn *ec2.EC2, associationID string) (*ec2.RouteTableAssociation, error) { + input := &ec2.DescribeRouteTablesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "association.route-table-association-id": associationID, + }), + } + + routeTable, err := FindRouteTable(conn, input) + + if err != nil { + return nil, err + } + + for _, association := range routeTable.Associations { + if aws.StringValue(association.RouteTableAssociationId) == associationID { + if association.AssociationState != nil { + if state := aws.StringValue(association.AssociationState.State); state == ec2.RouteTableAssociationStateCodeDisassociated { + return nil, &resource.NotFoundError{Message: state} + } + } + + return association, nil + } + } + + return nil, &resource.NotFoundError{} +} + +// FindMainRouteTableByVPCID returns the main route table for the specified VPC. +// Returns NotFoundError if no route table is found. +func FindMainRouteTableByVPCID(conn *ec2.EC2, vpcID string) (*ec2.RouteTable, error) { + input := &ec2.DescribeRouteTablesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "association.main": "true", + "vpc-id": vpcID, + }), + } + + return FindRouteTable(conn, input) +} + +// FindRouteTableByID returns the route table corresponding to the specified identifier. +// Returns NotFoundError if no route table is found. +func FindRouteTableByID(conn *ec2.EC2, routeTableID string) (*ec2.RouteTable, error) { + input := &ec2.DescribeRouteTablesInput{ + RouteTableIds: aws.StringSlice([]string{routeTableID}), + } + + return FindRouteTable(conn, input) +} + +func FindRouteTable(conn *ec2.EC2, input *ec2.DescribeRouteTablesInput) (*ec2.RouteTable, error) { + output, err := FindRouteTables(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindRouteTables(conn *ec2.EC2, input *ec2.DescribeRouteTablesInput) ([]*ec2.RouteTable, error) { + var output []*ec2.RouteTable + + err := conn.DescribeRouteTablesPages(input, func(page *ec2.DescribeRouteTablesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, table := range page.RouteTables { + if table == nil { + continue + } + + output = append(output, table) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidRouteTableIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +// RouteFinder returns the route corresponding to the specified destination. +// Returns NotFoundError if no route is found. +type RouteFinder func(*ec2.EC2, string, string) (*ec2.Route, error) + +// FindRouteByIPv4Destination returns the route corresponding to the specified IPv4 destination. +// Returns NotFoundError if no route is found. +func FindRouteByIPv4Destination(conn *ec2.EC2, routeTableID, destinationCidr string) (*ec2.Route, error) { + routeTable, err := FindRouteTableByID(conn, routeTableID) + + if err != nil { + return nil, err + } + + for _, route := range routeTable.Routes { + if verify.CIDRBlocksEqual(aws.StringValue(route.DestinationCidrBlock), destinationCidr) { + return route, nil + } + } + + return nil, &resource.NotFoundError{ + LastError: fmt.Errorf("Route in Route Table (%s) with IPv4 destination (%s) not found", routeTableID, destinationCidr), + } +} + +// FindRouteByIPv6Destination returns the route corresponding to the specified IPv6 destination. +// Returns NotFoundError if no route is found. +func FindRouteByIPv6Destination(conn *ec2.EC2, routeTableID, destinationIpv6Cidr string) (*ec2.Route, error) { + routeTable, err := FindRouteTableByID(conn, routeTableID) + + if err != nil { + return nil, err + } + + for _, route := range routeTable.Routes { + if verify.CIDRBlocksEqual(aws.StringValue(route.DestinationIpv6CidrBlock), destinationIpv6Cidr) { + return route, nil + } + } + + return nil, &resource.NotFoundError{ + LastError: fmt.Errorf("Route in Route Table (%s) with IPv6 destination (%s) not found", routeTableID, destinationIpv6Cidr), + } +} + +// FindRouteByPrefixListIDDestination returns the route corresponding to the specified prefix list destination. +// Returns NotFoundError if no route is found. +func FindRouteByPrefixListIDDestination(conn *ec2.EC2, routeTableID, prefixListID string) (*ec2.Route, error) { + routeTable, err := FindRouteTableByID(conn, routeTableID) + if err != nil { + return nil, err + } + + for _, route := range routeTable.Routes { + if aws.StringValue(route.DestinationPrefixListId) == prefixListID { + return route, nil + } + } + + return nil, &resource.NotFoundError{ + LastError: fmt.Errorf("Route in Route Table (%s) with Prefix List ID destination (%s) not found", routeTableID, prefixListID), + } +} + +func FindSecurityGroupByID(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { + input := &ec2.DescribeSecurityGroupsInput{ + GroupIds: aws.StringSlice([]string{id}), + } + + output, err := FindSecurityGroup(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.GroupId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +// FindSecurityGroupByNameAndVPCID looks up a security group by name and VPC ID. Returns a resource.NotFoundError if not found. +func FindSecurityGroupByNameAndVPCID(conn *ec2.EC2, name, vpcID string) (*ec2.SecurityGroup, error) { + input := &ec2.DescribeSecurityGroupsInput{ + Filters: BuildAttributeFilterList( + map[string]string{ + "group-name": name, + "vpc-id": vpcID, + }, + ), + } + return FindSecurityGroup(conn, input) +} + +// FindSecurityGroup looks up a security group using an ec2.DescribeSecurityGroupsInput. Returns a resource.NotFoundError if not found. +func FindSecurityGroup(conn *ec2.EC2, input *ec2.DescribeSecurityGroupsInput) (*ec2.SecurityGroup, error) { + output, err := FindSecurityGroups(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindSecurityGroups(conn *ec2.EC2, input *ec2.DescribeSecurityGroupsInput) ([]*ec2.SecurityGroup, error) { + var output []*ec2.SecurityGroup + + err := conn.DescribeSecurityGroupsPages(input, func(page *ec2.DescribeSecurityGroupsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.SecurityGroups { + if v == nil { + continue + } + + output = append(output, v) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidGroupNotFound, errCodeInvalidSecurityGroupIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindSpotFleetRequests(conn *ec2.EC2, input *ec2.DescribeSpotFleetRequestsInput) ([]*ec2.SpotFleetRequestConfig, error) { + var output []*ec2.SpotFleetRequestConfig + + err := conn.DescribeSpotFleetRequestsPages(input, func(page *ec2.DescribeSpotFleetRequestsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.SpotFleetRequestConfigs { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidSpotFleetRequestIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindSpotFleetRequest(conn *ec2.EC2, input *ec2.DescribeSpotFleetRequestsInput) (*ec2.SpotFleetRequestConfig, error) { + output, err := FindSpotFleetRequests(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].SpotFleetRequestConfig == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindSpotFleetRequestByID(conn *ec2.EC2, id string) (*ec2.SpotFleetRequestConfig, error) { + input := &ec2.DescribeSpotFleetRequestsInput{ + SpotFleetRequestIds: aws.StringSlice([]string{id}), + } + + output, err := FindSpotFleetRequest(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.SpotFleetRequestState); state == ec2.BatchStateCancelled || state == ec2.BatchStateCancelledRunning || state == ec2.BatchStateCancelledTerminating { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.SpotFleetRequestId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindSpotInstanceRequests(conn *ec2.EC2, input *ec2.DescribeSpotInstanceRequestsInput) ([]*ec2.SpotInstanceRequest, error) { + var output []*ec2.SpotInstanceRequest + + err := conn.DescribeSpotInstanceRequestsPages(input, func(page *ec2.DescribeSpotInstanceRequestsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.SpotInstanceRequests { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidSpotInstanceRequestIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindSpotInstanceRequest(conn *ec2.EC2, input *ec2.DescribeSpotInstanceRequestsInput) (*ec2.SpotInstanceRequest, error) { + output, err := FindSpotInstanceRequests(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].State == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindSpotInstanceRequestByID(conn *ec2.EC2, id string) (*ec2.SpotInstanceRequest, error) { + input := &ec2.DescribeSpotInstanceRequestsInput{ + SpotInstanceRequestIds: aws.StringSlice([]string{id}), + } + + output, err := FindSpotInstanceRequest(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.SpotInstanceStateCancelled || state == ec2.SpotInstanceStateClosed { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.SpotInstanceRequestId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindSubnetByID(conn *ec2.EC2, id string) (*ec2.Subnet, error) { + input := &ec2.DescribeSubnetsInput{ + SubnetIds: aws.StringSlice([]string{id}), + } + + output, err := FindSubnet(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.SubnetId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindSubnet(conn *ec2.EC2, input *ec2.DescribeSubnetsInput) (*ec2.Subnet, error) { + output, err := FindSubnets(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindSubnets(conn *ec2.EC2, input *ec2.DescribeSubnetsInput) ([]*ec2.Subnet, error) { + var output []*ec2.Subnet + + err := conn.DescribeSubnetsPages(input, func(page *ec2.DescribeSubnetsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Subnets { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidSubnetIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindSubnetCIDRReservationBySubnetIDAndReservationID(conn *ec2.EC2, subnetID, reservationID string) (*ec2.SubnetCidrReservation, error) { + input := &ec2.GetSubnetCidrReservationsInput{ + SubnetId: aws.String(subnetID), + } + + output, err := conn.GetSubnetCidrReservations(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidSubnetIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + } + } + + if err != nil { + return nil, err + } + + if output == nil || (len(output.SubnetIpv4CidrReservations) == 0 && len(output.SubnetIpv6CidrReservations) == 0) { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, r := range output.SubnetIpv4CidrReservations { + if aws.StringValue(r.SubnetCidrReservationId) == reservationID { + return r, nil + } + } + for _, r := range output.SubnetIpv6CidrReservations { + if aws.StringValue(r.SubnetCidrReservationId) == reservationID { + return r, nil + } + } + + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } +} + +func FindSubnetIPv6CIDRBlockAssociationByID(conn *ec2.EC2, associationID string) (*ec2.SubnetIpv6CidrBlockAssociation, error) { + input := &ec2.DescribeSubnetsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "ipv6-cidr-block-association.association-id": associationID, + }), + } + + output, err := FindSubnet(conn, input) + + if err != nil { + return nil, err + } + + for _, association := range output.Ipv6CidrBlockAssociationSet { + if aws.StringValue(association.AssociationId) == associationID { + if state := aws.StringValue(association.Ipv6CidrBlockState.State); state == ec2.SubnetCidrBlockStateCodeDisassociated { + return nil, &resource.NotFoundError{Message: state} + } + + return association, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindVolumeModifications(conn *ec2.EC2, input *ec2.DescribeVolumesModificationsInput) ([]*ec2.VolumeModification, error) { + var output []*ec2.VolumeModification + + err := conn.DescribeVolumesModificationsPages(input, func(page *ec2.DescribeVolumesModificationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.VolumesModifications { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVolumeNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVolumeModification(conn *ec2.EC2, input *ec2.DescribeVolumesModificationsInput) (*ec2.VolumeModification, error) { + output, err := FindVolumeModifications(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindVolumeModificationByID(conn *ec2.EC2, id string) (*ec2.VolumeModification, error) { + input := &ec2.DescribeVolumesModificationsInput{ + VolumeIds: aws.StringSlice([]string{id}), + } + + output, err := FindVolumeModification(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.VolumeId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPCAttribute(conn *ec2.EC2, vpcID string, attribute string) (bool, error) { + input := &ec2.DescribeVpcAttributeInput{ + Attribute: aws.String(attribute), + VpcId: aws.String(vpcID), + } + + output, err := conn.DescribeVpcAttribute(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCIDNotFound) { + return false, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return false, err + } + + if output == nil { + return false, tfresource.NewEmptyResultError(input) + } + + var v *ec2.AttributeBooleanValue + switch attribute { + case ec2.VpcAttributeNameEnableDnsHostnames: + v = output.EnableDnsHostnames + case ec2.VpcAttributeNameEnableDnsSupport: + v = output.EnableDnsSupport + default: + return false, fmt.Errorf("unsupported VPC attribute: %s", attribute) + } + + if v == nil { + return false, tfresource.NewEmptyResultError(input) + } + + return aws.BoolValue(v.Value), nil +} + +func FindVPCClassicLinkEnabled(conn *ec2.EC2, vpcID string) (bool, error) { + input := &ec2.DescribeVpcClassicLinkInput{ + VpcIds: aws.StringSlice([]string{vpcID}), + } + + output, err := conn.DescribeVpcClassicLink(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCIDNotFound, errCodeUnsupportedOperation) { + return false, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return false, err + } + + if output == nil || len(output.Vpcs) == 0 || output.Vpcs[0] == nil { + return false, tfresource.NewEmptyResultError(input) + } + + if count := len(output.Vpcs); count > 1 { + return false, tfresource.NewTooManyResultsError(count, input) + } + + vpc := output.Vpcs[0] + + // Eventual consistency check. + if aws.StringValue(vpc.VpcId) != vpcID { + return false, &resource.NotFoundError{ + LastRequest: input, + } + } + + return aws.BoolValue(vpc.ClassicLinkEnabled), nil +} + +func FindVPCClassicLinkDNSSupported(conn *ec2.EC2, vpcID string) (bool, error) { + input := &ec2.DescribeVpcClassicLinkDnsSupportInput{ + VpcIds: aws.StringSlice([]string{vpcID}), + } + + output, err := conn.DescribeVpcClassicLinkDnsSupport(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCIDNotFound, errCodeUnsupportedOperation) || + tfawserr.ErrMessageContains(err, errCodeAuthFailure, "This request has been administratively disabled") { + return false, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return false, err + } + + if output == nil || len(output.Vpcs) == 0 || output.Vpcs[0] == nil { + return false, tfresource.NewEmptyResultError(input) + } + + if count := len(output.Vpcs); count > 1 { + return false, tfresource.NewTooManyResultsError(count, input) + } + + vpc := output.Vpcs[0] + + // Eventual consistency check. + if aws.StringValue(vpc.VpcId) != vpcID { + return false, &resource.NotFoundError{ + LastRequest: input, + } + } + + return aws.BoolValue(vpc.ClassicLinkDnsSupported), nil +} + +func FindVPC(conn *ec2.EC2, input *ec2.DescribeVpcsInput) (*ec2.Vpc, error) { + output, err := FindVPCs(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindVPCs(conn *ec2.EC2, input *ec2.DescribeVpcsInput) ([]*ec2.Vpc, error) { + var output []*ec2.Vpc + + err := conn.DescribeVpcsPages(input, func(page *ec2.DescribeVpcsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Vpcs { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVPCByID(conn *ec2.EC2, id string) (*ec2.Vpc, error) { + input := &ec2.DescribeVpcsInput{ + VpcIds: aws.StringSlice([]string{id}), + } + + output, err := FindVPC(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.VpcId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPCDHCPOptionsAssociation(conn *ec2.EC2, vpcID string, dhcpOptionsID string) error { + vpc, err := FindVPCByID(conn, vpcID) + + if err != nil { + return err + } + + if aws.StringValue(vpc.DhcpOptionsId) != dhcpOptionsID { + return &resource.NotFoundError{ + LastError: fmt.Errorf("EC2 VPC (%s) DHCP Options Set (%s) Association not found", vpcID, dhcpOptionsID), + } + } + + return nil +} + +func FindVPCCIDRBlockAssociationByID(conn *ec2.EC2, id string) (*ec2.VpcCidrBlockAssociation, *ec2.Vpc, error) { + input := &ec2.DescribeVpcsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "cidr-block-association.association-id": id, + }), + } + + vpc, err := FindVPC(conn, input) + + if err != nil { + return nil, nil, err + } + + for _, association := range vpc.CidrBlockAssociationSet { + if aws.StringValue(association.AssociationId) == id { + if state := aws.StringValue(association.CidrBlockState.State); state == ec2.VpcCidrBlockStateCodeDisassociated { + return nil, nil, &resource.NotFoundError{Message: state} + } + + return association, vpc, nil + } + } + + return nil, nil, &resource.NotFoundError{} +} + +func FindVPCIPv6CIDRBlockAssociationByID(conn *ec2.EC2, id string) (*ec2.VpcIpv6CidrBlockAssociation, *ec2.Vpc, error) { + input := &ec2.DescribeVpcsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "ipv6-cidr-block-association.association-id": id, + }), + } + + vpc, err := FindVPC(conn, input) + + if err != nil { + return nil, nil, err + } + + for _, association := range vpc.Ipv6CidrBlockAssociationSet { + if aws.StringValue(association.AssociationId) == id { + if state := aws.StringValue(association.Ipv6CidrBlockState.State); state == ec2.VpcCidrBlockStateCodeDisassociated { + return nil, nil, &resource.NotFoundError{Message: state} + } + + return association, vpc, nil + } + } + + return nil, nil, &resource.NotFoundError{} +} + +func FindVPCDefaultNetworkACL(conn *ec2.EC2, id string) (*ec2.NetworkAcl, error) { + input := &ec2.DescribeNetworkAclsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "default": "true", + "vpc-id": id, + }), + } + + return FindNetworkACL(conn, input) +} + +func FindVPCDefaultSecurityGroup(conn *ec2.EC2, id string) (*ec2.SecurityGroup, error) { + input := &ec2.DescribeSecurityGroupsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "group-name": DefaultSecurityGroupName, + "vpc-id": id, + }), + } + + return FindSecurityGroup(conn, input) +} + +func FindVPCMainRouteTable(conn *ec2.EC2, id string) (*ec2.RouteTable, error) { + input := &ec2.DescribeRouteTablesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "association.main": "true", + "vpc-id": id, + }), + } + + return FindRouteTable(conn, input) +} + +func FindVPCEndpoint(conn *ec2.EC2, input *ec2.DescribeVpcEndpointsInput) (*ec2.VpcEndpoint, error) { + output, err := FindVPCEndpoints(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindVPCEndpoints(conn *ec2.EC2, input *ec2.DescribeVpcEndpointsInput) ([]*ec2.VpcEndpoint, error) { + var output []*ec2.VpcEndpoint + + err := conn.DescribeVpcEndpointsPages(input, func(page *ec2.DescribeVpcEndpointsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.VpcEndpoints { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVPCEndpointByID(conn *ec2.EC2, id string) (*ec2.VpcEndpoint, error) { + input := &ec2.DescribeVpcEndpointsInput{ + VpcEndpointIds: aws.StringSlice([]string{id}), + } + + output, err := FindVPCEndpoint(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == vpcEndpointStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.VpcEndpointId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPCEndpointServiceConfiguration(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServiceConfigurationsInput) (*ec2.ServiceConfiguration, error) { + output, err := FindVPCEndpointServiceConfigurations(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindVPCEndpointServiceConfigurations(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServiceConfigurationsInput) ([]*ec2.ServiceConfiguration, error) { + var output []*ec2.ServiceConfiguration + + err := conn.DescribeVpcEndpointServiceConfigurationsPages(input, func(page *ec2.DescribeVpcEndpointServiceConfigurationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.ServiceConfigurations { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVPCEndpointServiceConfigurationByServiceName(conn *ec2.EC2, name string) (*ec2.ServiceConfiguration, error) { + input := &ec2.DescribeVpcEndpointServiceConfigurationsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "service-name": name, + }), + } + + return FindVPCEndpointServiceConfiguration(conn, input) +} + +func FindVPCEndpointServiceConfigurationByID(conn *ec2.EC2, id string) (*ec2.ServiceConfiguration, error) { + input := &ec2.DescribeVpcEndpointServiceConfigurationsInput{ + ServiceIds: aws.StringSlice([]string{id}), + } + + output, err := FindVPCEndpointServiceConfiguration(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.ServiceState); state == ec2.ServiceStateDeleted || state == ec2.ServiceStateFailed { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.ServiceId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPCEndpointServicePermissions(conn *ec2.EC2, input *ec2.DescribeVpcEndpointServicePermissionsInput) ([]*ec2.AllowedPrincipal, error) { + var output []*ec2.AllowedPrincipal + + err := conn.DescribeVpcEndpointServicePermissionsPages(input, func(page *ec2.DescribeVpcEndpointServicePermissionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.AllowedPrincipals { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCEndpointServiceIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVPCEndpointServicePermissionsByID(conn *ec2.EC2, id string) ([]*ec2.AllowedPrincipal, error) { + input := &ec2.DescribeVpcEndpointServicePermissionsInput{ + ServiceId: aws.String(id), + } + + return FindVPCEndpointServicePermissions(conn, input) +} + +func FindVPCEndpointServicePermissionExists(conn *ec2.EC2, serviceID, principalARN string) error { + allowedPrincipals, err := FindVPCEndpointServicePermissionsByID(conn, serviceID) + + if err != nil { + return err + } + + for _, v := range allowedPrincipals { + if aws.StringValue(v.Principal) == principalARN { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("VPC Endpoint Service (%s) Principal (%s) not found", serviceID, principalARN), + } +} + +// FindVPCEndpointRouteTableAssociationExists returns NotFoundError if no association for the specified VPC endpoint and route table IDs is found. +func FindVPCEndpointRouteTableAssociationExists(conn *ec2.EC2, vpcEndpointID string, routeTableID string) error { + vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) + + if err != nil { + return err + } + + for _, vpcEndpointRouteTableID := range vpcEndpoint.RouteTableIds { + if aws.StringValue(vpcEndpointRouteTableID) == routeTableID { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("VPC Endpoint (%s) Route Table (%s) Association not found", vpcEndpointID, routeTableID), + } +} + +// FindVPCEndpointSecurityGroupAssociationExists returns NotFoundError if no association for the specified VPC endpoint and security group IDs is found. +func FindVPCEndpointSecurityGroupAssociationExists(conn *ec2.EC2, vpcEndpointID, securityGroupID string) error { + vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) + + if err != nil { + return err + } + + for _, group := range vpcEndpoint.Groups { + if aws.StringValue(group.GroupId) == securityGroupID { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("VPC Endpoint (%s) Security Group (%s) Association not found", vpcEndpointID, securityGroupID), + } +} + +// FindVPCEndpointSubnetAssociationExists returns NotFoundError if no association for the specified VPC endpoint and subnet IDs is found. +func FindVPCEndpointSubnetAssociationExists(conn *ec2.EC2, vpcEndpointID string, subnetID string) error { + vpcEndpoint, err := FindVPCEndpointByID(conn, vpcEndpointID) + + if err != nil { + return err + } + + for _, vpcEndpointSubnetID := range vpcEndpoint.SubnetIds { + if aws.StringValue(vpcEndpointSubnetID) == subnetID { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("VPC Endpoint (%s) Subnet (%s) Association not found", vpcEndpointID, subnetID), + } +} + +func FindVPCPeeringConnection(conn *ec2.EC2, input *ec2.DescribeVpcPeeringConnectionsInput) (*ec2.VpcPeeringConnection, error) { + output, err := FindVPCPeeringConnections(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Status == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindVPCPeeringConnections(conn *ec2.EC2, input *ec2.DescribeVpcPeeringConnectionsInput) ([]*ec2.VpcPeeringConnection, error) { + var output []*ec2.VpcPeeringConnection + + err := conn.DescribeVpcPeeringConnectionsPages(input, func(page *ec2.DescribeVpcPeeringConnectionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.VpcPeeringConnections { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPCPeeringConnectionIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindVPCPeeringConnectionByID(conn *ec2.EC2, id string) (*ec2.VpcPeeringConnection, error) { + input := &ec2.DescribeVpcPeeringConnectionsInput{ + VpcPeeringConnectionIds: aws.StringSlice([]string{id}), + } + + output, err := FindVPCPeeringConnection(conn, input) + + if err != nil { + return nil, err + } + + // See https://docs.aws.amazon.com/vpc/latest/peering/vpc-peering-basics.html#vpc-peering-lifecycle. + switch statusCode := aws.StringValue(output.Status.Code); statusCode { + case ec2.VpcPeeringConnectionStateReasonCodeDeleted, + ec2.VpcPeeringConnectionStateReasonCodeExpired, + ec2.VpcPeeringConnectionStateReasonCodeFailed, + ec2.VpcPeeringConnectionStateReasonCodeRejected: + return nil, &resource.NotFoundError{ + Message: statusCode, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.VpcPeeringConnectionId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +// FindVPNGatewayRoutePropagationExists returns NotFoundError if no route propagation for the specified VPN gateway is found. +func FindVPNGatewayRoutePropagationExists(conn *ec2.EC2, routeTableID, gatewayID string) error { + routeTable, err := FindRouteTableByID(conn, routeTableID) + + if err != nil { + return err + } + + for _, v := range routeTable.PropagatingVgws { + if aws.StringValue(v.GatewayId) == gatewayID { + return nil + } + } + + return &resource.NotFoundError{ + LastError: fmt.Errorf("Route Table (%s) VPN Gateway (%s) route propagation not found", routeTableID, gatewayID), + } +} + +func FindVPNGatewayVPCAttachment(conn *ec2.EC2, vpnGatewayID, vpcID string) (*ec2.VpcAttachment, error) { + vpnGateway, err := FindVPNGatewayByID(conn, vpnGatewayID) + + if err != nil { + return nil, err + } + + for _, vpcAttachment := range vpnGateway.VpcAttachments { + if aws.StringValue(vpcAttachment.VpcId) == vpcID { + if state := aws.StringValue(vpcAttachment.State); state == ec2.AttachmentStatusDetached { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: vpcID, + } + } + + return vpcAttachment, nil + } + } + + return nil, tfresource.NewEmptyResultError(vpcID) +} + +func FindVPNGatewayByID(conn *ec2.EC2, id string) (*ec2.VpnGateway, error) { + input := &ec2.DescribeVpnGatewaysInput{ + VpnGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindVPNGateway(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.VpnStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.VpnGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPNGateway(conn *ec2.EC2, input *ec2.DescribeVpnGatewaysInput) (*ec2.VpnGateway, error) { + output, err := conn.DescribeVpnGateways(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPNGatewayIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.VpnGateways) == 0 || output.VpnGateways[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.VpnGateways); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.VpnGateways[0], nil +} + +func FindCustomerGatewayByID(conn *ec2.EC2, id string) (*ec2.CustomerGateway, error) { + input := &ec2.DescribeCustomerGatewaysInput{ + CustomerGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindCustomerGateway(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == CustomerGatewayStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.CustomerGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindCustomerGateway(conn *ec2.EC2, input *ec2.DescribeCustomerGatewaysInput) (*ec2.CustomerGateway, error) { + output, err := conn.DescribeCustomerGateways(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidCustomerGatewayIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.CustomerGateways) == 0 || output.CustomerGateways[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.CustomerGateways); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.CustomerGateways[0], nil +} + +func FindVPNConnectionByID(conn *ec2.EC2, id string) (*ec2.VpnConnection, error) { + input := &ec2.DescribeVpnConnectionsInput{ + VpnConnectionIds: aws.StringSlice([]string{id}), + } + + output, err := FindVPNConnection(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.VpnStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.VpnConnectionId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindVPNConnection(conn *ec2.EC2, input *ec2.DescribeVpnConnectionsInput) (*ec2.VpnConnection, error) { + output, err := conn.DescribeVpnConnections(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidVPNConnectionIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.VpnConnections) == 0 || output.VpnConnections[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.VpnConnections); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.VpnConnections[0], nil +} + +func FindVPNConnectionRouteByVPNConnectionIDAndCIDR(conn *ec2.EC2, vpnConnectionID, cidrBlock string) (*ec2.VpnStaticRoute, error) { + input := &ec2.DescribeVpnConnectionsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "route.destination-cidr-block": cidrBlock, + "vpn-connection-id": vpnConnectionID, + }), + } + + output, err := FindVPNConnection(conn, input) + + if err != nil { + return nil, err + } + + for _, v := range output.Routes { + if aws.StringValue(v.DestinationCidrBlock) == cidrBlock && aws.StringValue(v.State) != ec2.VpnStateDeleted { + return v, nil + } + } + + return nil, &resource.NotFoundError{ + LastError: fmt.Errorf("EC2 VPN Connection (%s) Route (%s) not found", vpnConnectionID, cidrBlock), + } +} + +func FindTransitGateway(conn *ec2.EC2, input *ec2.DescribeTransitGatewaysInput) (*ec2.TransitGateway, error) { + output, err := FindTransitGateways(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Options == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGateways(conn *ec2.EC2, input *ec2.DescribeTransitGatewaysInput) ([]*ec2.TransitGateway, error) { + var output []*ec2.TransitGateway + + err := conn.DescribeTransitGatewaysPages(input, func(page *ec2.DescribeTransitGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGateways { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayByID(conn *ec2.EC2, id string) (*ec2.TransitGateway, error) { + input := &ec2.DescribeTransitGatewaysInput{ + TransitGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGateway(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.TransitGatewayStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayAttachment(conn *ec2.EC2, input *ec2.DescribeTransitGatewayAttachmentsInput) (*ec2.TransitGatewayAttachment, error) { + output, err := FindTransitGatewayAttachments(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayAttachments(conn *ec2.EC2, input *ec2.DescribeTransitGatewayAttachmentsInput) ([]*ec2.TransitGatewayAttachment, error) { + var output []*ec2.TransitGatewayAttachment + + err := conn.DescribeTransitGatewayAttachmentsPages(input, func(page *ec2.DescribeTransitGatewayAttachmentsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayAttachments { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayAttachmentIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayAttachmentByID(conn *ec2.EC2, id string) (*ec2.TransitGatewayAttachment, error) { + input := &ec2.DescribeTransitGatewayAttachmentsInput{ + TransitGatewayAttachmentIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGatewayAttachment(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayAttachmentId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayConnect(conn *ec2.EC2, input *ec2.DescribeTransitGatewayConnectsInput) (*ec2.TransitGatewayConnect, error) { + output, err := FindTransitGatewayConnects(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Options == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayConnects(conn *ec2.EC2, input *ec2.DescribeTransitGatewayConnectsInput) ([]*ec2.TransitGatewayConnect, error) { + var output []*ec2.TransitGatewayConnect + + err := conn.DescribeTransitGatewayConnectsPages(input, func(page *ec2.DescribeTransitGatewayConnectsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayConnects { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayAttachmentIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayConnectByID(conn *ec2.EC2, id string) (*ec2.TransitGatewayConnect, error) { + input := &ec2.DescribeTransitGatewayConnectsInput{ + TransitGatewayAttachmentIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGatewayConnect(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.TransitGatewayAttachmentStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayAttachmentId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayConnectPeer(conn *ec2.EC2, input *ec2.DescribeTransitGatewayConnectPeersInput) (*ec2.TransitGatewayConnectPeer, error) { + output, err := FindTransitGatewayConnectPeers(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].ConnectPeerConfiguration == nil || + len(output[0].ConnectPeerConfiguration.BgpConfigurations) == 0 || output[0].ConnectPeerConfiguration.BgpConfigurations[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayConnectPeers(conn *ec2.EC2, input *ec2.DescribeTransitGatewayConnectPeersInput) ([]*ec2.TransitGatewayConnectPeer, error) { + var output []*ec2.TransitGatewayConnectPeer + + err := conn.DescribeTransitGatewayConnectPeersPages(input, func(page *ec2.DescribeTransitGatewayConnectPeersOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayConnectPeers { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayConnectPeerIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayConnectPeerByID(conn *ec2.EC2, id string) (*ec2.TransitGatewayConnectPeer, error) { + input := &ec2.DescribeTransitGatewayConnectPeersInput{ + TransitGatewayConnectPeerIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGatewayConnectPeer(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.TransitGatewayConnectPeerStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayConnectPeerId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayMulticastDomain(conn *ec2.EC2, input *ec2.DescribeTransitGatewayMulticastDomainsInput) (*ec2.TransitGatewayMulticastDomain, error) { + output, err := FindTransitGatewayMulticastDomains(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Options == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayMulticastDomains(conn *ec2.EC2, input *ec2.DescribeTransitGatewayMulticastDomainsInput) ([]*ec2.TransitGatewayMulticastDomain, error) { + var output []*ec2.TransitGatewayMulticastDomain + + err := conn.DescribeTransitGatewayMulticastDomainsPages(input, func(page *ec2.DescribeTransitGatewayMulticastDomainsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayMulticastDomains { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayMulticastDomainByID(conn *ec2.EC2, id string) (*ec2.TransitGatewayMulticastDomain, error) { + input := &ec2.DescribeTransitGatewayMulticastDomainsInput{ + TransitGatewayMulticastDomainIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGatewayMulticastDomain(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.TransitGatewayMulticastDomainStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayMulticastDomainId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayMulticastDomainAssociation(conn *ec2.EC2, input *ec2.GetTransitGatewayMulticastDomainAssociationsInput) (*ec2.TransitGatewayMulticastDomainAssociation, error) { + output, err := FindTransitGatewayMulticastDomainAssociations(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Subnet == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayMulticastDomainAssociations(conn *ec2.EC2, input *ec2.GetTransitGatewayMulticastDomainAssociationsInput) ([]*ec2.TransitGatewayMulticastDomainAssociation, error) { + var output []*ec2.TransitGatewayMulticastDomainAssociation + + err := conn.GetTransitGatewayMulticastDomainAssociationsPages(input, func(page *ec2.GetTransitGatewayMulticastDomainAssociationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.MulticastDomainAssociations { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayMulticastDomainAssociationByThreePartKey(conn *ec2.EC2, multicastDomainID, attachmentID, subnetID string) (*ec2.TransitGatewayMulticastDomainAssociation, error) { + input := &ec2.GetTransitGatewayMulticastDomainAssociationsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "subnet-id": subnetID, + "transit-gateway-attachment-id": attachmentID, + }), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + output, err := FindTransitGatewayMulticastDomainAssociation(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.Subnet.State); state == ec2.TransitGatewayMulitcastDomainAssociationStateDisassociated { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayAttachmentId) != attachmentID || aws.StringValue(output.Subnet.SubnetId) != subnetID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayMulticastGroups(conn *ec2.EC2, input *ec2.SearchTransitGatewayMulticastGroupsInput) ([]*ec2.TransitGatewayMulticastGroup, error) { + var output []*ec2.TransitGatewayMulticastGroup + + err := conn.SearchTransitGatewayMulticastGroupsPages(input, func(page *ec2.SearchTransitGatewayMulticastGroupsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.MulticastGroups { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayMulticastDomainIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayMulticastGroupMemberByThreePartKey(conn *ec2.EC2, multicastDomainID, groupIPAddress, eniID string) (*ec2.TransitGatewayMulticastGroup, error) { + input := &ec2.SearchTransitGatewayMulticastGroupsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "group-ip-address": groupIPAddress, + "is-group-member": "true", + "is-group-source": "false", + }), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + output, err := FindTransitGatewayMulticastGroups(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, v := range output { + if aws.StringValue(v.NetworkInterfaceId) == eniID { + // Eventual consistency check. + if aws.StringValue(v.GroupIpAddress) != groupIPAddress || !aws.BoolValue(v.GroupMember) { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return v, nil + } + } + + return nil, tfresource.NewEmptyResultError(input) +} + +func FindTransitGatewayMulticastGroupSourceByThreePartKey(conn *ec2.EC2, multicastDomainID, groupIPAddress, eniID string) (*ec2.TransitGatewayMulticastGroup, error) { + input := &ec2.SearchTransitGatewayMulticastGroupsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "group-ip-address": groupIPAddress, + "is-group-member": "false", + "is-group-source": "true", + }), + TransitGatewayMulticastDomainId: aws.String(multicastDomainID), + } + + output, err := FindTransitGatewayMulticastGroups(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, v := range output { + if aws.StringValue(v.NetworkInterfaceId) == eniID { + // Eventual consistency check. + if aws.StringValue(v.GroupIpAddress) != groupIPAddress || !aws.BoolValue(v.GroupSource) { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return v, nil + } + } + + return nil, tfresource.NewEmptyResultError(input) +} + +func FindTransitGatewayPrefixListReference(conn *ec2.EC2, input *ec2.GetTransitGatewayPrefixListReferencesInput) (*ec2.TransitGatewayPrefixListReference, error) { + output, err := FindTransitGatewayPrefixListReferences(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayPrefixListReferences(conn *ec2.EC2, input *ec2.GetTransitGatewayPrefixListReferencesInput) ([]*ec2.TransitGatewayPrefixListReference, error) { + var output []*ec2.TransitGatewayPrefixListReference + + err := conn.GetTransitGatewayPrefixListReferencesPages(input, func(page *ec2.GetTransitGatewayPrefixListReferencesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayPrefixListReferences { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidRouteTableIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayPrefixListReferenceByTwoPartKey(conn *ec2.EC2, transitGatewayRouteTableID, prefixListID string) (*ec2.TransitGatewayPrefixListReference, error) { + input := &ec2.GetTransitGatewayPrefixListReferencesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "prefix-list-id": prefixListID, + }), + TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), + } + + output, err := FindTransitGatewayPrefixListReference(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.PrefixListId) != prefixListID || aws.StringValue(output.TransitGatewayRouteTableId) != transitGatewayRouteTableID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindTransitGatewayRoute(conn *ec2.EC2, transitGatewayRouteTableID, destination string) (*ec2.TransitGatewayRoute, error) { + input := &ec2.SearchTransitGatewayRoutesInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "type": ec2.TransitGatewayRouteTypeStatic, + }), + TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), + } + + output, err := conn.SearchTransitGatewayRoutes(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidRouteTableIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.Routes) == 0 { + return nil, tfresource.NewEmptyResultError(input) + } + + for _, route := range output.Routes { + if route == nil { + continue + } + + if v := aws.StringValue(route.DestinationCidrBlock); verify.CIDRBlocksEqual(v, destination) { + if state := aws.StringValue(route.State); state == ec2.TransitGatewayRouteStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + route.DestinationCidrBlock = aws.String(verify.CanonicalCIDRBlock(v)) + + return route, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindTransitGatewayRouteTables(conn *ec2.EC2, input *ec2.DescribeTransitGatewayRouteTablesInput) ([]*ec2.TransitGatewayRouteTable, error) { + var output []*ec2.TransitGatewayRouteTable + + err := conn.DescribeTransitGatewayRouteTablesPages(input, func(page *ec2.DescribeTransitGatewayRouteTablesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayRouteTables { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidRouteTableIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayRouteTablePropagation(conn *ec2.EC2, transitGatewayRouteTableID string, transitGatewayAttachmentID string) (*ec2.TransitGatewayRouteTablePropagation, error) { + if transitGatewayRouteTableID == "" { + return nil, nil + } + + input := &ec2.GetTransitGatewayRouteTablePropagationsInput{ + Filters: []*ec2.Filter{ + { + Name: aws.String("transit-gateway-attachment-id"), + Values: aws.StringSlice([]string{transitGatewayAttachmentID}), + }, + }, + TransitGatewayRouteTableId: aws.String(transitGatewayRouteTableID), + } + + var result *ec2.TransitGatewayRouteTablePropagation + + err := conn.GetTransitGatewayRouteTablePropagationsPages(input, func(page *ec2.GetTransitGatewayRouteTablePropagationsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, transitGatewayRouteTablePropagation := range page.TransitGatewayRouteTablePropagations { + if transitGatewayRouteTablePropagation == nil { + continue + } + + if aws.StringValue(transitGatewayRouteTablePropagation.TransitGatewayAttachmentId) == transitGatewayAttachmentID { + result = transitGatewayRouteTablePropagation + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return result, nil +} + +func FindTransitGatewayVPCAttachment(conn *ec2.EC2, input *ec2.DescribeTransitGatewayVpcAttachmentsInput) (*ec2.TransitGatewayVpcAttachment, error) { + output, err := FindTransitGatewayVPCAttachments(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].Options == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindTransitGatewayVPCAttachments(conn *ec2.EC2, input *ec2.DescribeTransitGatewayVpcAttachmentsInput) ([]*ec2.TransitGatewayVpcAttachment, error) { + var output []*ec2.TransitGatewayVpcAttachment + + err := conn.DescribeTransitGatewayVpcAttachmentsPages(input, func(page *ec2.DescribeTransitGatewayVpcAttachmentsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.TransitGatewayVpcAttachments { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidTransitGatewayAttachmentIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindTransitGatewayVPCAttachmentByID(conn *ec2.EC2, id string) (*ec2.TransitGatewayVpcAttachment, error) { + input := &ec2.DescribeTransitGatewayVpcAttachmentsInput{ + TransitGatewayAttachmentIds: aws.StringSlice([]string{id}), + } + + output, err := FindTransitGatewayVPCAttachment(conn, input) + + if err != nil { + return nil, err + } + + // See https://docs.aws.amazon.com/vpc/latest/tgw/tgw-vpc-attachments.html#vpc-attachment-lifecycle. + switch state := aws.StringValue(output.State); state { + case ec2.TransitGatewayAttachmentStateDeleted, + ec2.TransitGatewayAttachmentStateFailed, + ec2.TransitGatewayAttachmentStateRejected: + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + + } + + // Eventual consistency check. + if aws.StringValue(output.TransitGatewayAttachmentId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindDHCPOptions(conn *ec2.EC2, input *ec2.DescribeDhcpOptionsInput) (*ec2.DhcpOptions, error) { + output, err := FindDHCPOptionses(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindDHCPOptionses(conn *ec2.EC2, input *ec2.DescribeDhcpOptionsInput) ([]*ec2.DhcpOptions, error) { + var output []*ec2.DhcpOptions + + err := conn.DescribeDhcpOptionsPages(input, func(page *ec2.DescribeDhcpOptionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.DhcpOptions { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidDHCPOptionIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindDHCPOptionsByID(conn *ec2.EC2, id string) (*ec2.DhcpOptions, error) { + input := &ec2.DescribeDhcpOptionsInput{ + DhcpOptionsIds: aws.StringSlice([]string{id}), + } + + output, err := FindDHCPOptions(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.DhcpOptionsId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindEgressOnlyInternetGateway(conn *ec2.EC2, input *ec2.DescribeEgressOnlyInternetGatewaysInput) (*ec2.EgressOnlyInternetGateway, error) { + output, err := FindEgressOnlyInternetGateways(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindEgressOnlyInternetGateways(conn *ec2.EC2, input *ec2.DescribeEgressOnlyInternetGatewaysInput) ([]*ec2.EgressOnlyInternetGateway, error) { + var output []*ec2.EgressOnlyInternetGateway + + err := conn.DescribeEgressOnlyInternetGatewaysPages(input, func(page *ec2.DescribeEgressOnlyInternetGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.EgressOnlyInternetGateways { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindEgressOnlyInternetGatewayByID(conn *ec2.EC2, id string) (*ec2.EgressOnlyInternetGateway, error) { + input := &ec2.DescribeEgressOnlyInternetGatewaysInput{ + EgressOnlyInternetGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindEgressOnlyInternetGateway(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.EgressOnlyInternetGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindFleet(conn *ec2.EC2, input *ec2.DescribeFleetsInput) (*ec2.FleetData, error) { + output, err := FindFleets(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindFleets(conn *ec2.EC2, input *ec2.DescribeFleetsInput) ([]*ec2.FleetData, error) { + var output []*ec2.FleetData + + err := conn.DescribeFleetsPages(input, func(page *ec2.DescribeFleetsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Fleets { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidFleetIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindFleetByID(conn *ec2.EC2, id string) (*ec2.FleetData, error) { + input := &ec2.DescribeFleetsInput{ + FleetIds: aws.StringSlice([]string{id}), + } + + output, err := FindFleet(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.FleetState); state == ec2.FleetStateCodeDeleted || state == ec2.FleetStateCodeDeletedRunning || state == ec2.FleetStateCodeDeletedTerminating { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.FleetId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindFlowLogByID(conn *ec2.EC2, id string) (*ec2.FlowLog, error) { + input := &ec2.DescribeFlowLogsInput{ + FlowLogIds: aws.StringSlice([]string{id}), + } + + output, err := conn.DescribeFlowLogs(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.FlowLogs) == 0 || output.FlowLogs[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.FlowLogs); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.FlowLogs[0], nil +} + +func FindInternetGateway(conn *ec2.EC2, input *ec2.DescribeInternetGatewaysInput) (*ec2.InternetGateway, error) { + output, err := FindInternetGateways(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindInternetGateways(conn *ec2.EC2, input *ec2.DescribeInternetGatewaysInput) ([]*ec2.InternetGateway, error) { + var output []*ec2.InternetGateway + + err := conn.DescribeInternetGatewaysPages(input, func(page *ec2.DescribeInternetGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.InternetGateways { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidInternetGatewayIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindInternetGatewayByID(conn *ec2.EC2, id string) (*ec2.InternetGateway, error) { + input := &ec2.DescribeInternetGatewaysInput{ + InternetGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindInternetGateway(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.InternetGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindInternetGatewayAttachment(conn *ec2.EC2, internetGatewayID, vpcID string) (*ec2.InternetGatewayAttachment, error) { + internetGateway, err := FindInternetGatewayByID(conn, internetGatewayID) + + if err != nil { + return nil, err + } + + if len(internetGateway.Attachments) == 0 || internetGateway.Attachments[0] == nil { + return nil, tfresource.NewEmptyResultError(internetGatewayID) + } + + if count := len(internetGateway.Attachments); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, internetGatewayID) + } + + attachment := internetGateway.Attachments[0] + + if aws.StringValue(attachment.VpcId) != vpcID { + return nil, tfresource.NewEmptyResultError(vpcID) + } + + return attachment, nil +} + +func FindKeyPair(conn *ec2.EC2, input *ec2.DescribeKeyPairsInput) (*ec2.KeyPairInfo, error) { + output, err := FindKeyPairs(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindKeyPairs(conn *ec2.EC2, input *ec2.DescribeKeyPairsInput) ([]*ec2.KeyPairInfo, error) { + output, err := conn.DescribeKeyPairs(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidKeyPairNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output.KeyPairs, nil +} + +func FindKeyPairByName(conn *ec2.EC2, name string) (*ec2.KeyPairInfo, error) { + input := &ec2.DescribeKeyPairsInput{ + KeyNames: aws.StringSlice([]string{name}), + } + + output, err := FindKeyPair(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.KeyName) != name { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindLaunchTemplate(conn *ec2.EC2, input *ec2.DescribeLaunchTemplatesInput) (*ec2.LaunchTemplate, error) { + output, err := FindLaunchTemplates(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindLaunchTemplates(conn *ec2.EC2, input *ec2.DescribeLaunchTemplatesInput) ([]*ec2.LaunchTemplate, error) { + var output []*ec2.LaunchTemplate + + err := conn.DescribeLaunchTemplatesPages(input, func(page *ec2.DescribeLaunchTemplatesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.LaunchTemplates { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidLaunchTemplateIdMalformed, errCodeInvalidLaunchTemplateIdNotFound, errCodeInvalidLaunchTemplateNameNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLaunchTemplateByID(conn *ec2.EC2, id string) (*ec2.LaunchTemplate, error) { + input := &ec2.DescribeLaunchTemplatesInput{ + LaunchTemplateIds: aws.StringSlice([]string{id}), + } + + output, err := FindLaunchTemplate(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.LaunchTemplateId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindLaunchTemplateVersion(conn *ec2.EC2, input *ec2.DescribeLaunchTemplateVersionsInput) (*ec2.LaunchTemplateVersion, error) { + output, err := FindLaunchTemplateVersions(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].LaunchTemplateData == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindLaunchTemplateVersions(conn *ec2.EC2, input *ec2.DescribeLaunchTemplateVersionsInput) ([]*ec2.LaunchTemplateVersion, error) { + var output []*ec2.LaunchTemplateVersion + + err := conn.DescribeLaunchTemplateVersionsPages(input, func(page *ec2.DescribeLaunchTemplateVersionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.LaunchTemplateVersions { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidLaunchTemplateIdNotFound, errCodeInvalidLaunchTemplateNameNotFoundException, errCodeInvalidLaunchTemplateIdVersionNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindLaunchTemplateVersionByTwoPartKey(conn *ec2.EC2, launchTemplateID, version string) (*ec2.LaunchTemplateVersion, error) { + input := &ec2.DescribeLaunchTemplateVersionsInput{ + LaunchTemplateId: aws.String(launchTemplateID), + Versions: aws.StringSlice([]string{version}), + } + + output, err := FindLaunchTemplateVersion(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.LaunchTemplateId) != launchTemplateID { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindManagedPrefixListByID(conn *ec2.EC2, id string) (*ec2.ManagedPrefixList, error) { + input := &ec2.DescribeManagedPrefixListsInput{ + PrefixListIds: aws.StringSlice([]string{id}), + } + + output, err := conn.DescribeManagedPrefixLists(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidPrefixListIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.PrefixLists) == 0 || output.PrefixLists[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.PrefixLists); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + prefixList := output.PrefixLists[0] + + if state := aws.StringValue(prefixList.State); state == ec2.PrefixListStateDeleteComplete { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + return prefixList, nil +} + +func FindManagedPrefixListEntriesByID(conn *ec2.EC2, id string) ([]*ec2.PrefixListEntry, error) { + input := &ec2.GetManagedPrefixListEntriesInput{ + PrefixListId: aws.String(id), + } + + var prefixListEntries []*ec2.PrefixListEntry + + err := conn.GetManagedPrefixListEntriesPages(input, func(page *ec2.GetManagedPrefixListEntriesOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, entry := range page.Entries { + if entry == nil { + continue + } + + prefixListEntries = append(prefixListEntries, entry) + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidPrefixListIDNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return prefixListEntries, nil +} + +func FindManagedPrefixListEntryByIDAndCIDR(conn *ec2.EC2, id, cidr string) (*ec2.PrefixListEntry, error) { + prefixListEntries, err := FindManagedPrefixListEntriesByID(conn, id) + + if err != nil { + return nil, err + } + + for _, entry := range prefixListEntries { + if aws.StringValue(entry.Cidr) == cidr { + return entry, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func FindNATGateway(conn *ec2.EC2, input *ec2.DescribeNatGatewaysInput) (*ec2.NatGateway, error) { + output, err := FindNATGateways(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindNATGateways(conn *ec2.EC2, input *ec2.DescribeNatGatewaysInput) ([]*ec2.NatGateway, error) { + var output []*ec2.NatGateway + + err := conn.DescribeNatGatewaysPages(input, func(page *ec2.DescribeNatGatewaysOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.NatGateways { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeNatGatewayNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindNATGatewayByID(conn *ec2.EC2, id string) (*ec2.NatGateway, error) { + input := &ec2.DescribeNatGatewaysInput{ + NatGatewayIds: aws.StringSlice([]string{id}), + } + + output, err := FindNATGateway(conn, input) + + if err != nil { + return nil, err + } + + if state := aws.StringValue(output.State); state == ec2.NatGatewayStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + // Eventual consistency check. + if aws.StringValue(output.NatGatewayId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindPlacementGroupByName(conn *ec2.EC2, name string) (*ec2.PlacementGroup, error) { + input := &ec2.DescribePlacementGroupsInput{ + GroupNames: aws.StringSlice([]string{name}), + } + + output, err := conn.DescribePlacementGroups(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidPlacementGroupUnknown) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.PlacementGroups) == 0 || output.PlacementGroups[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.PlacementGroups); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + placementGroup := output.PlacementGroups[0] + + if state := aws.StringValue(placementGroup.State); state == ec2.PlacementGroupStateDeleted { + return nil, &resource.NotFoundError{ + Message: state, + LastRequest: input, + } + } + + return placementGroup, nil +} + +func FindPrefixList(conn *ec2.EC2, input *ec2.DescribePrefixListsInput) (*ec2.PrefixList, error) { + output, err := FindPrefixLists(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindPrefixLists(conn *ec2.EC2, input *ec2.DescribePrefixListsInput) ([]*ec2.PrefixList, error) { + var output []*ec2.PrefixList + + err := conn.DescribePrefixListsPages(input, func(page *ec2.DescribePrefixListsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.PrefixLists { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidPrefixListIdNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindPrefixListByName(conn *ec2.EC2, name string) (*ec2.PrefixList, error) { + input := &ec2.DescribePrefixListsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "prefix-list-name": name, + }), + } + + return FindPrefixList(conn, input) +} + +func FindVPCEndpointConnectionByServiceIDAndVPCEndpointID(conn *ec2.EC2, serviceID, vpcEndpointID string) (*ec2.VpcEndpointConnection, error) { + input := &ec2.DescribeVpcEndpointConnectionsInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "service-id": serviceID, + // "InvalidFilter: The filter vpc-endpoint-id is invalid" + // "vpc-endpoint-id ": vpcEndpointID, + }), + } + + var output *ec2.VpcEndpointConnection + + err := conn.DescribeVpcEndpointConnectionsPages(input, func(page *ec2.DescribeVpcEndpointConnectionsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.VpcEndpointConnections { + if aws.StringValue(v.VpcEndpointId) == vpcEndpointID { + output = v + + return false + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if vpcEndpointState := aws.StringValue(output.VpcEndpointState); vpcEndpointState == vpcEndpointStateDeleted { + return nil, &resource.NotFoundError{ + Message: vpcEndpointState, + LastRequest: input, + } + } + + return output, nil +} + +func FindImportSnapshotTasks(conn *ec2.EC2, input *ec2.DescribeImportSnapshotTasksInput) ([]*ec2.ImportSnapshotTask, error) { + var output []*ec2.ImportSnapshotTask + + err := conn.DescribeImportSnapshotTasksPages(input, func(page *ec2.DescribeImportSnapshotTasksOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.ImportSnapshotTasks { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrMessageContains(err, errCodeInvalidConversionTaskIdMalformed, "not found") { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindImportSnapshotTask(conn *ec2.EC2, input *ec2.DescribeImportSnapshotTasksInput) (*ec2.ImportSnapshotTask, error) { + output, err := FindImportSnapshotTasks(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil || output[0].SnapshotTaskDetail == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindImportSnapshotTaskByID(conn *ec2.EC2, id string) (*ec2.ImportSnapshotTask, error) { + input := &ec2.DescribeImportSnapshotTasksInput{ + ImportTaskIds: aws.StringSlice([]string{id}), + } + + output, err := FindImportSnapshotTask(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.ImportTaskId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindSnapshots(conn *ec2.EC2, input *ec2.DescribeSnapshotsInput) ([]*ec2.Snapshot, error) { + var output []*ec2.Snapshot + + err := conn.DescribeSnapshotsPages(input, func(page *ec2.DescribeSnapshotsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.Snapshots { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidSnapshotNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindSnapshot(conn *ec2.EC2, input *ec2.DescribeSnapshotsInput) (*ec2.Snapshot, error) { + output, err := FindSnapshots(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindSnapshotByID(conn *ec2.EC2, id string) (*ec2.Snapshot, error) { + input := &ec2.DescribeSnapshotsInput{ + SnapshotIds: aws.StringSlice([]string{id}), + } + + output, err := FindSnapshot(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.SnapshotId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output, nil +} + +func FindSnapshotAttribute(conn *ec2.EC2, input *ec2.DescribeSnapshotAttributeInput) (*ec2.DescribeSnapshotAttributeOutput, error) { + output, err := conn.DescribeSnapshotAttribute(input) + + if tfawserr.ErrCodeEquals(err, errCodeInvalidSnapshotNotFound) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + +func FindCreateSnapshotCreateVolumePermissionByTwoPartKey(conn *ec2.EC2, snapshotID, accountID string) (*ec2.CreateVolumePermission, error) { + input := &ec2.DescribeSnapshotAttributeInput{ + Attribute: aws.String(ec2.SnapshotAttributeNameCreateVolumePermission), + SnapshotId: aws.String(snapshotID), + } + + output, err := FindSnapshotAttribute(conn, input) + + if err != nil { + return nil, err + } + + for _, v := range output.CreateVolumePermissions { + if aws.StringValue(v.UserId) == accountID { + return v, nil + } + } + + return nil, &resource.NotFoundError{LastRequest: input} +} + +func FindFindSnapshotTierStatuses(conn *ec2.EC2, input *ec2.DescribeSnapshotTierStatusInput) ([]*ec2.SnapshotTierStatus, error) { + var output []*ec2.SnapshotTierStatus + + err := conn.DescribeSnapshotTierStatusPages(input, func(page *ec2.DescribeSnapshotTierStatusOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.SnapshotTierStatuses { + if v != nil { + output = append(output, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + return output, nil +} + +func FindFindSnapshotTierStatus(conn *ec2.EC2, input *ec2.DescribeSnapshotTierStatusInput) (*ec2.SnapshotTierStatus, error) { + output, err := FindFindSnapshotTierStatuses(conn, input) + + if err != nil { + return nil, err + } + + if len(output) == 0 || output[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output[0], nil +} + +func FindSnapshotTierStatusBySnapshotID(conn *ec2.EC2, id string) (*ec2.SnapshotTierStatus, error) { + input := &ec2.DescribeSnapshotTierStatusInput{ + Filters: BuildAttributeFilterList(map[string]string{ + "snapshot-id": id, + }), + } + + output, err := FindFindSnapshotTierStatus(conn, input) + + if err != nil { + return nil, err + } + + // Eventual consistency check. + if aws.StringValue(output.SnapshotId) != id { + return nil, &resource.NotFoundError{ + LastRequest: input, + } } - return nil, nil + return output, nil } diff --git a/internal/service/ec2/find_cloud_posse.go b/internal/service/ec2/find_cloud_posse.go new file mode 100644 index 0000000..34f83e6 --- /dev/null +++ b/internal/service/ec2/find_cloud_posse.go @@ -0,0 +1,100 @@ +package ec2 + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" +) + +// VpcDefault looks up the Default Vpc. When not found, returns nil and potentially an API error. +func FindInternetGatewayForVPC(conn *ec2.EC2, vpcID string) (*ec2.InternetGateway, error) { + filters := []*ec2.Filter{ + { + Name: aws.String("attachment.vpc-id"), + Values: []*string{aws.String(vpcID)}, + }, + } + + input := &ec2.DescribeInternetGatewaysInput{ + Filters: filters, + } + + output, err := conn.DescribeInternetGateways(input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, nil + } + + for _, ig := range output.InternetGateways { + if ig == nil { + continue + } + + return ig, nil + } + + return nil, nil +} + +// SubnetsForVPC looks up a the Subnets for a VPC. When not found, returns nil and potentially an API error. +func FindSubnetsForVPC(conn *ec2.EC2, vpcID string) ([]*ec2.Subnet, error) { + filters := []*ec2.Filter{ + { + Name: aws.String("vpc-id"), + Values: []*string{aws.String(vpcID)}, + }, + } + + input := &ec2.DescribeSubnetsInput{ + Filters: filters, + } + + output, err := conn.DescribeSubnets(input) + + if err != nil { + return nil, err + } + + if output == nil || len(output.Subnets) == 0 || output.Subnets[0] == nil { + return nil, nil + } + + return output.Subnets, nil +} + +// VpcDefault looks up the Default Vpc. When not found, returns nil and potentially an API error. +func FindDefaultVpc(conn *ec2.EC2) (*ec2.Vpc, error) { + filters := []*ec2.Filter{ + { + Name: aws.String("isDefault"), + Values: []*string{aws.String("true")}, + }, + } + + input := &ec2.DescribeVpcsInput{ + Filters: filters, + } + + output, err := conn.DescribeVpcs(input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, nil + } + + for _, vpc := range output.Vpcs { + if vpc == nil { + continue + } + + return vpc, nil + } + + return nil, nil +} diff --git a/internal/service/meta/arn.go b/internal/service/meta/arn.go new file mode 100644 index 0000000..d5b1647 --- /dev/null +++ b/internal/service/meta/arn.go @@ -0,0 +1,175 @@ +// TODO: Move this to a shared 'types' package. +package meta + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +type arnType uint8 + +const ( + ARNType arnType = iota +) + +var ( + _ xattr.TypeWithValidate = ARNType +) + +func (t arnType) TerraformType(_ context.Context) tftypes.Type { + return tftypes.String +} + +func (t arnType) ValueFromTerraform(_ context.Context, in tftypes.Value) (attr.Value, error) { + if !in.IsKnown() { + return ARN{Unknown: true}, nil + } + if in.IsNull() { + return ARN{Null: true}, nil + } + var s string + err := in.As(&s) + if err != nil { + return nil, err + } + a, err := arn.Parse(s) + if err != nil { + return nil, err + } + return ARN{Value: a}, nil +} + +// Equal returns true if `o` is also an ARNType. +func (t arnType) Equal(o attr.Type) bool { + _, ok := o.(arnType) + return ok +} + +// ApplyTerraform5AttributePathStep applies the given AttributePathStep to the +// type. +func (t arnType) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return nil, fmt.Errorf("cannot apply AttributePathStep %T to %s", step, t.String()) +} + +// String returns a human-friendly description of the ARNType. +func (t arnType) String() string { + return "types.ARNType" +} + +// Validate implements type validation. +func (t arnType) Validate(ctx context.Context, in tftypes.Value, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + if !in.Type().Is(tftypes.String) { + diags.AddAttributeError( + path, + "ARN Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Expected String value, received %T with value: %v", in, in), + ) + return diags + } + + if !in.IsKnown() || in.IsNull() { + return diags + } + + var value string + err := in.As(&value) + if err != nil { + diags.AddAttributeError( + path, + "ARN Type Validation Error", + "An unexpected error was encountered trying to validate an attribute value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Cannot convert value to arn.ARN: %s", err), + ) + return diags + } + + if !arn.IsARN(value) { + diags.AddAttributeError( + path, + "ARN Type Validation Error", + fmt.Sprintf("Value %q cannot be parsed as an ARN.", value), + ) + return diags + } + + return diags +} + +func (t arnType) Description() string { + return `An Amazon Resource Name.` +} + +type ARN struct { + Unknown bool + Null bool + Value arn.ARN +} + +func (a ARN) Type(_ context.Context) attr.Type { + return ARNType +} + +func (a ARN) ToTerraformValue(ctx context.Context) (tftypes.Value, error) { + t := ARNType.TerraformType(ctx) + if a.Null { + return tftypes.NewValue(t, nil), nil + } + if a.Unknown { + return tftypes.NewValue(t, tftypes.UnknownValue), nil + } + return tftypes.NewValue(t, a.Value.String()), nil +} + +// Equal returns true if `other` is a *ARN and has the same value as `a`. +func (a ARN) Equal(other attr.Value) bool { + o, ok := other.(ARN) + if !ok { + return false + } + if a.Unknown != o.Unknown { + return false + } + if a.Null != o.Null { + return false + } + return a.Value == o.Value +} + +// IsNull returns true if the Value is not set, or is explicitly set to null. +func (a ARN) IsNull() bool { + return a.Null +} + +// IsUnknown returns true if the Value is not yet known. +func (a ARN) IsUnknown() bool { + return a.Unknown +} + +// String returns a summary representation of either the underlying Value, +// or UnknownValueString (``) when IsUnknown() returns true, +// or NullValueString (``) when IsNull() return true. +// +// This is an intentionally lossy representation, that are best suited for +// logging and error reporting, as they are not protected by +// compatibility guarantees within the framework. +func (a ARN) String() string { + if a.IsUnknown() { + return attr.UnknownValueString + } + + if a.IsNull() { + return attr.NullValueString + } + + return a.Value.String() +} diff --git a/internal/service/meta/arn_data_source.go b/internal/service/meta/arn_data_source.go new file mode 100644 index 0000000..6a33e93 --- /dev/null +++ b/internal/service/meta/arn_data_source.go @@ -0,0 +1,60 @@ +package meta + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceARN() *schema.Resource { + return &schema.Resource{ + Read: dataSourceARNRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidARN, + }, + "partition": { + Type: schema.TypeString, + Computed: true, + }, + "service": { + Type: schema.TypeString, + Computed: true, + }, + "region": { + Type: schema.TypeString, + Computed: true, + }, + "account": { + Type: schema.TypeString, + Computed: true, + }, + "resource": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceARNRead(d *schema.ResourceData, meta interface{}) error { + v := d.Get("arn").(string) + arn, err := arn.Parse(v) + if err != nil { + return fmt.Errorf("Error parsing '%s': %w", v, err) + } + + d.SetId(arn.String()) + d.Set("partition", arn.Partition) + d.Set("service", arn.Service) + d.Set("region", arn.Region) + d.Set("account", arn.AccountID) + d.Set("resource", arn.Resource) + + return nil +} diff --git a/internal/service/meta/arn_data_source_fw.go b/internal/service/meta/arn_data_source_fw.go new file mode 100644 index 0000000..beebfa7 --- /dev/null +++ b/internal/service/meta/arn_data_source_fw.go @@ -0,0 +1,110 @@ +// Code generated by tools/tfsdk2fw/main.go. Manual editing is required. + +package meta + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// TODO: Remove +var NewDataSourceARNType = newDataSourceARNType + +func init() { + registerDataSourceTypeFactory("aws_arn", newDataSourceARNType) +} + +// newDataSourceARNType instantiates a new DataSourceType for the aws_arn data source. +func newDataSourceARNType(ctx context.Context) (tfsdk.DataSourceType, error) { + return &dataSourceARNType{}, nil +} + +type dataSourceARNType struct{} + +// GetSchema returns the schema for this data source. +func (t *dataSourceARNType) GetSchema(context.Context) (tfsdk.Schema, diag.Diagnostics) { + schema := tfsdk.Schema{ + Attributes: map[string]tfsdk.Attribute{ + "account": { + Type: types.StringType, + Computed: true, + }, + "arn": { + Type: ARNType, + Required: true, + }, + "id": { + Type: types.StringType, + Optional: true, + Computed: true, + }, + "partition": { + Type: types.StringType, + Computed: true, + }, + "region": { + Type: types.StringType, + Computed: true, + }, + "resource": { + Type: types.StringType, + Computed: true, + }, + "service": { + Type: types.StringType, + Computed: true, + }, + }, + } + + return schema, nil +} + +// NewDataSource instantiates a new DataSource of this DataSourceType. +func (t *dataSourceARNType) NewDataSource(ctx context.Context, provider tfsdk.Provider) (tfsdk.DataSource, diag.Diagnostics) { + return &dataSourceARN{}, nil +} + +type dataSourceARN struct{} + +// Read is called when the provider must read data source values in order to update state. +// Config values should be read from the ReadDataSourceRequest and new state values set on the ReadDataSourceResponse. +func (d *dataSourceARN) Read(ctx context.Context, request tfsdk.ReadDataSourceRequest, response *tfsdk.ReadDataSourceResponse) { + tflog.Trace(ctx, "dataSourceARN.Read enter") + + var config dataSourceARNData + + response.Diagnostics.Append(request.Config.Get(ctx, &config)...) + + if response.Diagnostics.HasError() { + return + } + + state := config + arn := &state.ARN.Value + id := arn.String() + + state.Account = &arn.AccountID + state.ID = &id + state.Partition = &arn.Partition + state.Region = &arn.Region + state.Resource = &arn.Resource + state.Service = &arn.Service + + response.Diagnostics.Append(response.State.Set(ctx, &state)...) +} + +// TODO: Generate this structure definition. +type dataSourceARNData struct { + Account *string `tfsdk:"account"` + ARN ARN `tfsdk:"arn"` + ID *string `tfsdk:"id"` + Partition *string `tfsdk:"partition"` + Region *string `tfsdk:"region"` + Resource *string `tfsdk:"resource"` + Service *string `tfsdk:"service"` +} diff --git a/internal/service/meta/arn_test.go b/internal/service/meta/arn_test.go new file mode 100644 index 0000000..54e3e96 --- /dev/null +++ b/internal/service/meta/arn_test.go @@ -0,0 +1,111 @@ +// TODO: Move this to a shared 'types' package. +package meta_test + +import ( + "context" + "testing" + + "github.com/aws/aws-sdk-go-v2/aws/arn" + "github.com/cloudposse/terraform-provider-awsutils/internal/service/meta" + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestARNTypeValueFromTerraform(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + val tftypes.Value + expected attr.Value + expectError bool + }{ + "null value": { + val: tftypes.NewValue(tftypes.String, nil), + expected: meta.ARN{Null: true}, + }, + "unknown value": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + expected: meta.ARN{Unknown: true}, + }, + "valid ARN": { + val: tftypes.NewValue(tftypes.String, "arn:aws:rds:us-east-1:123456789012:db:test"), // lintignore:AWSAT003,AWSAT005 + expected: meta.ARN{Value: arn.ARN{ + Partition: "aws", + Service: "rds", + Region: "us-east-1", // lintignore:AWSAT003 + AccountID: "123456789012", + Resource: "db:test", + }}, + }, + "invalid duration": { + val: tftypes.NewValue(tftypes.String, "not ok"), + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + val, err := meta.ARNType.ValueFromTerraform(ctx, test.val) + + if err == nil && test.expectError { + t.Fatal("expected error, got no error") + } + if err != nil && !test.expectError { + t.Fatalf("got unexpected error: %s", err) + } + + if diff := cmp.Diff(val, test.expected); diff != "" { + t.Errorf("unexpected diff (+wanted, -got): %s", diff) + } + }) + } +} + +func TestARNTypeValidate(t *testing.T) { + t.Parallel() + + type testCase struct { + val tftypes.Value + expectError bool + } + tests := map[string]testCase{ + "not a string": { + val: tftypes.NewValue(tftypes.Bool, true), + expectError: true, + }, + "unknown string": { + val: tftypes.NewValue(tftypes.String, tftypes.UnknownValue), + }, + "null string": { + val: tftypes.NewValue(tftypes.String, nil), + }, + "valid string": { + val: tftypes.NewValue(tftypes.String, "arn:aws:rds:us-east-1:123456789012:db:test"), // lintignore:AWSAT003,AWSAT005 + }, + "invalid string": { + val: tftypes.NewValue(tftypes.String, "not ok"), + expectError: true, + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + ctx := context.TODO() + + diags := meta.ARNType.Validate(ctx, test.val, path.Root("test")) + + if !diags.HasError() && test.expectError { + t.Fatal("expected error, got no error") + } + + if diags.HasError() && !test.expectError { + t.Fatalf("got unexpected error: %#v", diags) + } + }) + } +} diff --git a/internal/service/meta/billing_service_account_data_source.go b/internal/service/meta/billing_service_account_data_source.go new file mode 100644 index 0000000..fabd921 --- /dev/null +++ b/internal/service/meta/billing_service_account_data_source.go @@ -0,0 +1,36 @@ +package meta + +import ( + "github.com/aws/aws-sdk-go/aws/arn" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// See http://docs.aws.amazon.com/awsaccountbilling/latest/aboutv2/billing-getting-started.html#step-2 +var billingAccountId = "386209384616" + +func DataSourceBillingServiceAccount() *schema.Resource { + return &schema.Resource{ + Read: dataSourceBillingServiceAccountRead, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceBillingServiceAccountRead(d *schema.ResourceData, meta interface{}) error { + d.SetId(billingAccountId) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "iam", + AccountID: billingAccountId, + Resource: "root", + }.String() + d.Set("arn", arn) + + return nil +} diff --git a/internal/service/meta/consts.go b/internal/service/meta/consts.go new file mode 100644 index 0000000..ddc8e7a --- /dev/null +++ b/internal/service/meta/consts.go @@ -0,0 +1,5 @@ +package meta + +const ( + PseudoServiceID = "meta" +) diff --git a/internal/service/meta/default_tags_data_source.go b/internal/service/meta/default_tags_data_source.go new file mode 100644 index 0000000..2c47027 --- /dev/null +++ b/internal/service/meta/default_tags_data_source.go @@ -0,0 +1,38 @@ +package meta + +import ( + "fmt" + + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceDefaultTags() *schema.Resource { + return &schema.Resource{ + Read: dataSourceDefaultTagsRead, + + Schema: map[string]*schema.Schema{ + "tags": tftags.TagsSchemaComputed(), + }, + } +} + +func dataSourceDefaultTagsRead(d *schema.ResourceData, meta interface{}) error { + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + d.SetId(meta.(*conns.AWSClient).Partition) + + tags := defaultTagsConfig.GetTags() + + if tags != nil { + if err := d.Set("tags", tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + } else { + d.Set("tags", nil) + } + + return nil +} diff --git a/internal/service/meta/ip_ranges_data_source.go b/internal/service/meta/ip_ranges_data_source.go new file mode 100644 index 0000000..2ad1497 --- /dev/null +++ b/internal/service/meta/ip_ranges_data_source.go @@ -0,0 +1,177 @@ +package meta + +import ( + "encoding/json" + "fmt" + "io" + "log" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type dataSourceAwsIPRangesResult struct { + CreateDate string + Prefixes []dataSourceAwsIPRangesPrefix + Ipv6Prefixes []dataSourceAwsIPRangesIpv6Prefix `json:"ipv6_prefixes"` + SyncToken string +} + +type dataSourceAwsIPRangesPrefix struct { + IpPrefix string `json:"ip_prefix"` + Region string + Service string +} + +type dataSourceAwsIPRangesIpv6Prefix struct { + Ipv6Prefix string `json:"ipv6_prefix"` + Region string + Service string +} + +func DataSourceIPRanges() *schema.Resource { + return &schema.Resource{ + Read: dataSourceIPRangesRead, + + Schema: map[string]*schema.Schema{ + "cidr_blocks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "create_date": { + Type: schema.TypeString, + Computed: true, + }, + "ipv6_cidr_blocks": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "regions": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "services": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "sync_token": { + Type: schema.TypeInt, + Computed: true, + }, + "url": { + Type: schema.TypeString, + Optional: true, + Default: "https://ip-ranges.amazonaws.com/ip-ranges.json", + }, + }, + } +} + +func dataSourceIPRangesRead(d *schema.ResourceData, meta interface{}) error { + + conn := cleanhttp.DefaultClient() + url := d.Get("url").(string) + + log.Printf("[DEBUG] Reading IP ranges from %s", url) + + res, err := conn.Get(url) + + if err != nil { + return fmt.Errorf("Error listing IP ranges from (%s): %w", url, err) + } + + defer res.Body.Close() + + data, err := io.ReadAll(res.Body) + + if err != nil { + return fmt.Errorf("Error reading response body from (%s): %w", url, err) + } + + result := new(dataSourceAwsIPRangesResult) + + if err := json.Unmarshal(data, result); err != nil { + return fmt.Errorf("Error parsing result from (%s): %w", url, err) + } + + if err := d.Set("create_date", result.CreateDate); err != nil { + return fmt.Errorf("Error setting create date: %w", err) + } + + syncToken, err := strconv.Atoi(result.SyncToken) + + if err != nil { + return fmt.Errorf("Error while converting sync token: %w", err) + } + + d.SetId(result.SyncToken) + + if err := d.Set("sync_token", syncToken); err != nil { + return fmt.Errorf("Error setting sync token: %w", err) + } + + get := func(key string) *schema.Set { + + set := d.Get(key).(*schema.Set) + + for _, e := range set.List() { + + s := e.(string) + + set.Remove(s) + set.Add(strings.ToLower(s)) + + } + + return set + + } + + var ( + regions = get("regions") + services = get("services") + noRegionFilter = regions.Len() == 0 + ipPrefixes []string + ipv6Prefixes []string + ) + + matchFilter := func(region, service string) bool { + matchRegion := noRegionFilter || regions.Contains(strings.ToLower(region)) + matchService := services.Contains(strings.ToLower(service)) + return matchRegion && matchService + } + + for _, e := range result.Prefixes { + if matchFilter(e.Region, e.Service) { + ipPrefixes = append(ipPrefixes, e.IpPrefix) + } + } + + for _, e := range result.Ipv6Prefixes { + if matchFilter(e.Region, e.Service) { + ipv6Prefixes = append(ipv6Prefixes, e.Ipv6Prefix) + } + } + + sort.Strings(ipPrefixes) + + if err := d.Set("cidr_blocks", ipPrefixes); err != nil { + return fmt.Errorf("Error setting cidr_blocks: %w", err) + } + + sort.Strings(ipv6Prefixes) + + if err := d.Set("ipv6_cidr_blocks", ipv6Prefixes); err != nil { + return fmt.Errorf("Error setting ipv6_cidr_blocks: %w", err) + } + + return nil + +} diff --git a/internal/service/meta/partition_data_source.go b/internal/service/meta/partition_data_source.go new file mode 100644 index 0000000..b93bbf4 --- /dev/null +++ b/internal/service/meta/partition_data_source.go @@ -0,0 +1,49 @@ +package meta + +import ( + "log" + + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourcePartition() *schema.Resource { + return &schema.Resource{ + Read: dataSourcePartitionRead, + + Schema: map[string]*schema.Schema{ + "partition": { + Type: schema.TypeString, + Computed: true, + }, + + "dns_suffix": { + Type: schema.TypeString, + Computed: true, + }, + + "reverse_dns_prefix": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourcePartitionRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*conns.AWSClient) + + log.Printf("[DEBUG] Reading Partition.") + d.SetId(meta.(*conns.AWSClient).Partition) + + log.Printf("[DEBUG] Setting AWS Partition to %s.", client.Partition) + d.Set("partition", meta.(*conns.AWSClient).Partition) + + log.Printf("[DEBUG] Setting AWS URL Suffix to %s.", client.DNSSuffix) + d.Set("dns_suffix", meta.(*conns.AWSClient).DNSSuffix) + + d.Set("reverse_dns_prefix", meta.(*conns.AWSClient).ReverseDNSPrefix) + log.Printf("[DEBUG] Setting service prefix to %s.", meta.(*conns.AWSClient).ReverseDNSPrefix) + + return nil +} diff --git a/internal/service/meta/region_data_source.go b/internal/service/meta/region_data_source.go new file mode 100644 index 0000000..ae2cee8 --- /dev/null +++ b/internal/service/meta/region_data_source.go @@ -0,0 +1,111 @@ +package meta + +import ( + "fmt" + "strings" + + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceRegion() *schema.Resource { + return &schema.Resource{ + Read: dataSourceRegionRead, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "endpoint": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + + "description": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceRegionRead(d *schema.ResourceData, meta interface{}) error { + providerRegion := meta.(*conns.AWSClient).Region + + var region *endpoints.Region + + if v, ok := d.GetOk("endpoint"); ok { + endpoint := v.(string) + matchingRegion, err := FindRegionByEndpoint(endpoint) + if err != nil { + return err + } + region = matchingRegion + } + + if v, ok := d.GetOk("name"); ok { + name := v.(string) + matchingRegion, err := FindRegionByName(name) + if err != nil { + return err + } + if region != nil && region.ID() != matchingRegion.ID() { + return fmt.Errorf("multiple regions matched; use additional constraints to reduce matches to a single region") + } + region = matchingRegion + } + + // Default to provider current region if no other filters matched + if region == nil { + matchingRegion, err := FindRegionByName(providerRegion) + if err != nil { + return err + } + region = matchingRegion + } + + d.SetId(region.ID()) + + regionEndpointEc2, err := region.ResolveEndpoint(endpoints.Ec2ServiceID) + if err != nil { + return err + } + d.Set("endpoint", strings.TrimPrefix(regionEndpointEc2.URL, "https://")) + + d.Set("name", region.ID()) + + d.Set("description", region.Description()) + + return nil +} + +func FindRegionByEndpoint(endpoint string) (*endpoints.Region, error) { + for _, partition := range endpoints.DefaultPartitions() { + for _, region := range partition.Regions() { + regionEndpointEc2, err := region.ResolveEndpoint(endpoints.Ec2ServiceID) + if err != nil { + return nil, err + } + if strings.TrimPrefix(regionEndpointEc2.URL, "https://") == endpoint { + return ®ion, nil + } + } + } + return nil, fmt.Errorf("region not found for endpoint %q", endpoint) +} + +func FindRegionByName(name string) (*endpoints.Region, error) { + for _, partition := range endpoints.DefaultPartitions() { + for _, region := range partition.Regions() { + if region.ID() == name { + return ®ion, nil + } + } + } + return nil, fmt.Errorf("region not found for name %q", name) +} diff --git a/internal/service/meta/regions_data_source.go b/internal/service/meta/regions_data_source.go new file mode 100644 index 0000000..083ea5a --- /dev/null +++ b/internal/service/meta/regions_data_source.go @@ -0,0 +1,63 @@ +package meta + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/ec2" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + tfec2 "github.com/cloudposse/terraform-provider-awsutils/internal/service/ec2" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceRegions() *schema.Resource { + return &schema.Resource{ + Read: dataSourceRegionsRead, + + Schema: map[string]*schema.Schema{ + "filter": tfec2.DataSourceFiltersSchema(), + "names": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + + "all_regions": { + Type: schema.TypeBool, + Optional: true, + }, + }, + } +} + +func dataSourceRegionsRead(d *schema.ResourceData, meta interface{}) error { + connection := meta.(*conns.AWSClient).EC2Conn + + log.Printf("[DEBUG] Reading regions.") + request := &ec2.DescribeRegionsInput{} + if v, ok := d.GetOk("filter"); ok { + request.Filters = tfec2.BuildFiltersDataSource(v.(*schema.Set)) + } + if v, ok := d.GetOk("all_regions"); ok { + request.AllRegions = aws.Bool(v.(bool)) + } + + log.Printf("[DEBUG] Reading regions for request: %s", request) + response, err := connection.DescribeRegions(request) + if err != nil { + return fmt.Errorf("Error fetching Regions: %w", err) + } + + names := []string{} + for _, v := range response.Regions { + names = append(names, aws.StringValue(v.RegionName)) + } + + d.SetId(meta.(*conns.AWSClient).Partition) + if err := d.Set("names", names); err != nil { + return fmt.Errorf("error setting names: %w", err) + } + + return nil +} diff --git a/internal/service/meta/service_data_source.go b/internal/service/meta/service_data_source.go new file mode 100644 index 0000000..807609f --- /dev/null +++ b/internal/service/meta/service_data_source.go @@ -0,0 +1,121 @@ +package meta + +import ( + "fmt" + "strings" + + "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceService() *schema.Resource { + return &schema.Resource{ + Read: dataSourceServiceRead, + + Schema: map[string]*schema.Schema{ + "dns_name": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ExactlyOneOf: []string{"dns_name", "reverse_dns_name", "service_id"}, + }, + "partition": { + Type: schema.TypeString, + Computed: true, + }, + "region": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ConflictsWith: []string{"dns_name", "reverse_dns_name"}, + }, + "reverse_dns_name": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ExactlyOneOf: []string{"dns_name", "reverse_dns_name", "service_id"}, + }, + "reverse_dns_prefix": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ConflictsWith: []string{"dns_name", "reverse_dns_name"}, + }, + "service_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ExactlyOneOf: []string{"dns_name", "reverse_dns_name", "service_id"}, + }, + "supported": { + Type: schema.TypeBool, + Computed: true, + }, + }, + } +} + +func dataSourceServiceRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*conns.AWSClient) + + if v, ok := d.GetOk("reverse_dns_name"); ok { + serviceParts := strings.Split(v.(string), ".") + if len(serviceParts) < 4 { + return fmt.Errorf("reverse service DNS names must have at least 4 parts (%s has %d)", v.(string), len(serviceParts)) + } + + d.Set("service_id", serviceParts[len(serviceParts)-1]) + d.Set("region", serviceParts[len(serviceParts)-2]) + d.Set("reverse_dns_prefix", strings.Join(serviceParts[0:len(serviceParts)-2], ".")) + } + + if v, ok := d.GetOk("dns_name"); ok { + serviceParts := InvertStringSlice(strings.Split(v.(string), ".")) + if len(serviceParts) < 4 { + return fmt.Errorf("service DNS names must have at least 4 parts (%s has %d)", v.(string), len(serviceParts)) + } + + d.Set("service_id", serviceParts[len(serviceParts)-1]) + d.Set("region", serviceParts[len(serviceParts)-2]) + d.Set("reverse_dns_prefix", strings.Join(serviceParts[0:len(serviceParts)-2], ".")) + } + + if _, ok := d.GetOk("region"); !ok { + d.Set("region", client.Region) + } + + if _, ok := d.GetOk("service_id"); !ok { + return fmt.Errorf("service ID not provided directly or through a DNS name") + } + + if _, ok := d.GetOk("reverse_dns_prefix"); !ok { + dnsParts := strings.Split(meta.(*conns.AWSClient).DNSSuffix, ".") + d.Set("reverse_dns_prefix", strings.Join(InvertStringSlice(dnsParts), ".")) + } + + reverseDNS := fmt.Sprintf("%s.%s.%s", d.Get("reverse_dns_prefix").(string), d.Get("region").(string), d.Get("service_id").(string)) + d.Set("reverse_dns_name", reverseDNS) + d.Set("dns_name", strings.ToLower(strings.Join(InvertStringSlice(strings.Split(reverseDNS, ".")), "."))) + + d.Set("supported", true) + if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), d.Get("region").(string)); ok { + d.Set("partition", partition.ID()) + if _, ok := partition.Services()[d.Get("service_id").(string)]; !ok { + d.Set("supported", false) + } + } + + d.SetId(reverseDNS) + + return nil +} + +// invertStringSlice returns inverted string slice without sorting slice like sort.Reverse() +func InvertStringSlice(slice []string) []string { + inverse := make([]string, 0) + for i := 0; i < len(slice); i++ { + inverse = append(inverse, slice[len(slice)-i-1]) + } + return inverse +} diff --git a/internal/service/meta/svcimpl.go b/internal/service/meta/svcimpl.go new file mode 100644 index 0000000..82422e7 --- /dev/null +++ b/internal/service/meta/svcimpl.go @@ -0,0 +1,10 @@ +package meta + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +func registerDataSourceTypeFactory(name string, factory func(context.Context) (tfsdk.DataSourceType, error)) { +} diff --git a/internal/service/organizations/README.md b/internal/service/organizations/README.md new file mode 100644 index 0000000..9e8b2a5 --- /dev/null +++ b/internal/service/organizations/README.md @@ -0,0 +1,11 @@ +# Terraform AWS Provider Organizations Package + +This area is primarily for AWS provider contributors and maintainers. For information on _using_ Terraform and the AWS provider, see the links below. + + +## Handy Links + +* [Find out about contributing](../../../docs/contributing) to the AWS provider! +* AWS Provider Docs: [Home](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) +* AWS Provider Docs: [One of the Organizations resources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/organizations_account) +* AWS Docs: [AWS SDK for Go Organizations](https://docs.aws.amazon.com/sdk-for-go/api/service/organizations/) diff --git a/internal/service/organizations/account.go b/internal/service/organizations/account.go new file mode 100644 index 0000000..132400f --- /dev/null +++ b/internal/service/organizations/account.go @@ -0,0 +1,472 @@ +package organizations + +import ( + "errors" + "fmt" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" + "github.com/cloudposse/terraform-provider-awsutils/internal/tfresource" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceAccount() *schema.Resource { + return &schema.Resource{ + Create: resourceAccountCreate, + Read: resourceAccountRead, + Update: resourceAccountUpdate, + Delete: resourceAccountDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "close_on_deletion": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "create_govcloud": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "email": { + ForceNew: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.All( + validation.StringLenBetween(6, 64), + validation.StringMatch(regexp.MustCompile(`^[^\s@]+@[^\s@]+\.[^\s@]+$`), "must be a valid email address"), + ), + }, + "govcloud_id": { + Type: schema.TypeString, + Computed: true, + }, + "iam_user_access_to_billing": { + ForceNew: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{organizations.IAMUserAccessToBillingAllow, organizations.IAMUserAccessToBillingDeny}, true), + }, + "joined_method": { + Type: schema.TypeString, + Computed: true, + }, + "joined_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + ForceNew: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 50), + }, + "parent_id": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile("^(r-[0-9a-z]{4,32})|(ou-[0-9a-z]{4,32}-[a-z0-9]{8,32})$"), "see https://docs.aws.amazon.com/organizations/latest/APIReference/API_MoveAccount.html#organizations-MoveAccount-request-DestinationParentId"), + }, + "role_name": { + ForceNew: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile(`^[\w+=,.@-]{1,64}$`), "must consist of uppercase letters, lowercase letters, digits with no spaces, and any of the following characters"), + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceAccountCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + var iamUserAccessToBilling *string + + if v, ok := d.GetOk("iam_user_access_to_billing"); ok { + iamUserAccessToBilling = aws.String(v.(string)) + } + + var roleName *string + + if v, ok := d.GetOk("role_name"); ok { + roleName = aws.String(v.(string)) + } + + s, err := createAccount( + conn, + d.Get("name").(string), + d.Get("email").(string), + iamUserAccessToBilling, + roleName, + Tags(tags.IgnoreAWS()), + d.Get("create_govcloud").(bool), + ) + + if err != nil { + return fmt.Errorf("error creating AWS Organizations Account (%s): %w", d.Get("name").(string), err) + } + + output, err := waitAccountCreated(conn, aws.StringValue(s.Id)) + + if err != nil { + return fmt.Errorf("error waiting for AWS Organizations Account (%s) create: %w", d.Get("name").(string), err) + } + + d.SetId(aws.StringValue(output.AccountId)) + d.Set("govcloud_id", output.GovCloudAccountId) + + if v, ok := d.GetOk("parent_id"); ok { + oldParentAccountID, err := findParentAccountID(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error reading AWS Organizations Account (%s) parent: %w", d.Id(), err) + } + + if newParentAccountID := v.(string); newParentAccountID != oldParentAccountID { + input := &organizations.MoveAccountInput{ + AccountId: aws.String(d.Id()), + DestinationParentId: aws.String(newParentAccountID), + SourceParentId: aws.String(oldParentAccountID), + } + + log.Printf("[DEBUG] Moving AWS Organizations Account: %s", input) + if _, err := conn.MoveAccount(input); err != nil { + return fmt.Errorf("error moving AWS Organizations Account (%s): %w", d.Id(), err) + } + } + } + + return resourceAccountRead(d, meta) +} + +func resourceAccountRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + account, err := FindAccountByID(conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] AWS Organizations Account does not exist, removing from state: %s", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading AWS Organizations Account (%s): %w", d.Id(), err) + } + + parentAccountID, err := findParentAccountID(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error reading AWS Organizations Account (%s) parent: %w", d.Id(), err) + } + + d.Set("arn", account.Arn) + d.Set("email", account.Email) + d.Set("joined_method", account.JoinedMethod) + d.Set("joined_timestamp", aws.TimeValue(account.JoinedTimestamp).Format(time.RFC3339)) + d.Set("name", account.Name) + d.Set("parent_id", parentAccountID) + d.Set("status", account.Status) + + tags, err := ListTags(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error listing tags for AWS Organizations Account (%s): %w", d.Id(), err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceAccountUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + if d.HasChange("parent_id") { + o, n := d.GetChange("parent_id") + + input := &organizations.MoveAccountInput{ + AccountId: aws.String(d.Id()), + SourceParentId: aws.String(o.(string)), + DestinationParentId: aws.String(n.(string)), + } + + if _, err := conn.MoveAccount(input); err != nil { + return fmt.Errorf("error moving AWS Organizations Account (%s): %w", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Id(), o, n); err != nil { + return fmt.Errorf("error updating AWS Organizations Account (%s) tags: %w", d.Id(), err) + } + } + + return resourceAccountRead(d, meta) +} + +func resourceAccountDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + close := d.Get("close_on_deletion").(bool) + var err error + + if close { + log.Printf("[DEBUG] Closing AWS Organizations Account: %s", d.Id()) + _, err = conn.CloseAccount(&organizations.CloseAccountInput{ + AccountId: aws.String(d.Id()), + }) + } else { + log.Printf("[DEBUG] Removing AWS Organizations Account from organization: %s", d.Id()) + _, err = conn.RemoveAccountFromOrganization(&organizations.RemoveAccountFromOrganizationInput{ + AccountId: aws.String(d.Id()), + }) + } + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeAccountNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting AWS Organizations Account (%s): %w", d.Id(), err) + } + + if close { + if _, err := waitAccountDeleted(conn, d.Id()); err != nil { + return fmt.Errorf("error waiting for AWS Organizations Account (%s) delete: %w", d.Id(), err) + } + } + + return nil +} + +func createAccount(conn *organizations.Organizations, name, email string, iamUserAccessToBilling, roleName *string, tags []*organizations.Tag, govCloud bool) (*organizations.CreateAccountStatus, error) { + if govCloud { + input := &organizations.CreateGovCloudAccountInput{ + AccountName: aws.String(name), + Email: aws.String(email), + } + + if iamUserAccessToBilling != nil { + input.IamUserAccessToBilling = iamUserAccessToBilling + } + + if roleName != nil { + input.RoleName = roleName + } + + if len(tags) > 0 { + input.Tags = tags + } + + log.Printf("[DEBUG] Creating AWS Organizations Account with GovCloud Account: %s", input) + outputRaw, err := tfresource.RetryWhenAWSErrCodeEquals(4*time.Minute, + func() (interface{}, error) { + return conn.CreateGovCloudAccount(input) + }, + organizations.ErrCodeFinalizingOrganizationException, + ) + + if err != nil { + return nil, err + } + + return outputRaw.(*organizations.CreateGovCloudAccountOutput).CreateAccountStatus, nil + } + + input := &organizations.CreateAccountInput{ + AccountName: aws.String(name), + Email: aws.String(email), + } + + if iamUserAccessToBilling != nil { + input.IamUserAccessToBilling = iamUserAccessToBilling + } + + if roleName != nil { + input.RoleName = roleName + } + + if len(tags) > 0 { + input.Tags = tags + } + + log.Printf("[DEBUG] Creating AWS Organizations Account: %s", input) + outputRaw, err := tfresource.RetryWhenAWSErrCodeEquals(4*time.Minute, + func() (interface{}, error) { + return conn.CreateAccount(input) + }, + organizations.ErrCodeFinalizingOrganizationException, + ) + + if err != nil { + return nil, err + } + + return outputRaw.(*organizations.CreateAccountOutput).CreateAccountStatus, nil +} + +func findParentAccountID(conn *organizations.Organizations, id string) (string, error) { + input := &organizations.ListParentsInput{ + ChildId: aws.String(id), + } + var output []*organizations.Parent + + err := conn.ListParentsPages(input, func(page *organizations.ListParentsOutput, lastPage bool) bool { + output = append(output, page.Parents...) + + return !lastPage + }) + + if err != nil { + return "", err + } + + if len(output) == 0 || output[0] == nil { + return "", tfresource.NewEmptyResultError(input) + } + + // assume there is only a single parent + // https://docs.aws.amazon.com/organizations/latest/APIReference/API_ListParents.html + if count := len(output); count > 1 { + return "", tfresource.NewTooManyResultsError(count, input) + } + + return aws.StringValue(output[0].Id), nil +} + +func findCreateAccountStatusByID(conn *organizations.Organizations, id string) (*organizations.CreateAccountStatus, error) { + input := &organizations.DescribeCreateAccountStatusInput{ + CreateAccountRequestId: aws.String(id), + } + + output, err := conn.DescribeCreateAccountStatus(input) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeCreateAccountStatusNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.CreateAccountStatus == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.CreateAccountStatus, nil +} + +func statusCreateAccountState(conn *organizations.Organizations, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findCreateAccountStatusByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.State), nil + } +} + +func waitAccountCreated(conn *organizations.Organizations, id string) (*organizations.CreateAccountStatus, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{organizations.CreateAccountStateInProgress}, + Target: []string{organizations.CreateAccountStateSucceeded}, + Refresh: statusCreateAccountState(conn, id), + PollInterval: 10 * time.Second, + Timeout: 5 * time.Minute, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*organizations.CreateAccountStatus); ok { + if state := aws.StringValue(output.State); state == organizations.CreateAccountStateFailed { + tfresource.SetLastError(err, errors.New(aws.StringValue(output.FailureReason))) + } + + return output, err + } + + return nil, err +} + +func statusAccountStatus(conn *organizations.Organizations, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindAccountByID(conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status), nil + } +} + +func waitAccountDeleted(conn *organizations.Organizations, id string) (*organizations.Account, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{organizations.AccountStatusPendingClosure}, + Target: []string{}, + Refresh: statusAccountStatus(conn, id), + PollInterval: 10 * time.Second, + Timeout: 5 * time.Minute, + } + + outputRaw, err := stateConf.WaitForState() + + if output, ok := outputRaw.(*organizations.Account); ok { + return output, err + } + + return nil, err +} diff --git a/internal/service/organizations/delegated_administrator.go b/internal/service/organizations/delegated_administrator.go new file mode 100644 index 0000000..008b723 --- /dev/null +++ b/internal/service/organizations/delegated_administrator.go @@ -0,0 +1,160 @@ +package organizations + +import ( + "context" + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceDelegatedAdministrator() *schema.Resource { + return &schema.Resource{ + CreateWithoutTimeout: resourceDelegatedAdministratorCreate, + ReadWithoutTimeout: resourceDelegatedAdministratorRead, + DeleteWithoutTimeout: resourceDelegatedAdministratorDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + Schema: map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidAccountID, + }, + "service_principal": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "delegation_enabled_date": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "joined_method": { + Type: schema.TypeString, + Computed: true, + }, + "joined_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func resourceDelegatedAdministratorCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + + accountID := d.Get("account_id").(string) + servicePrincipal := d.Get("service_principal").(string) + input := &organizations.RegisterDelegatedAdministratorInput{ + AccountId: aws.String(accountID), + ServicePrincipal: aws.String(servicePrincipal), + } + + _, err := conn.RegisterDelegatedAdministratorWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Organizations DelegatedAdministrator (%s): %w", accountID, err)) + } + + d.SetId(fmt.Sprintf("%s/%s", accountID, servicePrincipal)) + + return resourceDelegatedAdministratorRead(ctx, d, meta) +} + +func resourceDelegatedAdministratorRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + + accountID, servicePrincipal, err := DecodeOrganizationDelegatedAdministratorID(d.Id()) + if err != nil { + return diag.FromErr(fmt.Errorf("error decoding ID AWS Organization (%s) DelegatedAdministrators: %w", d.Id(), err)) + } + input := &organizations.ListDelegatedAdministratorsInput{ + ServicePrincipal: aws.String(servicePrincipal), + } + var delegatedAccount *organizations.DelegatedAdministrator + err = conn.ListDelegatedAdministratorsPagesWithContext(ctx, input, func(page *organizations.ListDelegatedAdministratorsOutput, lastPage bool) bool { + for _, delegated := range page.DelegatedAdministrators { + if aws.StringValue(delegated.Id) == accountID { + delegatedAccount = delegated + } + } + + return !lastPage + }) + if err != nil { + return diag.FromErr(fmt.Errorf("error listing AWS Organization (%s) DelegatedAdministrators: %w", d.Id(), err)) + } + + if delegatedAccount == nil { + log.Printf("[WARN] AWS Organization DelegatedAdministrators not found (%s), removing from state", d.Id()) + d.SetId("") + return nil + } + + d.Set("arn", delegatedAccount.Arn) + d.Set("delegation_enabled_date", aws.TimeValue(delegatedAccount.DelegationEnabledDate).Format(time.RFC3339)) + d.Set("email", delegatedAccount.Email) + d.Set("joined_method", delegatedAccount.JoinedMethod) + d.Set("joined_timestamp", aws.TimeValue(delegatedAccount.JoinedTimestamp).Format(time.RFC3339)) + d.Set("name", delegatedAccount.Name) + d.Set("status", delegatedAccount.Status) + d.Set("account_id", accountID) + d.Set("service_principal", servicePrincipal) + + return nil +} + +func resourceDelegatedAdministratorDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + + accountID, servicePrincipal, err := DecodeOrganizationDelegatedAdministratorID(d.Id()) + if err != nil { + return diag.FromErr(fmt.Errorf("error decoding ID AWS Organization (%s) DelegatedAdministrators: %w", d.Id(), err)) + } + input := &organizations.DeregisterDelegatedAdministratorInput{ + AccountId: aws.String(accountID), + ServicePrincipal: aws.String(servicePrincipal), + } + + _, err = conn.DeregisterDelegatedAdministratorWithContext(ctx, input) + if err != nil { + return diag.FromErr(fmt.Errorf("error deleting Organizations DelegatedAdministrator (%s): %w", d.Id(), err)) + } + return nil +} + +func DecodeOrganizationDelegatedAdministratorID(id string) (string, string, error) { + idParts := strings.Split(id, "/") + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return "", "", fmt.Errorf("expected ID in the form of account_id/service_principal, given: %q", id) + } + return idParts[0], idParts[1], nil +} diff --git a/internal/service/organizations/delegated_administrators_data_source.go b/internal/service/organizations/delegated_administrators_data_source.go new file mode 100644 index 0000000..562f8cd --- /dev/null +++ b/internal/service/organizations/delegated_administrators_data_source.go @@ -0,0 +1,121 @@ +package organizations + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func DataSourceDelegatedAdministrators() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceDelegatedAdministratorsRead, + Schema: map[string]*schema.Schema{ + "service_principal": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "delegated_administrators": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "delegation_enabled_date": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "joined_method": { + Type: schema.TypeString, + Computed: true, + }, + "joined_timestamp": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceDelegatedAdministratorsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + + input := &organizations.ListDelegatedAdministratorsInput{} + + if v, ok := d.GetOk("service_principal"); ok { + input.ServicePrincipal = aws.String(v.(string)) + } + + var delegators []*organizations.DelegatedAdministrator + + err := conn.ListDelegatedAdministratorsPagesWithContext(ctx, input, func(page *organizations.ListDelegatedAdministratorsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + delegators = append(delegators, page.DelegatedAdministrators...) + + return !lastPage + }) + if err != nil { + return diag.FromErr(fmt.Errorf("error describing organizations delegated Administrators: %w", err)) + } + + if err = d.Set("delegated_administrators", flattenDelegatedAdministrators(delegators)); err != nil { + return diag.FromErr(fmt.Errorf("error setting delegated_administrators: %w", err)) + } + + d.SetId(meta.(*conns.AWSClient).AccountID) + + return nil +} + +func flattenDelegatedAdministrators(delegatedAdministrators []*organizations.DelegatedAdministrator) []map[string]interface{} { + if len(delegatedAdministrators) == 0 { + return nil + } + + var result []map[string]interface{} + for _, delegated := range delegatedAdministrators { + result = append(result, map[string]interface{}{ + "arn": aws.StringValue(delegated.Arn), + "delegation_enabled_date": aws.TimeValue(delegated.DelegationEnabledDate).Format(time.RFC3339), + "email": aws.StringValue(delegated.Email), + "id": aws.StringValue(delegated.Id), + "joined_method": aws.StringValue(delegated.JoinedMethod), + "joined_timestamp": aws.TimeValue(delegated.JoinedTimestamp).Format(time.RFC3339), + "name": aws.StringValue(delegated.Name), + "status": aws.StringValue(delegated.Status), + }) + } + return result +} diff --git a/internal/service/organizations/delegated_services_data_source.go b/internal/service/organizations/delegated_services_data_source.go new file mode 100644 index 0000000..a347ecf --- /dev/null +++ b/internal/service/organizations/delegated_services_data_source.go @@ -0,0 +1,88 @@ +package organizations + +import ( + "context" + "fmt" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceDelegatedServices() *schema.Resource { + return &schema.Resource{ + ReadWithoutTimeout: dataSourceDelegatedServicesRead, + Schema: map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidAccountID, + }, + "delegated_services": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "delegation_enabled_date": { + Type: schema.TypeString, + Computed: true, + }, + "service_principal": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceDelegatedServicesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + + input := &organizations.ListDelegatedServicesForAccountInput{ + AccountId: aws.String(d.Get("account_id").(string)), + } + + var delegators []*organizations.DelegatedService + err := conn.ListDelegatedServicesForAccountPagesWithContext(ctx, input, func(page *organizations.ListDelegatedServicesForAccountOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + delegators = append(delegators, page.DelegatedServices...) + + return !lastPage + }) + if err != nil { + return diag.FromErr(fmt.Errorf("error describing organizations delegated services: %w", err)) + } + + if err = d.Set("delegated_services", flattenDelegatedServices(delegators)); err != nil { + return diag.FromErr(fmt.Errorf("error setting delegated_services: %w", err)) + } + + d.SetId(meta.(*conns.AWSClient).AccountID) + + return nil +} + +func flattenDelegatedServices(delegatedServices []*organizations.DelegatedService) []map[string]interface{} { + if len(delegatedServices) == 0 { + return nil + } + + var result []map[string]interface{} + for _, delegated := range delegatedServices { + result = append(result, map[string]interface{}{ + "delegation_enabled_date": aws.TimeValue(delegated.DelegationEnabledDate).Format(time.RFC3339), + "service_principal": aws.StringValue(delegated.ServicePrincipal), + }) + } + return result +} diff --git a/internal/service/organizations/find.go b/internal/service/organizations/find.go new file mode 100644 index 0000000..b36f703 --- /dev/null +++ b/internal/service/organizations/find.go @@ -0,0 +1,64 @@ +package organizations + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/tfresource" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func FindAccountByID(conn *organizations.Organizations, id string) (*organizations.Account, error) { + input := &organizations.DescribeAccountInput{ + AccountId: aws.String(id), + } + + output, err := conn.DescribeAccount(input) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeAccountNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Account == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if status := aws.StringValue(output.Account.Status); status == organizations.AccountStatusSuspended { + return nil, &resource.NotFoundError{ + Message: status, + LastRequest: input, + } + } + + return output.Account, nil +} + +func FindOrganization(conn *organizations.Organizations) (*organizations.Organization, error) { + input := &organizations.DescribeOrganizationInput{} + + output, err := conn.DescribeOrganization(input) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeAWSOrganizationsNotInUseException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Organization == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Organization, nil +} diff --git a/internal/service/organizations/flex_test.go b/internal/service/organizations/flex_test.go new file mode 100644 index 0000000..16b92c9 --- /dev/null +++ b/internal/service/organizations/flex_test.go @@ -0,0 +1,32 @@ +package organizations + +import ( + "reflect" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" +) + +func TestFlattenOrganizationalUnits(t *testing.T) { + input := []*organizations.OrganizationalUnit{ + { + Arn: aws.String("arn:aws:organizations::123456789012:ou/o-abcde12345/ou-ab12-abcd1234"), //lintignore:AWSAT005 + Id: aws.String("ou-ab12-abcd1234"), + Name: aws.String("Engineering"), + }, + } + + expected_output := []map[string]interface{}{ + { + "arn": "arn:aws:organizations::123456789012:ou/o-abcde12345/ou-ab12-abcd1234", //lintignore:AWSAT005 + "id": "ou-ab12-abcd1234", + "name": "Engineering", + }, + } + + output := FlattenOrganizationalUnits(input) + if !reflect.DeepEqual(expected_output, output) { + t.Fatalf("Got:\n\n%#v\n\nExpected:\n\n%#v", output, expected_output) + } +} diff --git a/internal/service/organizations/generate.go b/internal/service/organizations/generate.go new file mode 100644 index 0000000..1c4eb60 --- /dev/null +++ b/internal/service/organizations/generate.go @@ -0,0 +1,4 @@ +//go:generate go run ../../generate/tags/main.go -ListTags -ListTagsInIDElem=ResourceId -ServiceTagsSlice -TagInIDElem=ResourceId -UpdateTags +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package organizations diff --git a/internal/service/organizations/organization.go b/internal/service/organizations/organization.go new file mode 100644 index 0000000..32ef013 --- /dev/null +++ b/internal/service/organizations/organization.go @@ -0,0 +1,542 @@ +package organizations + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +const policyTypeStatusDisabled = "DISABLED" + +func ResourceOrganization() *schema.Resource { + return &schema.Resource{ + Create: resourceOrganizationCreate, + Read: resourceOrganizationRead, + Update: resourceOrganizationUpdate, + Delete: resourceOrganizationDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + CustomizeDiff: customdiff.Sequence( + customdiff.ForceNewIfChange("feature_set", func(_ context.Context, old, new, meta interface{}) bool { + // Only changes from ALL to CONSOLIDATED_BILLING for feature_set should force a new resource + return old.(string) == organizations.OrganizationFeatureSetAll && new.(string) == organizations.OrganizationFeatureSetConsolidatedBilling + }), + ), + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "master_account_arn": { + Type: schema.TypeString, + Computed: true, + }, + "master_account_email": { + Type: schema.TypeString, + Computed: true, + }, + "master_account_id": { + Type: schema.TypeString, + Computed: true, + }, + "aws_service_access_principals": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "accounts": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "non_master_accounts": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "roots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "policy_types": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "enabled_policy_types": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice(organizations.PolicyType_Values(), false), + }, + }, + "feature_set": { + Type: schema.TypeString, + Optional: true, + Default: organizations.OrganizationFeatureSetAll, + ValidateFunc: validation.StringInSlice(organizations.OrganizationFeatureSet_Values(), true), + }, + }, + } +} + +func resourceOrganizationCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + createOpts := &organizations.CreateOrganizationInput{ + FeatureSet: aws.String(d.Get("feature_set").(string)), + } + log.Printf("[DEBUG] Creating Organization: %#v", createOpts) + + resp, err := conn.CreateOrganization(createOpts) + if err != nil { + return fmt.Errorf("Error creating organization: %s", err) + } + + org := resp.Organization + d.SetId(aws.StringValue(org.Id)) + + awsServiceAccessPrincipals := d.Get("aws_service_access_principals").(*schema.Set).List() + for _, principalRaw := range awsServiceAccessPrincipals { + principal := principalRaw.(string) + input := &organizations.EnableAWSServiceAccessInput{ + ServicePrincipal: aws.String(principal), + } + + log.Printf("[DEBUG] Enabling AWS Service Access in Organization: %s", input) + _, err := conn.EnableAWSServiceAccess(input) + + if err != nil { + return fmt.Errorf("error enabling AWS Service Access (%s) in Organization: %s", principal, err) + } + } + + enabledPolicyTypes := d.Get("enabled_policy_types").(*schema.Set).List() + + if len(enabledPolicyTypes) > 0 { + defaultRoot, err := getOrganizationDefaultRoot(conn) + + if err != nil { + return fmt.Errorf("error getting AWS Organization (%s) default root: %s", d.Id(), err) + } + + for _, v := range enabledPolicyTypes { + enabledPolicyType := v.(string) + input := &organizations.EnablePolicyTypeInput{ + PolicyType: aws.String(enabledPolicyType), + RootId: defaultRoot.Id, + } + + if _, err := conn.EnablePolicyType(input); err != nil { + return fmt.Errorf("error enabling policy type (%s) in Organization (%s): %s", enabledPolicyType, d.Id(), err) + } + + if err := waitForOrganizationDefaultRootPolicyTypeEnable(conn, enabledPolicyType); err != nil { + return fmt.Errorf("error waiting for policy type (%s) enabling in Organization (%s): %s", enabledPolicyType, d.Id(), err) + } + } + } + + return resourceOrganizationRead(d, meta) +} + +func resourceOrganizationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + log.Printf("[INFO] Reading Organization: %s", d.Id()) + org, err := conn.DescribeOrganization(&organizations.DescribeOrganizationInput{}) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeAWSOrganizationsNotInUseException) { + log.Printf("[WARN] Organization does not exist, removing from state: %s", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error describing Organization: %s", err) + } + + log.Printf("[INFO] Listing Accounts for Organization: %s", d.Id()) + var accounts []*organizations.Account + var nonMasterAccounts []*organizations.Account + err = conn.ListAccountsPages(&organizations.ListAccountsInput{}, func(page *organizations.ListAccountsOutput, lastPage bool) bool { + for _, account := range page.Accounts { + if aws.StringValue(account.Id) != aws.StringValue(org.Organization.MasterAccountId) { + nonMasterAccounts = append(nonMasterAccounts, account) + } + + accounts = append(accounts, account) + } + + return !lastPage + }) + if err != nil { + return fmt.Errorf("error listing AWS Organization (%s) accounts: %s", d.Id(), err) + } + + log.Printf("[INFO] Listing Roots for Organization: %s", d.Id()) + var roots []*organizations.Root + err = conn.ListRootsPages(&organizations.ListRootsInput{}, func(page *organizations.ListRootsOutput, lastPage bool) bool { + roots = append(roots, page.Roots...) + return !lastPage + }) + if err != nil { + return fmt.Errorf("error listing AWS Organization (%s) roots: %s", d.Id(), err) + } + + if err := d.Set("accounts", flattenAccounts(accounts)); err != nil { + return fmt.Errorf("error setting accounts: %s", err) + } + + d.Set("arn", org.Organization.Arn) + d.Set("feature_set", org.Organization.FeatureSet) + d.Set("master_account_arn", org.Organization.MasterAccountArn) + d.Set("master_account_email", org.Organization.MasterAccountEmail) + d.Set("master_account_id", org.Organization.MasterAccountId) + + if err := d.Set("non_master_accounts", flattenAccounts(nonMasterAccounts)); err != nil { + return fmt.Errorf("error setting non_master_accounts: %s", err) + } + + if err := d.Set("roots", FlattenRoots(roots)); err != nil { + return fmt.Errorf("error setting roots: %s", err) + } + + awsServiceAccessPrincipals := make([]string, 0) + + // ConstraintViolationException: The request failed because the organization does not have all features enabled. Please enable all features in your organization and then retry. + if aws.StringValue(org.Organization.FeatureSet) == organizations.OrganizationFeatureSetAll { + err = conn.ListAWSServiceAccessForOrganizationPages(&organizations.ListAWSServiceAccessForOrganizationInput{}, func(page *organizations.ListAWSServiceAccessForOrganizationOutput, lastPage bool) bool { + for _, enabledServicePrincipal := range page.EnabledServicePrincipals { + awsServiceAccessPrincipals = append(awsServiceAccessPrincipals, aws.StringValue(enabledServicePrincipal.ServicePrincipal)) + } + return !lastPage + }) + + if err != nil { + return fmt.Errorf("error listing AWS Service Access for Organization (%s): %s", d.Id(), err) + } + } + + if err := d.Set("aws_service_access_principals", awsServiceAccessPrincipals); err != nil { + return fmt.Errorf("error setting aws_service_access_principals: %s", err) + } + + enabledPolicyTypes := make([]string, 0) + + for _, policyType := range roots[0].PolicyTypes { + if aws.StringValue(policyType.Status) == organizations.PolicyTypeStatusEnabled { + enabledPolicyTypes = append(enabledPolicyTypes, aws.StringValue(policyType.Type)) + } + } + + if err := d.Set("enabled_policy_types", enabledPolicyTypes); err != nil { + return fmt.Errorf("error setting enabled_policy_types: %s", err) + } + + return nil +} + +func resourceOrganizationUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + if d.HasChange("aws_service_access_principals") { + oldRaw, newRaw := d.GetChange("aws_service_access_principals") + oldSet := oldRaw.(*schema.Set) + newSet := newRaw.(*schema.Set) + + for _, disablePrincipalRaw := range oldSet.Difference(newSet).List() { + principal := disablePrincipalRaw.(string) + input := &organizations.DisableAWSServiceAccessInput{ + ServicePrincipal: aws.String(principal), + } + + log.Printf("[DEBUG] Disabling AWS Service Access in Organization: %s", input) + _, err := conn.DisableAWSServiceAccess(input) + + if err != nil { + return fmt.Errorf("error disabling AWS Service Access (%s) in Organization: %s", principal, err) + } + } + + for _, enablePrincipalRaw := range newSet.Difference(oldSet).List() { + principal := enablePrincipalRaw.(string) + input := &organizations.EnableAWSServiceAccessInput{ + ServicePrincipal: aws.String(principal), + } + + log.Printf("[DEBUG] Enabling AWS Service Access in Organization: %s", input) + _, err := conn.EnableAWSServiceAccess(input) + + if err != nil { + return fmt.Errorf("error enabling AWS Service Access (%s) in Organization: %s", principal, err) + } + } + } + + if d.HasChange("enabled_policy_types") { + defaultRootID := d.Get("roots.0.id").(string) + o, n := d.GetChange("enabled_policy_types") + oldSet := o.(*schema.Set) + newSet := n.(*schema.Set) + + for _, v := range oldSet.Difference(newSet).List() { + policyType := v.(string) + input := &organizations.DisablePolicyTypeInput{ + PolicyType: aws.String(policyType), + RootId: aws.String(defaultRootID), + } + + log.Printf("[DEBUG] Disabling Policy Type in Organization: %s", input) + if _, err := conn.DisablePolicyType(input); err != nil { + return fmt.Errorf("error disabling policy type (%s) in Organization (%s) Root (%s): %s", policyType, d.Id(), defaultRootID, err) + } + + if err := waitForOrganizationDefaultRootPolicyTypeDisable(conn, policyType); err != nil { + return fmt.Errorf("error waiting for policy type (%s) disabling in Organization (%s) Root (%s): %s", policyType, d.Id(), defaultRootID, err) + } + } + + for _, v := range newSet.Difference(oldSet).List() { + policyType := v.(string) + input := &organizations.EnablePolicyTypeInput{ + PolicyType: aws.String(policyType), + RootId: aws.String(defaultRootID), + } + + log.Printf("[DEBUG] Enabling Policy Type in Organization: %s", input) + if _, err := conn.EnablePolicyType(input); err != nil { + return fmt.Errorf("error enabling policy type (%s) in Organization (%s) Root (%s): %s", policyType, d.Id(), defaultRootID, err) + } + + if err := waitForOrganizationDefaultRootPolicyTypeEnable(conn, policyType); err != nil { + return fmt.Errorf("error waiting for policy type (%s) enabling in Organization (%s) Root (%s): %s", policyType, d.Id(), defaultRootID, err) + } + } + } + + if d.HasChange("feature_set") { + if _, err := conn.EnableAllFeatures(&organizations.EnableAllFeaturesInput{}); err != nil { + return fmt.Errorf("error enabling all features in Organization (%s): %w", d.Id(), err) + } + } + + return resourceOrganizationRead(d, meta) +} + +func resourceOrganizationDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + log.Printf("[INFO] Deleting Organization: %s", d.Id()) + + _, err := conn.DeleteOrganization(&organizations.DeleteOrganizationInput{}) + if err != nil { + return fmt.Errorf("Error deleting Organization: %s", err) + } + + return nil +} + +func flattenAccounts(accounts []*organizations.Account) []map[string]interface{} { + if len(accounts) == 0 { + return nil + } + var result []map[string]interface{} + for _, account := range accounts { + result = append(result, map[string]interface{}{ + "arn": aws.StringValue(account.Arn), + "email": aws.StringValue(account.Email), + "id": aws.StringValue(account.Id), + "name": aws.StringValue(account.Name), + "status": aws.StringValue(account.Status), + }) + } + return result +} + +func FlattenRoots(roots []*organizations.Root) []map[string]interface{} { + if len(roots) == 0 { + return nil + } + var result []map[string]interface{} + for _, r := range roots { + result = append(result, map[string]interface{}{ + "id": aws.StringValue(r.Id), + "name": aws.StringValue(r.Name), + "arn": aws.StringValue(r.Arn), + "policy_types": flattenRootPolicyTypeSummaries(r.PolicyTypes), + }) + } + return result +} + +func flattenRootPolicyTypeSummaries(summaries []*organizations.PolicyTypeSummary) []map[string]interface{} { + if len(summaries) == 0 { + return nil + } + var result []map[string]interface{} + for _, s := range summaries { + result = append(result, map[string]interface{}{ + "status": aws.StringValue(s.Status), + "type": aws.StringValue(s.Type), + }) + } + return result +} + +func getOrganizationDefaultRoot(conn *organizations.Organizations) (*organizations.Root, error) { + var roots []*organizations.Root + + err := conn.ListRootsPages(&organizations.ListRootsInput{}, func(page *organizations.ListRootsOutput, lastPage bool) bool { + roots = append(roots, page.Roots...) + + return !lastPage + }) + + if err != nil { + return nil, err + } + + if len(roots) == 0 { + return nil, fmt.Errorf("no roots found") + } + + return roots[0], nil +} + +func getOrganizationDefaultRootPolicyTypeRefreshFunc(conn *organizations.Organizations, policyType string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + defaultRoot, err := getOrganizationDefaultRoot(conn) + + if err != nil { + return nil, "", fmt.Errorf("error getting default root: %s", err) + } + + for _, pt := range defaultRoot.PolicyTypes { + if aws.StringValue(pt.Type) == policyType { + return pt, aws.StringValue(pt.Status), nil + } + } + + return &organizations.PolicyTypeSummary{}, policyTypeStatusDisabled, nil + } +} + +func waitForOrganizationDefaultRootPolicyTypeDisable(conn *organizations.Organizations, policyType string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + organizations.PolicyTypeStatusEnabled, + organizations.PolicyTypeStatusPendingDisable, + }, + Target: []string{policyTypeStatusDisabled}, + Refresh: getOrganizationDefaultRootPolicyTypeRefreshFunc(conn, policyType), + Timeout: 5 * time.Minute, + } + + _, err := stateConf.WaitForState() + + return err +} + +func waitForOrganizationDefaultRootPolicyTypeEnable(conn *organizations.Organizations, policyType string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{ + policyTypeStatusDisabled, + organizations.PolicyTypeStatusPendingEnable, + }, + Target: []string{organizations.PolicyTypeStatusEnabled}, + Refresh: getOrganizationDefaultRootPolicyTypeRefreshFunc(conn, policyType), + Timeout: 5 * time.Minute, + } + + _, err := stateConf.WaitForState() + + return err +} diff --git a/internal/service/organizations/organization_data_source.go b/internal/service/organizations/organization_data_source.go new file mode 100644 index 0000000..a44265c --- /dev/null +++ b/internal/service/organizations/organization_data_source.go @@ -0,0 +1,231 @@ +package organizations + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceOrganization() *schema.Resource { + return &schema.Resource{ + Read: dataSourceOrganizationRead, + + Schema: map[string]*schema.Schema{ + "accounts": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "aws_service_access_principals": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "enabled_policy_types": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "feature_set": { + Type: schema.TypeString, + Computed: true, + }, + "master_account_arn": { + Type: schema.TypeString, + Computed: true, + }, + "master_account_email": { + Type: schema.TypeString, + Computed: true, + }, + "master_account_id": { + Type: schema.TypeString, + Computed: true, + }, + "non_master_accounts": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "roots": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "policy_types": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "status": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func dataSourceOrganizationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + org, err := conn.DescribeOrganization(&organizations.DescribeOrganizationInput{}) + if err != nil { + return fmt.Errorf("Error describing organization: %w", err) + } + + d.SetId(aws.StringValue(org.Organization.Id)) + d.Set("arn", org.Organization.Arn) + d.Set("feature_set", org.Organization.FeatureSet) + d.Set("master_account_arn", org.Organization.MasterAccountArn) + d.Set("master_account_email", org.Organization.MasterAccountEmail) + d.Set("master_account_id", org.Organization.MasterAccountId) + + if aws.StringValue(org.Organization.MasterAccountId) == meta.(*conns.AWSClient).AccountID { + var accounts []*organizations.Account + var nonMasterAccounts []*organizations.Account + err = conn.ListAccountsPages(&organizations.ListAccountsInput{}, func(page *organizations.ListAccountsOutput, lastPage bool) bool { + for _, account := range page.Accounts { + if aws.StringValue(account.Id) != aws.StringValue(org.Organization.MasterAccountId) { + nonMasterAccounts = append(nonMasterAccounts, account) + } + + accounts = append(accounts, account) + } + + return !lastPage + }) + if err != nil { + return fmt.Errorf("error listing AWS Organization (%s) accounts: %w", d.Id(), err) + } + + var roots []*organizations.Root + err = conn.ListRootsPages(&organizations.ListRootsInput{}, func(page *organizations.ListRootsOutput, lastPage bool) bool { + roots = append(roots, page.Roots...) + return !lastPage + }) + if err != nil { + return fmt.Errorf("error listing AWS Organization (%s) roots: %w", d.Id(), err) + } + + awsServiceAccessPrincipals := make([]string, 0) + // ConstraintViolationException: The request failed because the organization does not have all features enabled. Please enable all features in your organization and then retry. + if aws.StringValue(org.Organization.FeatureSet) == organizations.OrganizationFeatureSetAll { + err = conn.ListAWSServiceAccessForOrganizationPages(&organizations.ListAWSServiceAccessForOrganizationInput{}, func(page *organizations.ListAWSServiceAccessForOrganizationOutput, lastPage bool) bool { + for _, enabledServicePrincipal := range page.EnabledServicePrincipals { + awsServiceAccessPrincipals = append(awsServiceAccessPrincipals, aws.StringValue(enabledServicePrincipal.ServicePrincipal)) + } + return !lastPage + }) + + if err != nil { + return fmt.Errorf("error listing AWS Service Access for Organization (%s): %w", d.Id(), err) + } + } + + enabledPolicyTypes := make([]string, 0) + for _, policyType := range roots[0].PolicyTypes { + if aws.StringValue(policyType.Status) == organizations.PolicyTypeStatusEnabled { + enabledPolicyTypes = append(enabledPolicyTypes, aws.StringValue(policyType.Type)) + } + } + + if err := d.Set("accounts", flattenAccounts(accounts)); err != nil { + return fmt.Errorf("error setting accounts: %w", err) + } + + if err := d.Set("aws_service_access_principals", awsServiceAccessPrincipals); err != nil { + return fmt.Errorf("error setting aws_service_access_principals: %w", err) + } + + if err := d.Set("enabled_policy_types", enabledPolicyTypes); err != nil { + return fmt.Errorf("error setting enabled_policy_types: %w", err) + } + + if err := d.Set("non_master_accounts", flattenAccounts(nonMasterAccounts)); err != nil { + return fmt.Errorf("error setting non_master_accounts: %w", err) + } + + if err := d.Set("roots", FlattenRoots(roots)); err != nil { + return fmt.Errorf("error setting roots: %w", err) + } + + } + return nil +} diff --git a/internal/service/organizations/organization_test.go b/internal/service/organizations/organization_test.go new file mode 100644 index 0000000..12ab1c4 --- /dev/null +++ b/internal/service/organizations/organization_test.go @@ -0,0 +1,453 @@ +package organizations_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/acctest" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + tforganizations "github.com/cloudposse/terraform-provider-awsutils/internal/service/organizations" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccOrganization_basic(t *testing.T) { + var organization organizations.Organization + resourceName := "aws_organizations_organization.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "accounts.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "accounts.0.arn", resourceName, "master_account_arn"), + resource.TestCheckResourceAttrPair(resourceName, "accounts.0.email", resourceName, "master_account_email"), + resource.TestCheckResourceAttrPair(resourceName, "accounts.0.id", resourceName, "master_account_id"), + acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "organizations", regexp.MustCompile(`organization/o-.+`)), + resource.TestCheckResourceAttr(resourceName, "aws_service_access_principals.#", "0"), + resource.TestCheckResourceAttr(resourceName, "feature_set", organizations.OrganizationFeatureSetAll), + acctest.MatchResourceAttrGlobalARN(resourceName, "master_account_arn", "organizations", regexp.MustCompile(`account/o-.+/.+`)), + resource.TestMatchResourceAttr(resourceName, "master_account_email", regexp.MustCompile(`.+@.+`)), + acctest.CheckResourceAttrAccountID(resourceName, "master_account_id"), + resource.TestCheckResourceAttr(resourceName, "non_master_accounts.#", "0"), + resource.TestCheckResourceAttr(resourceName, "roots.#", "1"), + resource.TestMatchResourceAttr(resourceName, "roots.0.id", regexp.MustCompile(`r-[a-z0-9]{4,32}`)), + resource.TestCheckResourceAttrSet(resourceName, "roots.0.name"), + resource.TestCheckResourceAttrSet(resourceName, "roots.0.arn"), + resource.TestCheckResourceAttr(resourceName, "roots.0.policy_types.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccOrganization_serviceAccessPrincipals(t *testing.T) { + var organization organizations.Organization + resourceName := "aws_organizations_organization.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationConfig_serviceAccessPrincipals1("config.amazonaws.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "aws_service_access_principals.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "aws_service_access_principals.*", "config.amazonaws.com"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfig_serviceAccessPrincipals2("config.amazonaws.com", "ds.amazonaws.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "aws_service_access_principals.#", "2"), + resource.TestCheckTypeSetElemAttr(resourceName, "aws_service_access_principals.*", "config.amazonaws.com"), + resource.TestCheckTypeSetElemAttr(resourceName, "aws_service_access_principals.*", "ds.amazonaws.com"), + ), + }, + { + Config: testAccOrganizationConfig_serviceAccessPrincipals1("fms.amazonaws.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "aws_service_access_principals.#", "1"), + resource.TestCheckTypeSetElemAttr(resourceName, "aws_service_access_principals.*", "fms.amazonaws.com"), + ), + }, + }, + }) +} + +func testAccOrganization_EnabledPolicyTypes(t *testing.T) { + var organization organizations.Organization + resourceName := "aws_organizations_organization.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationConfig_enabledPolicyTypes1(organizations.PolicyTypeServiceControlPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.0", organizations.PolicyTypeServiceControlPolicy), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "0"), + ), + }, + { + Config: testAccOrganizationConfig_enabledPolicyTypes1(organizations.PolicyTypeAiservicesOptOutPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.0", organizations.PolicyTypeAiservicesOptOutPolicy), + ), + }, + { + Config: testAccOrganizationConfig_enabledPolicyTypes1(organizations.PolicyTypeServiceControlPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.0", organizations.PolicyTypeServiceControlPolicy), + ), + }, + { + Config: testAccOrganizationConfig_enabledPolicyTypes1(organizations.PolicyTypeBackupPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.0", organizations.PolicyTypeBackupPolicy), + ), + }, + { + Config: testAccOrganizationConfig_enabledPolicyTypes1(organizations.PolicyTypeTagPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "1"), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.0", organizations.PolicyTypeTagPolicy), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "0"), + ), + }, + { + Config: testAccOrganizationConfig_enabledPolicyTypes1(organizations.PolicyTypeTagPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "enabled_policy_types.#", "1"), + ), + }, + }, + }) +} + +func testAccOrganization_FeatureSet(t *testing.T) { + var organization organizations.Organization + resourceName := "aws_organizations_organization.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationConfig_featureSet(organizations.OrganizationFeatureSetConsolidatedBilling), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &organization), + resource.TestCheckResourceAttr(resourceName, "feature_set", organizations.OrganizationFeatureSetConsolidatedBilling), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccOrganization_FeatureSetForcesNew(t *testing.T) { + var beforeValue, afterValue organizations.Organization + resourceName := "aws_organizations_organization.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationConfig_featureSet(organizations.OrganizationFeatureSetAll), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &beforeValue), + resource.TestCheckResourceAttr(resourceName, "feature_set", organizations.OrganizationFeatureSetAll), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfig_featureSet(organizations.OrganizationFeatureSetConsolidatedBilling), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &afterValue), + resource.TestCheckResourceAttr(resourceName, "feature_set", organizations.OrganizationFeatureSetConsolidatedBilling), + testAccOrganizationRecreated(&beforeValue, &afterValue), + ), + }, + }, + }) +} + +func testAccOrganization_FeatureSetUpdate(t *testing.T) { + var beforeValue, afterValue organizations.Organization + resourceName := "aws_organizations_organization.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOrganizationDestroy, + Steps: []resource.TestStep{ + { + Config: testAccOrganizationConfig_featureSet(organizations.OrganizationFeatureSetConsolidatedBilling), + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &beforeValue), + resource.TestCheckResourceAttr(resourceName, "feature_set", organizations.OrganizationFeatureSetConsolidatedBilling), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccOrganizationConfig_featureSet(organizations.OrganizationFeatureSetAll), + ExpectNonEmptyPlan: true, // See note below on this perpetual difference + Check: resource.ComposeTestCheckFunc( + testAccCheckOrganizationExists(resourceName, &afterValue), + // The below check cannot be performed here because the user must confirm the change + // via Console. Until then, the FeatureSet will not actually be toggled to ALL + // and will continue to show as CONSOLIDATED_BILLING when calling DescribeOrganization + // resource.TestCheckResourceAttr(resourceName, "feature_set", organizations.OrganizationFeatureSetAll), + testAccOrganizationNotRecreated(&beforeValue, &afterValue), + ), + }, + }, + }) +} + +func testAccCheckOrganizationDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_organizations_organization" { + continue + } + + params := &organizations.DescribeOrganizationInput{} + + resp, err := conn.DescribeOrganization(params) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeAWSOrganizationsNotInUseException) { + return nil + } + + if err != nil { + return err + } + + if resp != nil && resp.Organization != nil { + return fmt.Errorf("Bad: Organization still exists: %q", rs.Primary.ID) + } + } + + return nil +} + +func testAccCheckOrganizationExists(n string, org *organizations.Organization) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("Organization ID not set") + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsConn + params := &organizations.DescribeOrganizationInput{} + + resp, err := conn.DescribeOrganization(params) + + if err != nil { + return err + } + + if resp == nil || resp.Organization == nil { + return fmt.Errorf("Organization %q does not exist", rs.Primary.ID) + } + + *org = *resp.Organization + + return nil + } +} + +const testAccOrganizationConfig_basic = "resource \"aws_organizations_organization\" \"test\" {}" + +func testAccOrganizationConfig_serviceAccessPrincipals1(principal1 string) string { + return fmt.Sprintf(` +resource "aws_organizations_organization" "test" { + aws_service_access_principals = [%q] +} +`, principal1) +} + +func testAccOrganizationConfig_serviceAccessPrincipals2(principal1, principal2 string) string { + return fmt.Sprintf(` +resource "aws_organizations_organization" "test" { + aws_service_access_principals = [%q, %q] +} +`, principal1, principal2) +} + +func testAccOrganizationConfig_enabledPolicyTypes1(policyType1 string) string { + return fmt.Sprintf(` +resource "aws_organizations_organization" "test" { + enabled_policy_types = [%[1]q] +} +`, policyType1) +} + +func testAccOrganizationConfig_featureSet(featureSet string) string { + return fmt.Sprintf(` +resource "aws_organizations_organization" "test" { + feature_set = %q +} +`, featureSet) +} + +func TestFlattenRoots(t *testing.T) { + roots := []*organizations.Root{ + { + Name: aws.String("Root1"), + Arn: aws.String("arn:1"), + Id: aws.String("r-1"), + PolicyTypes: []*organizations.PolicyTypeSummary{ + { + Status: aws.String("ENABLED"), + Type: aws.String("SERVICE_CONTROL_POLICY"), + }, + { + Status: aws.String("DISABLED"), + Type: aws.String("SERVICE_CONTROL_POLICY"), + }, + }, + }, + } + result := tforganizations.FlattenRoots(roots) + + if len(result) != len(roots) { + t.Fatalf("expected result to have %d elements, got %d", len(roots), len(result)) + } + + for i, r := range roots { + if aws.StringValue(r.Name) != result[i]["name"] { + t.Fatalf(`expected result[%d]["name"] to equal %q, got %q`, i, aws.StringValue(r.Name), result[i]["name"]) + } + if aws.StringValue(r.Arn) != result[i]["arn"] { + t.Fatalf(`expected result[%d]["arn"] to equal %q, got %q`, i, aws.StringValue(r.Arn), result[i]["arn"]) + } + if aws.StringValue(r.Id) != result[i]["id"] { + t.Fatalf(`expected result[%d]["id"] to equal %q, got %q`, i, aws.StringValue(r.Id), result[i]["id"]) + } + if result[i]["policy_types"] == nil { + continue + } + if types, ok := result[i]["policy_types"].([]map[string]interface{}); ok { + testFlattenRootPolicyTypes(t, i, types, r.PolicyTypes) + continue + } + t.Fatalf(`result[%d]["policy_types"] could not be converted to []map[string]interface{}`, i) + } +} + +func testFlattenRootPolicyTypes(t *testing.T, index int, result []map[string]interface{}, types []*organizations.PolicyTypeSummary) { + if len(result) != len(types) { + t.Fatalf(`expected result[%d]["policy_types"] to have %d elements, got %d`, index, len(types), len(result)) + } + for i, v := range types { + if aws.StringValue(v.Status) != result[i]["status"] { + t.Fatalf(`expected result[%d]["policy_types"][%d]["status"] to equal %q, got %q`, index, i, aws.StringValue(v.Status), result[i]["status"]) + } + if aws.StringValue(v.Type) != result[i]["type"] { + t.Fatalf(`expected result[%d]["policy_types"][%d]["type"] to equal %q, got %q`, index, i, aws.StringValue(v.Type), result[i]["type"]) + } + } +} + +func testAccOrganizationRecreated(before, after *organizations.Organization) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(before.Id) == aws.StringValue(after.Id) { + return fmt.Errorf("Organization (%s) not recreated", aws.StringValue(before.Id)) + } + return nil + } +} + +func testAccOrganizationNotRecreated(before, after *organizations.Organization) resource.TestCheckFunc { + return func(s *terraform.State) error { + if aws.StringValue(before.Id) != aws.StringValue(after.Id) { + return fmt.Errorf("Organization (%s) recreated", aws.StringValue(before.Id)) + } + return nil + } +} diff --git a/internal/service/organizations/organizational_unit.go b/internal/service/organizations/organizational_unit.go new file mode 100644 index 0000000..7239a67 --- /dev/null +++ b/internal/service/organizations/organizational_unit.go @@ -0,0 +1,297 @@ +package organizations + +import ( + "fmt" + "log" + "regexp" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" + "github.com/cloudposse/terraform-provider-awsutils/internal/tfresource" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceOrganizationalUnit() *schema.Resource { + return &schema.Resource{ + Create: resourceOrganizationalUnitCreate, + Read: resourceOrganizationalUnitRead, + Update: resourceOrganizationalUnitUpdate, + Delete: resourceOrganizationalUnitDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "accounts": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "email": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 128), + }, + "parent_id": { + ForceNew: true, + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringMatch(regexp.MustCompile("^(r-[0-9a-z]{4,32})|(ou-[0-9a-z]{4,32}-[a-z0-9]{8,32})$"), "see https://docs.aws.amazon.com/organizations/latest/APIReference/API_CreateOrganizationalUnit.html#organizations-CreateOrganizationalUnit-request-ParentId"), + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourceOrganizationalUnitCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + // Create the organizational unit + createOpts := &organizations.CreateOrganizationalUnitInput{ + Name: aws.String(d.Get("name").(string)), + ParentId: aws.String(d.Get("parent_id").(string)), + Tags: Tags(tags.IgnoreAWS()), + } + + var err error + var resp *organizations.CreateOrganizationalUnitOutput + err = resource.Retry(4*time.Minute, func() *resource.RetryError { + resp, err = conn.CreateOrganizationalUnit(createOpts) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeFinalizingOrganizationException) { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + return nil + }) + if tfresource.TimedOut(err) { + resp, err = conn.CreateOrganizationalUnit(createOpts) + } + + if err != nil { + return fmt.Errorf("error creating Organizations Organizational Unit: %w", err) + } + + // Store the ID + d.SetId(aws.StringValue(resp.OrganizationalUnit.Id)) + + return resourceOrganizationalUnitRead(d, meta) +} + +func resourceOrganizationalUnitRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + describeOpts := &organizations.DescribeOrganizationalUnitInput{ + OrganizationalUnitId: aws.String(d.Id()), + } + resp, err := conn.DescribeOrganizationalUnit(describeOpts) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, organizations.ErrCodeOrganizationalUnitNotFoundException) { + log.Printf("[WARN] Organizations Organizational Unit (%s) does not exist, removing from state", d.Id()) + d.SetId("") + return nil + } + + if err != nil { + return fmt.Errorf("error reading Organizations Organizational Unit (%s): %w", d.Id(), err) + } + + if resp == nil { + return fmt.Errorf("error reading Organizations Organizational Unit (%s): empty response", d.Id()) + } + + ou := resp.OrganizationalUnit + if ou == nil { + if d.IsNewResource() { + return fmt.Errorf("error reading Organizations Organizational Unit (%s): not found after creation", d.Id()) + } + + log.Printf("[WARN] Organizations Organizational Unit (%s) does not exist, removing from state", d.Id()) + d.SetId("") + return nil + } + + parentId, err := resourceOrganizationalUnitGetParentID(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error listing Organizations Organizational Unit (%s) parents: %w", d.Id(), err) + } + + var accounts []*organizations.Account + input := &organizations.ListAccountsForParentInput{ + ParentId: aws.String(d.Id()), + } + + err = conn.ListAccountsForParentPages(input, func(page *organizations.ListAccountsForParentOutput, lastPage bool) bool { + accounts = append(accounts, page.Accounts...) + + return !lastPage + }) + + if err != nil { + return fmt.Errorf("error listing Organizations Organizational Unit (%s) accounts: %w", d.Id(), err) + } + + if err := d.Set("accounts", flattenOrganizationalUnitAccounts(accounts)); err != nil { + return fmt.Errorf("error setting accounts: %w", err) + } + + d.Set("arn", ou.Arn) + d.Set("name", ou.Name) + d.Set("parent_id", parentId) + + tags, err := ListTags(conn, d.Id()) + + if err != nil { + return fmt.Errorf("error listing tags for Organizations Organizational Unit (%s): %w", d.Id(), err) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return fmt.Errorf("error setting tags: %w", err) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return fmt.Errorf("error setting tags_all: %w", err) + } + + return nil +} + +func resourceOrganizationalUnitUpdate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + if d.HasChange("name") { + updateOpts := &organizations.UpdateOrganizationalUnitInput{ + Name: aws.String(d.Get("name").(string)), + OrganizationalUnitId: aws.String(d.Id()), + } + + _, err := conn.UpdateOrganizationalUnit(updateOpts) + if err != nil { + return fmt.Errorf("error updating Organizations Organizational Unit (%s): %w", d.Id(), err) + } + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + + if err := UpdateTags(conn, d.Id(), o, n); err != nil { + return fmt.Errorf("error updating Organizations Organizational Unit (%s) tags: %w", d.Id(), err) + } + } + + return nil +} + +func resourceOrganizationalUnitDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + input := &organizations.DeleteOrganizationalUnitInput{ + OrganizationalUnitId: aws.String(d.Id()), + } + + _, err := conn.DeleteOrganizationalUnit(input) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeOrganizationalUnitNotFoundException) { + return nil + } + + if err != nil { + return fmt.Errorf("error deleting Organizations Organizational Unit (%s): %w", d.Id(), err) + } + + return nil +} + +func resourceOrganizationalUnitGetParentID(conn *organizations.Organizations, childId string) (string, error) { + input := &organizations.ListParentsInput{ + ChildId: aws.String(childId), + } + var parents []*organizations.Parent + + err := conn.ListParentsPages(input, func(page *organizations.ListParentsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + parents = append(parents, page.Parents...) + + return !lastPage + }) + + if err != nil { + return "", err + } + + if len(parents) == 0 { + return "", nil + } + + // assume there is only a single parent + // https://docs.aws.amazon.com/organizations/latest/APIReference/API_ListParents.html + parent := parents[0] + return aws.StringValue(parent.Id), nil +} + +func flattenOrganizationalUnitAccounts(accounts []*organizations.Account) []map[string]interface{} { + if len(accounts) == 0 { + return nil + } + + var result []map[string]interface{} + + for _, account := range accounts { + result = append(result, map[string]interface{}{ + "arn": aws.StringValue(account.Arn), + "email": aws.StringValue(account.Email), + "id": aws.StringValue(account.Id), + "name": aws.StringValue(account.Name), + }) + } + + return result +} diff --git a/internal/service/organizations/organizational_units_data_source.go b/internal/service/organizations/organizational_units_data_source.go new file mode 100644 index 0000000..846fffe --- /dev/null +++ b/internal/service/organizations/organizational_units_data_source.go @@ -0,0 +1,89 @@ +package organizations + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceOrganizationalUnits() *schema.Resource { + return &schema.Resource{ + Read: dataSourceOrganizationalUnitsRead, + + Schema: map[string]*schema.Schema{ + "parent_id": { + Type: schema.TypeString, + Required: true, + }, + "children": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dataSourceOrganizationalUnitsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + parent_id := d.Get("parent_id").(string) + + params := &organizations.ListOrganizationalUnitsForParentInput{ + ParentId: aws.String(parent_id), + } + + var children []*organizations.OrganizationalUnit + + err := conn.ListOrganizationalUnitsForParentPages(params, + func(page *organizations.ListOrganizationalUnitsForParentOutput, lastPage bool) bool { + children = append(children, page.OrganizationalUnits...) + + return !lastPage + }) + + if err != nil { + return fmt.Errorf("error listing Organizations Organization Units for parent (%s): %w", parent_id, err) + } + + d.SetId(parent_id) + + if err := d.Set("children", FlattenOrganizationalUnits(children)); err != nil { + return fmt.Errorf("error setting children: %w", err) + } + + return nil +} + +func FlattenOrganizationalUnits(ous []*organizations.OrganizationalUnit) []map[string]interface{} { + if len(ous) == 0 { + return nil + } + var result []map[string]interface{} + for _, ou := range ous { + result = append(result, map[string]interface{}{ + "arn": aws.StringValue(ou.Arn), + "id": aws.StringValue(ou.Id), + "name": aws.StringValue(ou.Name), + }) + } + return result +} diff --git a/internal/service/organizations/policy.go b/internal/service/organizations/policy.go new file mode 100644 index 0000000..ec24108 --- /dev/null +++ b/internal/service/organizations/policy.go @@ -0,0 +1,242 @@ +package organizations + +import ( + "context" + "fmt" + "log" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + tftags "github.com/cloudposse/terraform-provider-awsutils/internal/tags" + "github.com/cloudposse/terraform-provider-awsutils/internal/tfresource" + "github.com/cloudposse/terraform-provider-awsutils/internal/verify" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourcePolicy() *schema.Resource { + return &schema.Resource{ + CreateContext: resourcePolicyCreate, + ReadContext: resourcePolicyRead, + UpdateContext: resourcePolicyUpdate, + DeleteContext: resourcePolicyDelete, + Importer: &schema.ResourceImporter{ + StateContext: resourcePolicyImport, + }, + + Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, + "content": { + Type: schema.TypeString, + Required: true, + DiffSuppressFunc: verify.SuppressEquivalentPolicyDiffs, + ValidateFunc: validation.StringIsJSON, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: organizations.PolicyTypeServiceControlPolicy, + ValidateFunc: validation.StringInSlice(organizations.PolicyType_Values(), false), + }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), + }, + + CustomizeDiff: verify.SetTagsDiff, + } +} + +func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + + name := d.Get("name").(string) + + input := &organizations.CreatePolicyInput{ + Content: aws.String(d.Get("content").(string)), + Description: aws.String(d.Get("description").(string)), + Name: aws.String(name), + Type: aws.String(d.Get("type").(string)), + Tags: Tags(tags.IgnoreAWS()), + } + + log.Printf("[DEBUG] Creating Organizations Policy (%s): %v", name, input) + + var err error + var resp *organizations.CreatePolicyOutput + err = resource.Retry(4*time.Minute, func() *resource.RetryError { + resp, err = conn.CreatePolicy(input) + + if err != nil { + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeFinalizingOrganizationException) { + log.Printf("[DEBUG] Retrying creating Organizations Policy (%s): %s", name, err) + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + + return nil + }) + if tfresource.TimedOut(err) { + resp, err = conn.CreatePolicy(input) + } + + if err != nil { + return diag.FromErr(fmt.Errorf("error creating Organizations Policy (%s): %w", name, err)) + } + + d.SetId(aws.StringValue(resp.Policy.PolicySummary.Id)) + + return resourcePolicyRead(ctx, d, meta) +} + +func resourcePolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig + ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig + + input := &organizations.DescribePolicyInput{ + PolicyId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Reading Organizations policy: %s", input) + resp, err := conn.DescribePolicy(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, organizations.ErrCodePolicyNotFoundException) { + log.Printf("[WARN] Organizations policy does not exist, removing from state: %s", d.Id()) + d.SetId("") + return nil + } + return diag.FromErr(fmt.Errorf("error reading Organizations Policy (%s): %w", d.Id(), err)) + } + + if resp.Policy == nil || resp.Policy.PolicySummary == nil { + log.Printf("[WARN] Organizations policy does not exist, removing from state: %s", d.Id()) + d.SetId("") + return nil + } + + d.Set("arn", resp.Policy.PolicySummary.Arn) + d.Set("content", resp.Policy.Content) + d.Set("description", resp.Policy.PolicySummary.Description) + d.Set("name", resp.Policy.PolicySummary.Name) + d.Set("type", resp.Policy.PolicySummary.Type) + + if aws.BoolValue(resp.Policy.PolicySummary.AwsManaged) { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "AWS-managed Organizations policies cannot be imported", + Detail: fmt.Sprintf("This resource should be removed from your Terraform state using `terraform state rm` (https://www.terraform.io/docs/commands/state/rm.html) and references should use the ID (%s) directly.", d.Id()), + }, + } + } + + tags, err := ListTags(conn, d.Id()) + if err != nil { + return diag.FromErr(fmt.Errorf("error listing tags for Organizations policy (%s): %w", d.Id(), err)) + } + + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags: %w", err)) + } + + if err := d.Set("tags_all", tags.Map()); err != nil { + return diag.FromErr(fmt.Errorf("error setting tags_all: %w", err)) + } + + return nil +} + +func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + + input := &organizations.UpdatePolicyInput{ + PolicyId: aws.String(d.Id()), + } + + if d.HasChange("content") { + input.Content = aws.String(d.Get("content").(string)) + } + + if d.HasChange("description") { + input.Description = aws.String(d.Get("description").(string)) + } + + if d.HasChange("name") { + input.Name = aws.String(d.Get("name").(string)) + } + + log.Printf("[DEBUG] Updating Organizations Policy: %s", input) + _, err := conn.UpdatePolicy(input) + if err != nil { + return diag.FromErr(fmt.Errorf("error updating Organizations policy (%s): %w", d.Id(), err)) + } + + if d.HasChange("tags_all") { + o, n := d.GetChange("tags_all") + if err := UpdateTags(conn, d.Id(), o, n); err != nil { + return diag.FromErr(fmt.Errorf("error updating tags for Organizations policy (%s): %w", d.Id(), err)) + } + } + + return resourcePolicyRead(ctx, d, meta) +} + +func resourcePolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.AWSClient).OrganizationsConn + + input := &organizations.DeletePolicyInput{ + PolicyId: aws.String(d.Id()), + } + + log.Printf("[DEBUG] Deleting Organizations Policy: %s", input) + _, err := conn.DeletePolicy(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, organizations.ErrCodePolicyNotFoundException) { + return nil + } + return diag.FromErr(fmt.Errorf("error deleting Organizations policy (%s): %w", d.Id(), err)) + } + return nil +} + +func resourcePolicyImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + conn := meta.(*conns.AWSClient).OrganizationsConn + + input := &organizations.DescribePolicyInput{ + PolicyId: aws.String(d.Id()), + } + resp, err := conn.DescribePolicyWithContext(ctx, input) + if err != nil { + return nil, err + } + + if aws.BoolValue(resp.Policy.PolicySummary.AwsManaged) { + return nil, fmt.Errorf("AWS-managed Organizations policy (%s) cannot be imported. Use the policy ID directly in your configuration.", d.Id()) + } + + return []*schema.ResourceData{d}, nil +} diff --git a/internal/service/organizations/policy_attachment.go b/internal/service/organizations/policy_attachment.go new file mode 100644 index 0000000..c31d340 --- /dev/null +++ b/internal/service/organizations/policy_attachment.go @@ -0,0 +1,160 @@ +package organizations + +import ( + "fmt" + "log" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/cloudposse/terraform-provider-awsutils/internal/tfresource" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func ResourcePolicyAttachment() *schema.Resource { + return &schema.Resource{ + Create: resourcePolicyAttachmentCreate, + Read: resourcePolicyAttachmentRead, + Delete: resourcePolicyAttachmentDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + + Schema: map[string]*schema.Schema{ + "policy_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "target_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + }, + } +} + +func resourcePolicyAttachmentCreate(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + policyID := d.Get("policy_id").(string) + targetID := d.Get("target_id").(string) + + input := &organizations.AttachPolicyInput{ + PolicyId: aws.String(policyID), + TargetId: aws.String(targetID), + } + + log.Printf("[DEBUG] Creating Organizations Policy Attachment: %s", input) + + err := resource.Retry(4*time.Minute, func() *resource.RetryError { + _, err := conn.AttachPolicy(input) + + if err != nil { + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeFinalizingOrganizationException) { + log.Printf("[DEBUG] Trying to create policy attachment again: %q", err.Error()) + return resource.RetryableError(err) + } + + return resource.NonRetryableError(err) + } + + return nil + }) + if tfresource.TimedOut(err) { + _, err = conn.AttachPolicy(input) + } + + if err != nil { + return fmt.Errorf("error creating Organizations Policy Attachment: %s", err) + } + + d.SetId(fmt.Sprintf("%s:%s", targetID, policyID)) + + return resourcePolicyAttachmentRead(d, meta) +} + +func resourcePolicyAttachmentRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + targetID, policyID, err := DecodePolicyAttachmentID(d.Id()) + if err != nil { + return err + } + + input := &organizations.ListTargetsForPolicyInput{ + PolicyId: aws.String(policyID), + } + + log.Printf("[DEBUG] Listing Organizations Policies for Target: %s", input) + var output *organizations.PolicyTargetSummary + + err = conn.ListTargetsForPolicyPages(input, func(page *organizations.ListTargetsForPolicyOutput, lastPage bool) bool { + for _, policySummary := range page.Targets { + if aws.StringValue(policySummary.TargetId) == targetID { + output = policySummary + return true + } + } + return !lastPage + }) + + if err != nil { + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeTargetNotFoundException) { + log.Printf("[WARN] Target does not exist, removing from state: %s", d.Id()) + d.SetId("") + return nil + } + return err + } + + if output == nil { + log.Printf("[WARN] Attachment does not exist, removing from state: %s", d.Id()) + d.SetId("") + return nil + } + + d.Set("policy_id", policyID) + d.Set("target_id", targetID) + return nil +} + +func resourcePolicyAttachmentDelete(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*conns.AWSClient).OrganizationsConn + + targetID, policyID, err := DecodePolicyAttachmentID(d.Id()) + if err != nil { + return err + } + + input := &organizations.DetachPolicyInput{ + PolicyId: aws.String(policyID), + TargetId: aws.String(targetID), + } + + log.Printf("[DEBUG] Detaching Organizations Policy %q from %q", policyID, targetID) + _, err = conn.DetachPolicy(input) + if err != nil { + if tfawserr.ErrCodeEquals(err, organizations.ErrCodePolicyNotFoundException) { + return nil + } + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeTargetNotFoundException) { + return nil + } + return err + } + return nil +} + +func DecodePolicyAttachmentID(id string) (string, string, error) { + idParts := strings.Split(id, ":") + if len(idParts) != 2 { + return "", "", fmt.Errorf("expected ID in format of TARGETID:POLICYID, received: %s", id) + } + return idParts[0], idParts[1], nil +} diff --git a/internal/service/organizations/policy_test.go b/internal/service/organizations/policy_test.go new file mode 100644 index 0000000..8741e94 --- /dev/null +++ b/internal/service/organizations/policy_test.go @@ -0,0 +1,738 @@ +package organizations_test + +import ( + "fmt" + "regexp" + "strconv" + "testing" + + "github.com/aws/aws-sdk-go/service/organizations" + "github.com/cloudposse/terraform-provider-awsutils/internal/acctest" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + tforganizations "github.com/cloudposse/terraform-provider-awsutils/internal/service/organizations" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func testAccPolicy_basic(t *testing.T) { + var policy organizations.Policy + content1 := `{"Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "*", "Resource": "*"}}` + content2 := `{"Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "s3:*", "Resource": "*"}}` + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_required(rName, content1), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + acctest.MatchResourceAttrGlobalARN(resourceName, "arn", "organizations", regexp.MustCompile("policy/o-.+/service_control_policy/p-.+$")), + resource.TestCheckResourceAttr(resourceName, "content", content1), + resource.TestCheckResourceAttr(resourceName, "description", ""), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "type", organizations.PolicyTypeServiceControlPolicy), + ), + }, + { + Config: testAccPolicyConfig_required(rName, content2), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "content", content2), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/5073 +func testAccPolicy_concurrent(t *testing.T) { + var policy1, policy2, policy3, policy4, policy5 organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName1 := "aws_organizations_policy.test1" + resourceName2 := "aws_organizations_policy.test2" + resourceName3 := "aws_organizations_policy.test3" + resourceName4 := "aws_organizations_policy.test4" + resourceName5 := "aws_organizations_policy.test5" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_concurrent(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName1, &policy1), + testAccCheckPolicyExists(resourceName2, &policy2), + testAccCheckPolicyExists(resourceName3, &policy3), + testAccCheckPolicyExists(resourceName4, &policy4), + testAccCheckPolicyExists(resourceName5, &policy5), + ), + }, + }, + }) +} + +func testAccPolicy_description(t *testing.T) { + var policy organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_description(rName, "description1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "description", "description1"), + ), + }, + { + Config: testAccPolicyConfig_description(rName, "description2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "description", "description2"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccPolicy_tags(t *testing.T) { + var p1, p2, p3, p4 organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_tagA(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &p1), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.TerraformProviderAwsTest", "true"), + resource.TestCheckResourceAttr(resourceName, "tags.Alpha", "1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccPolicyConfig_tagB(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &p2), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.TerraformProviderAwsTest", "true"), + resource.TestCheckResourceAttr(resourceName, "tags.Beta", "1"), + ), + }, + { + Config: testAccPolicyConfig_tagC(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &p3), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.TerraformProviderAwsTest", "true"), + ), + }, + { + Config: testAccPolicyConfig_noTag(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &p4), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func testAccPolicy_disappears(t *testing.T) { + var p organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_description(rName, ""), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &p), + acctest.CheckResourceDisappears(acctest.Provider, tforganizations.ResourcePolicy(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccPolicy_type_AI_OPT_OUT(t *testing.T) { + var policy organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + // Reference: https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_ai-opt-out_syntax.html + AiOptOutPolicyContent := `{ "services": { "rekognition": { "opt_out_policy": { "@@assign": "optOut" } }, "lex": { "opt_out_policy": { "@@assign": "optIn" } } } }` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_type(rName, AiOptOutPolicyContent, organizations.PolicyTypeAiservicesOptOutPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "type", organizations.PolicyTypeAiservicesOptOutPolicy), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccPolicy_type_Backup(t *testing.T) { + var policy organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + // Reference: https://docs.aws.amazon.com/organizations/latest/userguide/orgs_manage_policies_backup_syntax.html + backupPolicyContent := fmt.Sprintf(`{ + "plans":{ + "PII_Backup_Plan":{ + "regions":{ + "@@assign":[ + "%[1]s", + "%[2]s" + ] + }, + "rules":{ + "Hourly":{ + "schedule_expression":{ + "@@assign":"cron(0 5/1 ? * * *)" + }, + "start_backup_window_minutes":{ + "@@assign":"480" + }, + "complete_backup_window_minutes":{ + "@@assign":"10080" + }, + "lifecycle":{ + "move_to_cold_storage_after_days":{ + "@@assign":"180" + }, + "delete_after_days":{ + "@@assign":"270" + } + }, + "target_backup_vault_name":{ + "@@assign":"FortKnox" + }, + "copy_actions":{ + "arn:%[3]s:backup:%[1]s:$account:backup-vault:secondary_vault":{ + "target_backup_vault_arn":{ + "@@assign":"arn:%[3]s:backup:%[1]s:$account:backup-vault:secondary_vault" + }, + "lifecycle":{ + "delete_after_days":{ + "@@assign":"100" + }, + "move_to_cold_storage_after_days":{ + "@@assign":"10" + } + } + } + } + } + }, + "selections":{ + "tags":{ + "datatype":{ + "iam_role_arn":{ + "@@assign":"arn:%[3]s:iam::$account:role/MyIamRole" + }, + "tag_key":{ + "@@assign":"dataType" + }, + "tag_value":{ + "@@assign":[ + "PII", + "RED" + ] + } + } + } + } + } + } +}`, acctest.AlternateRegion(), acctest.Region(), acctest.Partition()) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_type(rName, backupPolicyContent, organizations.PolicyTypeBackupPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "type", organizations.PolicyTypeBackupPolicy), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccPolicy_type_SCP(t *testing.T) { + var policy organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + serviceControlPolicyContent := `{"Version": "2012-10-17", "Statement": { "Effect": "Allow", "Action": "*", "Resource": "*"}}` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_type(rName, serviceControlPolicyContent, organizations.PolicyTypeServiceControlPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "type", organizations.PolicyTypeServiceControlPolicy), + ), + }, + { + Config: testAccPolicyConfig_required(rName, serviceControlPolicyContent), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "type", organizations.PolicyTypeServiceControlPolicy), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccPolicy_type_Tag(t *testing.T) { + var policy organizations.Policy + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_organizations_policy.test" + tagPolicyContent := `{ "tags": { "Product": { "tag_key": { "@@assign": "Product" }, "enforced_for": { "@@assign": [ "ec2:instance" ] } } } }` + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_type(rName, tagPolicyContent, organizations.PolicyTypeTagPolicy), + Check: resource.ComposeTestCheckFunc( + testAccCheckPolicyExists(resourceName, &policy), + resource.TestCheckResourceAttr(resourceName, "type", organizations.PolicyTypeTagPolicy), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccPolicy_importManagedPolicy(t *testing.T) { + resourceName := "aws_organizations_policy.test" + + resourceID := "p-FullAWSAccess" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckOrganizationsAccount(t) }, + ErrorCheck: acctest.ErrorCheck(t, organizations.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckPolicyDestroy, + Steps: []resource.TestStep{ + { + Config: testAccPolicyConfig_managedSetup, + }, + { + Config: testAccPolicyConfig_managed, + ResourceName: resourceName, + ImportStateId: resourceID, + ImportState: true, + ExpectError: regexp.MustCompile(regexp.QuoteMeta(fmt.Sprintf("AWS-managed Organizations policy (%s) cannot be imported.", resourceID))), + }, + }, + }) +} + +func testAccCheckPolicyDestroy(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsConn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_organizations_policy" { + continue + } + + input := &organizations.DescribePolicyInput{ + PolicyId: &rs.Primary.ID, + } + + resp, err := conn.DescribePolicy(input) + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeAWSOrganizationsNotInUseException) { + continue + } + + if tfawserr.ErrCodeEquals(err, organizations.ErrCodePolicyNotFoundException) { + continue + } + + if err != nil { + return err + } + + if resp != nil && resp.Policy != nil { + return fmt.Errorf("Policy %q still exists", rs.Primary.ID) + } + } + + return nil + +} + +func testAccCheckPolicyExists(resourceName string, policy *organizations.Policy) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("Not found: %s", resourceName) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).OrganizationsConn + input := &organizations.DescribePolicyInput{ + PolicyId: &rs.Primary.ID, + } + + resp, err := conn.DescribePolicy(input) + + if err != nil { + return err + } + + if resp == nil || resp.Policy == nil { + return fmt.Errorf("Policy %q does not exist", rs.Primary.ID) + } + + *policy = *resp.Policy + + return nil + } +} + +func testAccPolicyConfig_description(rName, description string) string { + return fmt.Sprintf(` +resource "aws_organizations_organization" "test" {} + +resource "aws_organizations_policy" "test" { + content = < 0 { + input := &organizations.UntagResourceInput{ + ResourceId: aws.String(identifier), + TagKeys: aws.StringSlice(removedTags.IgnoreAWS().Keys()), + } + + _, err := conn.UntagResourceWithContext(ctx, input) + + if err != nil { + return fmt.Errorf("untagging resource (%s): %w", identifier, err) + } + } + + if updatedTags := oldTags.Updated(newTags); len(updatedTags) > 0 { + input := &organizations.TagResourceInput{ + ResourceId: aws.String(identifier), + Tags: Tags(updatedTags.IgnoreAWS()), + } + + _, err := conn.TagResourceWithContext(ctx, input) + + if err != nil { + return fmt.Errorf("tagging resource (%s): %w", identifier, err) + } + } + + return nil +} diff --git a/internal/service/sts/README.md b/internal/service/sts/README.md new file mode 100644 index 0000000..81cb204 --- /dev/null +++ b/internal/service/sts/README.md @@ -0,0 +1,11 @@ +# Terraform AWS Provider STS Package + +This area is primarily for AWS provider contributors and maintainers. For information on _using_ Terraform and the AWS provider, see the links below. + + +## Handy Links + +* [Find out about contributing](../../../docs/contributing) to the AWS provider! +* AWS Provider Docs: [Home](https://registry.terraform.io/providers/hashicorp/aws/latest/docs) +* AWS Provider Docs: [One of the STS data sources](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/caller_identity) +* AWS Docs: [AWS SDK for Go STS](https://docs.aws.amazon.com/sdk-for-go/api/service/sts/) diff --git a/internal/service/sts/caller_identity_data_source.go b/internal/service/sts/caller_identity_data_source.go new file mode 100644 index 0000000..c79cead --- /dev/null +++ b/internal/service/sts/caller_identity_data_source.go @@ -0,0 +1,54 @@ +package sts + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/sts" + "github.com/cloudposse/terraform-provider-awsutils/internal/conns" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func DataSourceCallerIdentity() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCallerIdentityRead, + + Schema: map[string]*schema.Schema{ + "account_id": { + Type: schema.TypeString, + Computed: true, + }, + + "arn": { + Type: schema.TypeString, + Computed: true, + }, + + "user_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCallerIdentityRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*conns.AWSClient).STSConn + + log.Printf("[DEBUG] Reading Caller Identity") + res, err := client.GetCallerIdentity(&sts.GetCallerIdentityInput{}) + + if err != nil { + return fmt.Errorf("getting Caller Identity: %w", err) + } + + log.Printf("[DEBUG] Received Caller Identity: %s", res) + + d.SetId(aws.StringValue(res.Account)) + d.Set("account_id", res.Account) + d.Set("arn", res.Arn) + d.Set("user_id", res.UserId) + + return nil +} diff --git a/internal/service/sts/find.go b/internal/service/sts/find.go new file mode 100644 index 0000000..5eacbea --- /dev/null +++ b/internal/service/sts/find.go @@ -0,0 +1,25 @@ +package sts + +import ( + "github.com/aws/aws-sdk-go/service/sts" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func FindCallerIdentity(conn *sts.STS) (*sts.GetCallerIdentityOutput, error) { + input := &sts.GetCallerIdentityInput{} + + output, err := conn.GetCallerIdentity(input) + + if err != nil { + return nil, err + } + + if output == nil { + return nil, &resource.NotFoundError{ + Message: "Empty result", + LastRequest: input, + } + } + + return output, nil +} diff --git a/internal/tfresource/errors.go b/internal/tfresource/errors.go index a87246e..5952e82 100644 --- a/internal/tfresource/errors.go +++ b/internal/tfresource/errors.go @@ -10,7 +10,7 @@ import ( // Specifically, NotFound returns true if the error or a wrapped error is of type // resource.NotFoundError. func NotFound(err error) bool { - var e *resource.NotFoundError // nosemgrep: is-not-found-error + var e *resource.NotFoundError // nosemgrep:ci.is-not-found-error return errors.As(err, &e) } diff --git a/internal/tfresource/retry.go b/internal/tfresource/retry.go index 1fc7e99..4927b8c 100644 --- a/internal/tfresource/retry.go +++ b/internal/tfresource/retry.go @@ -2,11 +2,12 @@ package tfresource import ( "context" + "errors" "math/rand" "sync" "time" - "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) @@ -21,11 +22,12 @@ type Retryable func(error) (bool, error) func RetryWhenContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error), retryable Retryable) (interface{}, error) { var output interface{} - err := resource.Retry(timeout, func() *resource.RetryError { + err := resource.Retry(timeout, func() *resource.RetryError { // nosemgrep:ci.helper-schema-resource-Retry-without-TimeoutError-check var err error + var retry bool output, err = f() - retry, err := retryable(err) + retry, err = retryable(err) if retry { return resource.RetryableError(err) @@ -56,7 +58,7 @@ func RetryWhen(timeout time.Duration, f func() (interface{}, error), retryable R } // RetryWhenAWSErrCodeEqualsContext retries the specified function when it returns one of the specified AWS error code. -func RetryWhenAWSErrCodeEqualsContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error), codes ...string) (interface{}, error) { +func RetryWhenAWSErrCodeEqualsContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error), codes ...string) (interface{}, error) { // nosemgrep:ci.aws-in-func-name return RetryWhenContext(ctx, timeout, f, func(err error) (bool, error) { if tfawserr.ErrCodeEquals(err, codes...) { return true, err @@ -67,10 +69,48 @@ func RetryWhenAWSErrCodeEqualsContext(ctx context.Context, timeout time.Duration } // RetryWhenAWSErrCodeEquals retries the specified function when it returns one of the specified AWS error code. -func RetryWhenAWSErrCodeEquals(timeout time.Duration, f func() (interface{}, error), codes ...string) (interface{}, error) { +func RetryWhenAWSErrCodeEquals(timeout time.Duration, f func() (interface{}, error), codes ...string) (interface{}, error) { // nosemgrep:ci.aws-in-func-name return RetryWhenAWSErrCodeEqualsContext(context.Background(), timeout, f, codes...) } +// RetryWhenAWSErrMessageContainsContext retries the specified function when it returns an AWS error containing the specified message. +func RetryWhenAWSErrMessageContainsContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error), code, message string) (interface{}, error) { // nosemgrep:ci.aws-in-func-name + return RetryWhenContext(ctx, timeout, f, func(err error) (bool, error) { + if tfawserr.ErrMessageContains(err, code, message) { + return true, err + } + + return false, err + }) +} + +// RetryWhenAWSErrMessageContains retries the specified function when it returns an AWS error containing the specified message. +func RetryWhenAWSErrMessageContains(timeout time.Duration, f func() (interface{}, error), code, message string) (interface{}, error) { // nosemgrep:ci.aws-in-func-name + return RetryWhenAWSErrMessageContainsContext(context.Background(), timeout, f, code, message) +} + +var errFoundResource = errors.New(`found resource`) + +// RetryUntilNotFoundContext retries the specified function until it returns a resource.NotFoundError. +func RetryUntilNotFoundContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error)) (interface{}, error) { + return RetryWhenContext(ctx, timeout, f, func(err error) (bool, error) { + if NotFound(err) { + return false, nil + } + + if err != nil { + return false, err + } + + return true, errFoundResource + }) +} + +// RetryUntilNotFound retries the specified function until it returns a resource.NotFoundError. +func RetryUntilNotFound(timeout time.Duration, f func() (interface{}, error)) (interface{}, error) { + return RetryUntilNotFoundContext(context.Background(), timeout, f) +} + // RetryWhenNotFoundContext retries the specified function when it returns a resource.NotFoundError. func RetryWhenNotFoundContext(ctx context.Context, timeout time.Duration, f func() (interface{}, error)) (interface{}, error) { return RetryWhenContext(ctx, timeout, f, func(err error) (bool, error) { diff --git a/internal/tfresource/retry_test.go b/internal/tfresource/retry_test.go index 6785738..39f395f 100644 --- a/internal/tfresource/retry_test.go +++ b/internal/tfresource/retry_test.go @@ -13,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) -func TestRetryWhenAWSErrCodeEquals(t *testing.T) { +func TestRetryWhenAWSErrCodeEquals(t *testing.T) { // nosemgrep:ci.aws-in-func-name var retryCount int32 testCases := []struct { @@ -75,6 +75,68 @@ func TestRetryWhenAWSErrCodeEquals(t *testing.T) { } } +func TestRetryWhenAWSErrMessageContains(t *testing.T) { // nosemgrep:ci.aws-in-func-name + var retryCount int32 + + testCases := []struct { + Name string + F func() (interface{}, error) + ExpectError bool + }{ + { + Name: "no error", + F: func() (interface{}, error) { + return nil, nil + }, + }, + { + Name: "non-retryable other error", + F: func() (interface{}, error) { + return nil, errors.New("TestCode") + }, + ExpectError: true, + }, + { + Name: "non-retryable AWS error", + F: func() (interface{}, error) { + return nil, awserr.New("TestCode1", "Testing", nil) + }, + ExpectError: true, + }, + { + Name: "retryable AWS error timeout", + F: func() (interface{}, error) { + return nil, awserr.New("TestCode1", "TestMessage1", nil) + }, + ExpectError: true, + }, + { + Name: "retryable AWS error success", + F: func() (interface{}, error) { + if atomic.CompareAndSwapInt32(&retryCount, 0, 1) { + return nil, awserr.New("TestCode1", "TestMessage1", nil) + } + + return nil, nil + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + retryCount = 0 + + _, err := tfresource.RetryWhenAWSErrMessageContains(5*time.Second, testCase.F, "TestCode1", "TestMessage1") + + if testCase.ExpectError && err == nil { + t.Fatal("expected error") + } else if !testCase.ExpectError && err != nil { + t.Fatalf("unexpected error: %s", err) + } + }) + } +} + func TestRetryWhenNewResourceNotFound(t *testing.T) { var retryCount int32 @@ -224,6 +286,68 @@ func TestRetryWhenNotFound(t *testing.T) { } } +func TestRetryUntilNotFound(t *testing.T) { + var retryCount int32 + + testCases := []struct { + Name string + F func() (interface{}, error) + ExpectError bool + }{ + { + Name: "no error", + F: func() (interface{}, error) { + return nil, nil + }, + ExpectError: true, + }, + { + Name: "other error", + F: func() (interface{}, error) { + return nil, errors.New("TestCode") + }, + ExpectError: true, + }, + { + Name: "AWS error", + F: func() (interface{}, error) { + return nil, awserr.New("Testing", "Testing", nil) + }, + ExpectError: true, + }, + { + Name: "NotFoundError", + F: func() (interface{}, error) { + return nil, &resource.NotFoundError{} + }, + }, + { + Name: "retryable NotFoundError", + F: func() (interface{}, error) { + if atomic.CompareAndSwapInt32(&retryCount, 0, 1) { + return nil, nil + } + + return nil, &resource.NotFoundError{} + }, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.Name, func(t *testing.T) { + retryCount = 0 + + _, err := tfresource.RetryUntilNotFound(5*time.Second, testCase.F) + + if testCase.ExpectError && err == nil { + t.Fatal("expected error") + } else if !testCase.ExpectError && err != nil { + t.Fatalf("unexpected error: %s", err) + } + }) + } +} + func TestRetryConfigContext_error(t *testing.T) { t.Parallel() diff --git a/main.go b/main.go index 6ff8143..b6d6b80 100644 --- a/main.go +++ b/main.go @@ -6,28 +6,36 @@ import ( "log" "github.com/cloudposse/terraform-provider-awsutils/internal/provider" - "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" -) - -var ( - version = "dev" + "github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server" ) func main() { - var debugMode bool - - flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") + debugFlag := flag.Bool("debug", false, "Start provider in debug mode.") flag.Parse() - opts := &plugin.ServeOpts{ProviderFunc: provider.Provider} + serverFactory, err := provider.ProtoV5ProviderServerFactory(context.Background()) - if debugMode { - err := plugin.Debug(context.Background(), "registry.terraform.io/cloudposse/awsutils", opts) - if err != nil { - log.Fatal(err.Error()) - } - return + if err != nil { + log.Fatal(err) } - plugin.Serve(opts) + var serveOpts []tf5server.ServeOpt + + if *debugFlag { + serveOpts = append(serveOpts, tf5server.WithManagedDebug()) + } + + logFlags := log.Flags() + logFlags = logFlags &^ (log.Ldate | log.Ltime) + log.SetFlags(logFlags) + + err = tf5server.Serve( + "registry.terraform.io/cloudposse/awsutils", + serverFactory, + serveOpts..., + ) + + if err != nil { + log.Fatal(err) + } } diff --git a/names/README.md b/names/README.md new file mode 100644 index 0000000..0af1d8e --- /dev/null +++ b/names/README.md @@ -0,0 +1,46 @@ +# names + +Package `names` provides AWS service-name information that is critical to the Terraform AWS Provider working correctly. If you are unsure about a change you are making, please do not hesitate to ask! + +**NOTE:** The information in `names_data.csv` affects the provider, generators, documentation, website navigation, etc. working correctly. _Please do not make any changes until you understand the table below._ + +The core of the `names` package is `names_data.csv`, which contains raw, comma-separated data about naming in the AWS Provider, AWS Go SDKs v1 and v2, and AWS CLI. The file is dynamically embedded at build time in the AWS Provider and referenced by generators when generating code. _The information it contains must be correct._ Please double-check any changes. + +Consumers of `names` include: + +* Package `provider` (`internal/provider`) +* Package `conns` (`internal/conns`) +* AWS Provider generators +* `skaff` tool + +After any edits to `names_data.csv`, run `make gen`. Doing so regenerates code and performs checks on `names_data.csv`. + +The columns of `names_data.csv` are as follows: + +| Index | Name | Use | Description | +| --- | --- | --- | --- | +| 0 | **AWSCLIV2Command** | Reference | Service command in [AWS CLI v2](https://awscli.amazonaws.com/v2/documentation/api/latest/index.html) | +| 1 | **AWSCLIV2CommandNoDashes** | Reference | Same as **AWSCLIV2Command** without dashes | +| 2 | **GoV1Package** | Code | [AWS SDK for Go v1](https://docs.aws.amazon.com/sdk-for-go/api/) package name | +| 3 | **GoV2Package** | Code | [AWS SDK for Go v2](https://pkg.go.dev/github.com/aws/aws-sdk-go-v2) package name | +| 4 | **ProviderPackageActual** | Code | Actual TF AWS provide package name _if_ **ProviderPackageCorrect** is not used; takes precedence over **ProviderPackageCorrect** if both are defined | +| 5 | **ProviderPackageCorrect** | Code | Shorter of **AWSCLIV2CommandNoDashes** and **GoV2Package**; should _not_ be blank if either exists; same as [Service Identifier](https://github.com/hashicorp/terraform-provider-aws/blob/main/docs/contributing/naming.md#service-identifier); what the TF AWS Provider package name _should be_; **ProviderPackageActual** takes precedence | +| 6 | **SplitPackageRealPackage** | Code | If multiple "services" live in one service, this is the package where the service's Go files live (_e.g._, VPC is part of EC2) | +| 7 | **Aliases** | Code | _Semicolon_-separated list of name variations (_e.g._, for "AMP", `prometheus;prometheusservice`). Do not include **ProviderPackageActual** (or **ProviderPackageCorrect**, if blank) since that will create duplicates in the [Custom Endpoints guide](https://registry.terraform.io/providers/hashicorp/aws/latest/docs/guides/custom-service-endpoints). | +| 8 | **ProviderNameUpper** | Code | [Correctly capitalized](https://github.com/hashicorp/terraform-provider-aws/blob/main/docs/contributing/naming.md#mixedcaps) **ProviderPackageActual**, if it exists, otherwise **ProviderPackageCorrect** | +| 9 | **GoV1ClientTypeName** | Code | _Exact name_ (_i.e._, spelling and capitalization) of the AWS SDK for Go v1 client type (_e.g._, see the [`New()` return type](https://docs.aws.amazon.com/sdk-for-go/api/service/ses/#New) for SES) | +| 10 | **SkipClientGenerate** | Code | Some service clients need special configuration rather than the default generated configuration; use a non-empty value to skip generation but you must then manually configure the client in `internal/conns/config.go` | +| 11 | **SDKVersion** | Code | Whether, in the TF AWS Provider, the service currently uses AWS SDK for Go v1 or v2; use `1` or `2` | +| 12 | **ResourcePrefixActual** | Code | Regular expression to match anomalous TF resource name prefixes (_e.g._, for the resource name `aws_config_config_rule`, `aws_config_` will match all resources); only use if **ResourcePrefixCorrect** is not suitable (_e.g._, `aws_codepipeline_` won't work as there is only one resource named `aws_codepipeline`); takes precedence over **ResourcePrefixCorrect** | +| 13 | **ResourcePrefixCorrect** | Code | Regular expression to match what resource name prefixes _should be_ (_i.e._, `aws_` + **ProviderPackageCorrect** + `_`); used if **ResourcePrefixActual** is blank | +| 14 | **FilePrefix** | Code | If multiple "services" live in one service, this is the prefix that files must have to be associated with this sub-service (_e.g._, VPC files in the EC2 service are prefixed with `vpc_`); see also **SplitPackageRealPackage** | +| 15 | **DocPrefix** | Code | _Semicolon_-separated list of prefixes for service documentation files in `website/docs/r` and `website/docs/d`; usually only one prefix, _i.e._, `<**ProviderPackageCorrect**>_` | +| 16 | **HumanFriendly** | Code | [REQUIRED] Human-friendly name of service as used by AWS; documentation `subcategory` must exactly match this value; used in website navigation and error messages | +| 17 | **Brand** | Code | Either `Amazon`, `AWS`, or blank (rare) as used by AWS; used in error messages | +| 18 | **Exclude** | Code | Whether or not the service should be included; if included (blank), **ProviderPackageActual** or **ProviderPackageCorrect** must have a value | +| 19 | **AllowedSubcategory** | Code | If **Exclude** is non-blank, whether to include **HumanFriendly** in `website/allowed-subcategories.txt` anyway. In other words, if non-blank, overrides **Exclude** in some situations. Some excluded pseudo-services (_e.g._, VPC is part of EC2) are still subcategories. Only applies if **Exclude** is non-blank. | +| 20 | **DeprecatedEnvVar** | Code | Deprecated environment variable name | +| 21 | **EnvVar** | Code | Current environment variable associated with service | +| 22 | **Note** | Reference | Very brief note usually to explain why excluded | + +For more information about service naming, see [the Naming Guide](https://github.com/hashicorp/terraform-provider-aws/blob/main/docs/contributing/naming.md#service-identifier). diff --git a/names/caps.csv b/names/caps.csv new file mode 100644 index 0000000..497a666 --- /dev/null +++ b/names/caps.csv @@ -0,0 +1,125 @@ +Wrong,Right +Acl,ACL +Acm,ACM +Acmpca,ACMPCA +AcmPca,ACMPCA +Ami,AMI +Api,API +ApiGateway,APIGateway +Appconfig,AppConfig +Appmesh,AppMesh +Appsync,AppSync +Arn,ARN +Asg,ASG +Asn,ASN +Autoscaling,AutoScaling +Bgp,BGP +Byoip,BYOIP +Cidr,CIDR +Cloudformation,CloudFormation +Cloudfront,CloudFront +Cloudwatch,CloudWatch +Cmk,CMK +Cname,CNAME +Coip,CoIP +Cpu,CPU +Css,CSS +Csv,CSV +Dax,DAX +Db,DB +Dhcp,DHCP +Dkim,DKIM +Dlm,DLM +Dms,DMS +Dns,DNS +Dnssec,DNSSEC +DocDb,DocDB +Docdb,DocDB +Dynamodb,DynamoDB +DynamoDb,DynamoDB +Ebs,EBS +Ec2,EC2 +Ecmp,ECMP +Ecr,ECR +Ecs,ECS +Efs,EFS +Eip,EIP +Eks,EKS +Elasticache,ElastiCache +ElasticSearch,Elasticsearch +Elb,ELB +Emr,EMR +Fifo,FIFO +Fms,FMS +Fqdns,FQDNS +Fsx,FSx +FSX,FSx +Gamelift,GameLift +Gcm,GCM +Gp2,GP2 +Gp3,GP3 +Graphql,GraphQL +Grpc,GRPC +Guardduty,GuardDuty +Haproxy,HAProxy +Hsm,HSM +Http,HTTP +Https,HTTPS +Hvm,HVM +Iam,IAM +Iot,IoT +Ip,IP +Ipam,IPAM +Ipset,IPSet +Iscsi,iSCSI +Jdbc,JDBC +Json,JSON +Kms,KMS +Mfa,MFA +Msk,MSK +Mwaa,MWAA +Mysql,MySQL +Nfs,NFS +Oauth,OAuth +Oidc,OIDC +Opsworks,OpsWorks +Php,PHP +Pitr,PITR +Posix,POSIX +Precheck,PreCheck +Qldb,QLDB +Rabbitmq,RabbitMQ +Rds,RDS +Rfc,RFC +Sagemaker,SageMaker +Sasl,SASL +Sfn,SFN +Smb,SMB +Sms,SMS +Smtp,SMTP +Sns,SNS +Sql,SQL +Sqs,SQS +Ssh,SSH +Ssl,SSL +Ssm,SSM +Sso,SSO +Sts,STS +Swf,SWF +Tcp,TCP +Tls,TLS +Ttl,TTL +Uri,URI +Url,URL +Vgw,VGW +Voip,VoIP +Vpc,VPC +Vpn,VPN +Waf,WAF +Wafv2,WAFV2 +Workgroup,WorkGroup +Worklink,WorkLink +Workspaces,WorkSpaces +Xray,XRay +Xss,XSS +Yaml,YAML \ No newline at end of file diff --git a/names/caps.md b/names/caps.md new file mode 100644 index 0000000..209e952 --- /dev/null +++ b/names/caps.md @@ -0,0 +1,152 @@ +# Caps List + + +In Terraform AWS Provider code, we have two guiding principles for capitalization in function, constant, and variable names: + +1. We use [Go-idiomatic MixedCaps](https://go.dev/doc/effective_go#mixed-caps), with [idiomatic treatment of initialisms](https://github.com/golang/go/wiki/CodeReviewComments#initialisms). +2. We follow AWS's preferred capitalization for service names (_e.g._, ["SageMaker"](https://aws.amazon.com/sagemaker/) rather than "Sagemaker"). + +For _principle 1_, the [golang/go Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments#initialisms) are instructive: + +> Words in names that are initialisms or acronyms (e.g. "URL" or "NATO") have a consistent case. For example, "URL" should appear as "URL" or "url" (as in "urlPony", or "URLPony"), never as "Url". As an example: ServeHTTP not ServeHttp. For identifiers with multiple initialized "words", use for example "xmlHTTPRequest" or "XMLHTTPRequest". + +> This rule also applies to "ID" when it is short for "identifier" (which is pretty much all cases when it's not the "id" as in "ego", "superego"), so write "appID" instead of "appId". + +For _principle 2_, take a look at the AWS website for a service to see the capitalization that AWS prefers. For example, [GameLift](https://aws.amazon.com/gamelift/) rather than "Gamelift". + +To help enforce these principles, we are using [semgrep](https://semgrep.dev/) rules. Below, you will find a list of the specific caps cases we look for currently. This list is generated from the [same source](caps.csv) as the semgrep rules. + +**NOTE:** Please do NOT add "Id" to the list. Use "ID" or "id", never "Id", but we have found linter enforcement of this initialism to be too error prone because of the number of false positives for "Id" (_e.g._, "Identifier"). + +**NOTE:** Capitalization rules are all found in the ".semgrep-caps-aws-ec2.yml" configuration file are named `-in-func-name`, `-in-var-name`, or `-in-const-name`, where `Test#` comes from the table below. + +**NOTE:** The `Test#` order is _wonky_ because we need to match on longer names first. In the future, with an option to automatically correct problems, fixing the longest possible initialism first is important (_e.g._, `HTTPS` needs to be fixed before `HTTP` to avoid something like `HTTPs`). + +The caps enforced are as follows: + +| Wrong | Right | Test# | +| --- | --- | --- | +| Acl | ACL | caps1 | +| Acm | ACM | caps1 | +| AcmPca | ACMPCA | caps0 | +| Acmpca | ACMPCA | caps0 | +| Ami | AMI | caps1 | +| Api | API | caps1 | +| ApiGateway | APIGateway | caps0 | +| Appconfig | AppConfig | caps0 | +| Appmesh | AppMesh | caps0 | +| Appsync | AppSync | caps0 | +| Arn | ARN | caps2 | +| Asg | ASG | caps2 | +| Asn | ASN | caps2 | +| Autoscaling | AutoScaling | caps0 | +| Bgp | BGP | caps2 | +| Byoip | BYOIP | caps0 | +| Cidr | CIDR | caps1 | +| Cloudformation | CloudFormation | caps0 | +| Cloudfront | CloudFront | caps0 | +| Cloudwatch | CloudWatch | caps0 | +| Cmk | CMK | caps2 | +| Cname | CNAME | caps0 | +| Coip | CoIP | caps1 | +| Cpu | CPU | caps2 | +| Css | CSS | caps2 | +| Csv | CSV | caps2 | +| Dax | DAX | caps2 | +| Db | DB | caps3 | +| Dhcp | DHCP | caps1 | +| Dkim | DKIM | caps1 | +| Dlm | DLM | caps2 | +| Dms | DMS | caps2 | +| Dns | DNS | caps2 | +| Dnssec | DNSSEC | caps0 | +| DocDb | DocDB | caps0 | +| Docdb | DocDB | caps0 | +| DynamoDb | DynamoDB | caps0 | +| Dynamodb | DynamoDB | caps0 | +| Ebs | EBS | caps2 | +| Ec2 | EC2 | caps2 | +| Ecmp | ECMP | caps1 | +| Ecr | ECR | caps2 | +| Ecs | ECS | caps2 | +| Efs | EFS | caps2 | +| Eip | EIP | caps2 | +| Eks | EKS | caps2 | +| Elasticache | ElastiCache | caps0 | +| ElasticSearch | Elasticsearch | caps0 | +| Elb | ELB | caps2 | +| Emr | EMR | caps2 | +| Fifo | FIFO | caps1 | +| Fms | FMS | caps2 | +| Fqdns | FQDNS | caps0 | +| FSX | FSx | caps2 | +| Fsx | FSx | caps2 | +| Gamelift | GameLift | caps0 | +| Gcm | GCM | caps2 | +| Gp2 | GP2 | caps2 | +| Gp3 | GP3 | caps2 | +| Graphql | GraphQL | caps0 | +| Grpc | GRPC | caps1 | +| Guardduty | GuardDuty | caps0 | +| Haproxy | HAProxy | caps0 | +| Hsm | HSM | caps2 | +| Http | HTTP | caps1 | +| Https | HTTPS | caps1 | +| Hvm | HVM | caps2 | +| Iam | IAM | caps2 | +| Iot | IoT | caps2 | +| Ip | IP | caps3 | +| Ipam | IPAM | caps1 | +| Ipset | IPSet | caps1 | +| Iscsi | iSCSI | caps1 | +| Jdbc | JDBC | caps1 | +| Json | JSON | caps1 | +| Kms | KMS | caps3 | +| Mfa | MFA | caps3 | +| Msk | MSK | caps3 | +| Mwaa | MWAA | caps1 | +| Mysql | MySQL | caps1 | +| Nfs | NFS | caps3 | +| Oauth | OAuth | caps1 | +| Oidc | OIDC | caps1 | +| Opsworks | OpsWorks | caps0 | +| Php | PHP | caps3 | +| Pitr | PITR | caps1 | +| Posix | POSIX | caps1 | +| Precheck | PreCheck | caps0 | +| Qldb | QLDB | caps1 | +| Rabbitmq | RabbitMQ | caps0 | +| Rds | RDS | caps3 | +| Rfc | RFC | caps3 | +| Sagemaker | SageMaker | caps0 | +| Sasl | SASL | caps1 | +| Sfn | SFN | caps3 | +| Smb | SMB | caps3 | +| Sms | SMS | caps3 | +| Smtp | SMTP | caps1 | +| Sns | SNS | caps3 | +| Sql | SQL | caps3 | +| Sqs | SQS | caps3 | +| Ssh | SSH | caps3 | +| Ssl | SSL | caps3 | +| Ssm | SSM | caps3 | +| Sso | SSO | caps3 | +| Sts | STS | caps3 | +| Swf | SWF | caps3 | +| Tcp | TCP | caps3 | +| Tls | TLS | caps3 | +| Ttl | TTL | caps3 | +| Uri | URI | caps3 | +| Url | URL | caps3 | +| Vgw | VGW | caps3 | +| Voip | VoIP | caps1 | +| Vpc | VPC | caps3 | +| Vpn | VPN | caps3 | +| Waf | WAF | caps3 | +| Wafv2 | WAFV2 | caps1 | +| Workgroup | WorkGroup | caps0 | +| Worklink | WorkLink | caps0 | +| Workspaces | WorkSpaces | caps0 | +| Xray | XRay | caps1 | +| Xss | XSS | caps3 | +| Yaml | YAML | caps1 | diff --git a/names/columns.go b/names/columns.go new file mode 100644 index 0000000..ca9e3c0 --- /dev/null +++ b/names/columns.go @@ -0,0 +1,27 @@ +package names + +const ( + ColAWSCLIV2Command = 0 + ColAWSCLIV2CommandNoDashes = 1 + ColGoV1Package = 2 + ColGoV2Package = 3 + ColProviderPackageActual = 4 + ColProviderPackageCorrect = 5 + ColSplitPackageRealPackage = 6 + ColAliases = 7 + ColProviderNameUpper = 8 + ColGoV1ClientTypeName = 9 + ColSkipClientGenerate = 10 + ColSDKVersion = 11 + ColResourcePrefixActual = 12 + ColResourcePrefixCorrect = 13 + ColFilePrefix = 14 + ColDocPrefix = 15 + ColHumanFriendly = 16 + ColBrand = 17 + ColExclude = 18 + ColAllowedSubcategory = 19 + ColDeprecatedEnvVar = 20 + ColEnvVar = 21 + ColNote = 22 +) diff --git a/names/consts_gen.go b/names/consts_gen.go new file mode 100644 index 0000000..3131d97 --- /dev/null +++ b/names/consts_gen.go @@ -0,0 +1,307 @@ +// Code generated by internal/generate/namesconsts/main.go; DO NOT EDIT. +package names + +const ( + ACM = "acm" + ACMPCA = "acmpca" + AMP = "amp" + APIGateway = "apigateway" + APIGatewayManagementAPI = "apigatewaymanagementapi" + APIGatewayV2 = "apigatewayv2" + AccessAnalyzer = "accessanalyzer" + Account = "account" + AlexaForBusiness = "alexaforbusiness" + Amplify = "amplify" + AmplifyBackend = "amplifybackend" + AmplifyUIBuilder = "amplifyuibuilder" + AppAutoScaling = "appautoscaling" + AppConfig = "appconfig" + AppConfigData = "appconfigdata" + AppFlow = "appflow" + AppIntegrations = "appintegrations" + AppMesh = "appmesh" + AppRunner = "apprunner" + AppStream = "appstream" + AppSync = "appsync" + ApplicationCostProfiler = "applicationcostprofiler" + ApplicationInsights = "applicationinsights" + Athena = "athena" + AuditManager = "auditmanager" + AutoScaling = "autoscaling" + AutoScalingPlans = "autoscalingplans" + Backup = "backup" + BackupGateway = "backupgateway" + Batch = "batch" + BillingConductor = "billingconductor" + Braket = "braket" + Budgets = "budgets" + CE = "ce" + CUR = "cur" + Chime = "chime" + ChimeSDKIdentity = "chimesdkidentity" + ChimeSDKMeetings = "chimesdkmeetings" + ChimeSDKMessaging = "chimesdkmessaging" + Cloud9 = "cloud9" + CloudControl = "cloudcontrol" + CloudDirectory = "clouddirectory" + CloudFormation = "cloudformation" + CloudFront = "cloudfront" + CloudHSMV2 = "cloudhsmv2" + CloudSearch = "cloudsearch" + CloudSearchDomain = "cloudsearchdomain" + CloudTrail = "cloudtrail" + CloudWatch = "cloudwatch" + CodeArtifact = "codeartifact" + CodeBuild = "codebuild" + CodeCommit = "codecommit" + CodeGuruProfiler = "codeguruprofiler" + CodeGuruReviewer = "codegurureviewer" + CodePipeline = "codepipeline" + CodeStar = "codestar" + CodeStarConnections = "codestarconnections" + CodeStarNotifications = "codestarnotifications" + CognitoIDP = "cognitoidp" + CognitoIdentity = "cognitoidentity" + CognitoSync = "cognitosync" + Comprehend = "comprehend" + ComprehendMedical = "comprehendmedical" + ComputeOptimizer = "computeoptimizer" + ConfigService = "configservice" + Connect = "connect" + ConnectContactLens = "connectcontactlens" + ConnectParticipant = "connectparticipant" + CustomerProfiles = "customerprofiles" + DAX = "dax" + DLM = "dlm" + DMS = "dms" + DRS = "drs" + DS = "ds" + DataBrew = "databrew" + DataExchange = "dataexchange" + DataPipeline = "datapipeline" + DataSync = "datasync" + Deploy = "deploy" + Detective = "detective" + DevOpsGuru = "devopsguru" + DeviceFarm = "devicefarm" + DirectConnect = "directconnect" + Discovery = "discovery" + DocDB = "docdb" + DynamoDB = "dynamodb" + DynamoDBStreams = "dynamodbstreams" + EBS = "ebs" + EC2 = "ec2" + EC2InstanceConnect = "ec2instanceconnect" + ECR = "ecr" + ECRPublic = "ecrpublic" + ECS = "ecs" + EFS = "efs" + EKS = "eks" + ELB = "elb" + ELBV2 = "elbv2" + EMR = "emr" + EMRContainers = "emrcontainers" + EMRServerless = "emrserverless" + ElastiCache = "elasticache" + ElasticBeanstalk = "elasticbeanstalk" + ElasticInference = "elasticinference" + ElasticTranscoder = "elastictranscoder" + Elasticsearch = "elasticsearch" + Events = "events" + Evidently = "evidently" + FIS = "fis" + FMS = "fms" + FSx = "fsx" + FinSpace = "finspace" + FinSpaceData = "finspacedata" + Firehose = "firehose" + Forecast = "forecast" + ForecastQuery = "forecastquery" + FraudDetector = "frauddetector" + GameLift = "gamelift" + Glacier = "glacier" + GlobalAccelerator = "globalaccelerator" + Glue = "glue" + Grafana = "grafana" + Greengrass = "greengrass" + GreengrassV2 = "greengrassv2" + GroundStation = "groundstation" + GuardDuty = "guardduty" + Health = "health" + HealthLake = "healthlake" + Honeycode = "honeycode" + IAM = "iam" + IVS = "ivs" + IdentityStore = "identitystore" + ImageBuilder = "imagebuilder" + Inspector = "inspector" + Inspector2 = "inspector2" + IoT = "iot" + IoT1ClickDevices = "iot1clickdevices" + IoT1ClickProjects = "iot1clickprojects" + IoTAnalytics = "iotanalytics" + IoTData = "iotdata" + IoTDeviceAdvisor = "iotdeviceadvisor" + IoTEvents = "iotevents" + IoTEventsData = "ioteventsdata" + IoTFleetHub = "iotfleethub" + IoTJobsData = "iotjobsdata" + IoTSecureTunneling = "iotsecuretunneling" + IoTSiteWise = "iotsitewise" + IoTThingsGraph = "iotthingsgraph" + IoTTwinMaker = "iottwinmaker" + IoTWireless = "iotwireless" + KMS = "kms" + Kafka = "kafka" + KafkaConnect = "kafkaconnect" + Kendra = "kendra" + Keyspaces = "keyspaces" + Kinesis = "kinesis" + KinesisAnalytics = "kinesisanalytics" + KinesisAnalyticsV2 = "kinesisanalyticsv2" + KinesisVideo = "kinesisvideo" + KinesisVideoArchivedMedia = "kinesisvideoarchivedmedia" + KinesisVideoMedia = "kinesisvideomedia" + KinesisVideoSignaling = "kinesisvideosignaling" + LakeFormation = "lakeformation" + Lambda = "lambda" + LexModels = "lexmodels" + LexModelsV2 = "lexmodelsv2" + LexRuntime = "lexruntime" + LexRuntimeV2 = "lexruntimev2" + LicenseManager = "licensemanager" + Lightsail = "lightsail" + Location = "location" + Logs = "logs" + LookoutEquipment = "lookoutequipment" + LookoutMetrics = "lookoutmetrics" + LookoutVision = "lookoutvision" + MQ = "mq" + MTurk = "mturk" + MWAA = "mwaa" + MachineLearning = "machinelearning" + Macie = "macie" + Macie2 = "macie2" + ManagedBlockchain = "managedblockchain" + MarketplaceCatalog = "marketplacecatalog" + MarketplaceCommerceAnalytics = "marketplacecommerceanalytics" + MarketplaceEntitlement = "marketplaceentitlement" + MarketplaceMetering = "marketplacemetering" + MediaConnect = "mediaconnect" + MediaConvert = "mediaconvert" + MediaLive = "medialive" + MediaPackage = "mediapackage" + MediaPackageVOD = "mediapackagevod" + MediaStore = "mediastore" + MediaStoreData = "mediastoredata" + MediaTailor = "mediatailor" + MemoryDB = "memorydb" + MgH = "mgh" + Mgn = "mgn" + MigrationHubConfig = "migrationhubconfig" + MigrationHubRefactorSpaces = "migrationhubrefactorspaces" + MigrationHubStrategy = "migrationhubstrategy" + Mobile = "mobile" + Neptune = "neptune" + NetworkFirewall = "networkfirewall" + NetworkManager = "networkmanager" + Nimble = "nimble" + OpenSearch = "opensearch" + OpsWorks = "opsworks" + OpsWorksCM = "opsworkscm" + Organizations = "organizations" + Outposts = "outposts" + PI = "pi" + Panorama = "panorama" + Personalize = "personalize" + PersonalizeEvents = "personalizeevents" + PersonalizeRuntime = "personalizeruntime" + Pinpoint = "pinpoint" + PinpointEmail = "pinpointemail" + PinpointSMSVoice = "pinpointsmsvoice" + Polly = "polly" + Pricing = "pricing" + Proton = "proton" + QLDB = "qldb" + QLDBSession = "qldbsession" + QuickSight = "quicksight" + RAM = "ram" + RBin = "rbin" + RDS = "rds" + RDSData = "rdsdata" + RUM = "rum" + Redshift = "redshift" + RedshiftData = "redshiftdata" + RedshiftServerless = "redshiftserverless" + Rekognition = "rekognition" + ResilienceHub = "resiliencehub" + ResourceGroups = "resourcegroups" + ResourceGroupsTaggingAPI = "resourcegroupstaggingapi" + RoboMaker = "robomaker" + RolesAnywhere = "rolesanywhere" + Route53 = "route53" + Route53Domains = "route53domains" + Route53RecoveryCluster = "route53recoverycluster" + Route53RecoveryControlConfig = "route53recoverycontrolconfig" + Route53RecoveryReadiness = "route53recoveryreadiness" + Route53Resolver = "route53resolver" + S3 = "s3" + S3Control = "s3control" + S3Outposts = "s3outposts" + SES = "ses" + SESV2 = "sesv2" + SFN = "sfn" + SMS = "sms" + SNS = "sns" + SQS = "sqs" + SSM = "ssm" + SSMContacts = "ssmcontacts" + SSMIncidents = "ssmincidents" + SSO = "sso" + SSOAdmin = "ssoadmin" + SSOOIDC = "ssooidc" + STS = "sts" + SWF = "swf" + SageMaker = "sagemaker" + SageMakerA2IRuntime = "sagemakera2iruntime" + SageMakerEdge = "sagemakeredge" + SageMakerFeatureStoreRuntime = "sagemakerfeaturestoreruntime" + SageMakerRuntime = "sagemakerruntime" + SavingsPlans = "savingsplans" + Schemas = "schemas" + SecretsManager = "secretsmanager" + SecurityHub = "securityhub" + ServerlessRepo = "serverlessrepo" + ServiceCatalog = "servicecatalog" + ServiceCatalogAppRegistry = "servicecatalogappregistry" + ServiceDiscovery = "servicediscovery" + ServiceQuotas = "servicequotas" + Shield = "shield" + Signer = "signer" + SimpleDB = "simpledb" + SnowDeviceManagement = "snowdevicemanagement" + Snowball = "snowball" + StorageGateway = "storagegateway" + Support = "support" + Synthetics = "synthetics" + Textract = "textract" + TimestreamQuery = "timestreamquery" + TimestreamWrite = "timestreamwrite" + Transcribe = "transcribe" + TranscribeStreaming = "transcribestreaming" + Transfer = "transfer" + Translate = "translate" + VoiceID = "voiceid" + WAF = "waf" + WAFRegional = "wafregional" + WAFV2 = "wafv2" + WellArchitected = "wellarchitected" + Wisdom = "wisdom" + WorkDocs = "workdocs" + WorkLink = "worklink" + WorkMail = "workmail" + WorkMailMessageFlow = "workmailmessageflow" + WorkSpaces = "workspaces" + WorkSpacesWeb = "workspacesweb" + XRay = "xray" +) \ No newline at end of file diff --git a/names/errors.go b/names/errors.go new file mode 100644 index 0000000..298f1ff --- /dev/null +++ b/names/errors.go @@ -0,0 +1,100 @@ +package names + +import ( + "errors" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" +) + +const ( + ErrActionChecking = "checking" + ErrActionCheckingDestroyed = "checking destroyed" + ErrActionCheckingExistence = "checking existence" + ErrActionCheckingNotRecreated = "checking not recreated" + ErrActionCheckingRecreated = "checking recreated" + ErrActionCreating = "creating" + ErrActionDeleting = "deleting" + ErrActionImporting = "importing" + ErrActionReading = "reading" + ErrActionSetting = "setting" + ErrActionUpdating = "updating" + ErrActionWaitingForCreation = "waiting for creation" + ErrActionWaitingForDeletion = "waiting for delete" + ErrActionWaitingForUpdate = "waiting for update" +) + +// ProblemStandardMessage is a standardized message for reporting errors and warnings +func ProblemStandardMessage(service, action, resource, id string, gotError error) string { + hf, err := FullHumanFriendly(service) + + if err != nil { + return fmt.Sprintf("finding human-friendly name for service (%s) while creating error (%s, %s, %s, %s): %s", service, action, resource, id, gotError, err) + } + + if gotError == nil { + return fmt.Sprintf("%s %s %s (%s)", action, hf, resource, id) + } + + return fmt.Sprintf("%s %s %s (%s): %s", action, hf, resource, id, gotError) +} + +// Error returns an errors.Error with a standardized error message +func Error(service, action, resource, id string, gotError error) error { + return errors.New(ProblemStandardMessage(service, action, resource, id, gotError)) +} + +// DiagError returns a 1-length diag.Diagnostics with a diag.Error-level diag.Diagnostic +// with a standardized error message +func DiagError(service, action, resource, id string, gotError error) diag.Diagnostics { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: ProblemStandardMessage(service, action, resource, id, gotError), + }, + } +} + +// ErrorSetting returns an errors.Error with a standardized error message when setting +// arguments and attributes values. +func ErrorSetting(service, resource, id, argument string, gotError error) error { + return errors.New(ProblemStandardMessage(service, fmt.Sprintf("%s %s", ErrActionSetting, argument), resource, id, gotError)) +} + +// DiagErrorSetting returns an errors.Error with a standardized error message when setting +// arguments and attributes values. +func DiagErrorSetting(service, resource, id, argument string, gotError error) diag.Diagnostics { + return DiagError(service, fmt.Sprintf("%s %s", ErrActionSetting, argument), resource, id, gotError) +} + +// AddWarning returns diag.Diagnostics with an additional diag.Diagnostic containing +// a warning using a standardized problem message +func AddWarning(diags diag.Diagnostics, service, action, resource, id string, gotError error) diag.Diagnostics { + return append(diags, + diag.Diagnostic{ + Severity: diag.Warning, + Summary: ProblemStandardMessage(service, action, resource, id, gotError), + }, + ) +} + +// AddWarningNotFoundRemoveState returns diag.Diagnostics with an additional diag.Diagnostic containing +// a warning using a standardized problem message +func AddWarningNotFoundRemoveState(service, action, resource, id string) diag.Diagnostics { + return append(diag.Diagnostics{}, + diag.Diagnostic{ + Severity: diag.Warning, + Summary: ProblemStandardMessage(service, action, resource, id, errors.New("not found, removing from state")), + }, + ) +} + +// WarnLog logs to the default logger a standardized problem message +func WarnLog(service, action, resource, id string, gotError error) { + log.Printf("[WARN] %s", ProblemStandardMessage(service, action, resource, id, gotError)) +} + +func LogNotFoundRemoveState(service, action, resource, id string) { + WarnLog(service, action, resource, id, errors.New("not found, removing from state")) +} diff --git a/names/generate.go b/names/generate.go new file mode 100644 index 0000000..5108487 --- /dev/null +++ b/names/generate.go @@ -0,0 +1,4 @@ +//go:generate go run ../internal/generate/namesconsts/main.go +// ONLY generate directives and package declaration! Do not add anything else to this file. + +package names diff --git a/names/names.go b/names/names.go new file mode 100644 index 0000000..061a5d9 --- /dev/null +++ b/names/names.go @@ -0,0 +1,232 @@ +// Package names provides constants for AWS service names that are used as keys +// for the endpoints slice in internal/conns/conns.go. The package also exposes +// access to data found in the names_data.csv file, which provides additional +// service-related name information. +// +// Consumers of the names package include the conns package +// (internal/conn/conns.go), the provider package +// (internal/provider/provider.go), generators, and the skaff tool. +// +// It is very important that information in the names_data.csv be exactly +// correct because the Terrform AWS Provider relies on the information to +// function correctly. + +package names + +import ( + _ "embed" + "encoding/csv" + "fmt" + "log" + "strings" +) + +// This "should" be defined by the AWS Go SDK v2, but currently isn't. +const ( + KendraEndpointID = "kendra" + RolesAnywhereEndpointID = "rolesanywhere" + Route53DomainsEndpointID = "route53domains" + TranscribeEndpointID = "transcribe" +) + +// Type ServiceDatum corresponds closely to columns in `names_data.csv` and are +// described in detail in README.md. +type ServiceDatum struct { + Aliases []string + Brand string + DeprecatedEnvVar string + EnvVar string + GoV1ClientTypeName string + GoV1Package string + GoV2Package string + HumanFriendly string + ProviderNameUpper string +} + +// serviceData key is the AWS provider service package +var serviceData map[string]*ServiceDatum + +func init() { + serviceData = make(map[string]*ServiceDatum) + + // Data from names_data.csv + if err := readCSVIntoServiceData(); err != nil { + log.Fatalf("reading CSV into service data: %s", err) + } +} + +//go:embed names_data.csv +var namesData string + +func readCSVIntoServiceData() error { + // names_data.csv is dynamically embedded so changes, additions should be made + // there also + + r := csv.NewReader(strings.NewReader(namesData)) + + d, err := r.ReadAll() + if err != nil { + return fmt.Errorf("reading CSV into service data: %w", err) + } + + for i, l := range d { + if i < 1 { // omit header line + continue + } + + if l[ColExclude] != "" { + continue + } + + if l[ColProviderPackageActual] == "" && l[ColProviderPackageCorrect] == "" { + continue + } + + p := l[ColProviderPackageCorrect] + + if l[ColProviderPackageActual] != "" { + p = l[ColProviderPackageActual] + } + + serviceData[p] = &ServiceDatum{ + Brand: l[ColBrand], + DeprecatedEnvVar: l[ColDeprecatedEnvVar], + EnvVar: l[ColEnvVar], + GoV1ClientTypeName: l[ColGoV1ClientTypeName], + GoV1Package: l[ColGoV1Package], + GoV2Package: l[ColGoV2Package], + HumanFriendly: l[ColHumanFriendly], + ProviderNameUpper: l[ColProviderNameUpper], + } + + a := []string{p} + + if l[ColAliases] != "" { + a = append(a, strings.Split(l[ColAliases], ";")...) + } + + serviceData[p].Aliases = a + } + + return nil +} + +func ProviderPackageForAlias(serviceAlias string) (string, error) { + for k, v := range serviceData { + for _, hclKey := range v.Aliases { + if serviceAlias == hclKey { + return k, nil + } + } + } + + return "", fmt.Errorf("unable to find service for service alias %s", serviceAlias) +} + +func ProviderPackages() []string { + keys := make([]string, len(serviceData)) + + i := 0 + for k := range serviceData { + keys[i] = k + i++ + } + + return keys +} + +func Aliases() []string { + keys := make([]string, 0) + + for _, v := range serviceData { + keys = append(keys, v.Aliases...) + } + + return keys +} + +func ProviderNameUpper(service string) (string, error) { + if v, ok := serviceData[service]; ok { + return v.ProviderNameUpper, nil + } + + return "", fmt.Errorf("no service data found for %s", service) +} + +func DeprecatedEnvVar(service string) string { + if v, ok := serviceData[service]; ok { + return v.DeprecatedEnvVar + } + + return "" +} + +func EnvVar(service string) string { + if v, ok := serviceData[service]; ok { + return v.EnvVar + } + + return "" +} + +func FullHumanFriendly(service string) (string, error) { + if v, ok := serviceData[service]; ok { + if v.Brand == "" { + return v.HumanFriendly, nil + } + + return fmt.Sprintf("%s %s", v.Brand, v.HumanFriendly), nil + } + + if s, err := ProviderPackageForAlias(service); err == nil { + return FullHumanFriendly(s) + } + + return "", fmt.Errorf("no service data found for %s", service) +} + +func AWSGoPackage(providerPackage string, version int) (string, error) { + switch version { + case 1: + return AWSGoV1Package(providerPackage) + case 2: + return AWSGoV2Package(providerPackage) + default: + return "", fmt.Errorf("unsupported AWS SDK Go version: %d", version) + } +} + +func AWSGoV1Package(providerPackage string) (string, error) { + if v, ok := serviceData[providerPackage]; ok { + return v.GoV1Package, nil + } + + return "", fmt.Errorf("getting AWS SDK Go v1 package, %s not found", providerPackage) +} + +func AWSGoV2Package(providerPackage string) (string, error) { + if v, ok := serviceData[providerPackage]; ok { + return v.GoV2Package, nil + } + + return "", fmt.Errorf("getting AWS SDK Go v2 package, %s not found", providerPackage) +} + +func AWSGoClientTypeName(providerPackage string, version int) (string, error) { + switch version { + case 1: + return AWSGoV1ClientTypeName(providerPackage) + case 2: + return "Client", nil + default: + return "", fmt.Errorf("unsupported AWS SDK Go version: %d", version) + } +} + +func AWSGoV1ClientTypeName(providerPackage string) (string, error) { + if v, ok := serviceData[providerPackage]; ok { + return v.GoV1ClientTypeName, nil + } + + return "", fmt.Errorf("getting AWS SDK Go v1 client type name, %s not found", providerPackage) +} diff --git a/names/names_data.csv b/names/names_data.csv new file mode 100644 index 0000000..286340b --- /dev/null +++ b/names/names_data.csv @@ -0,0 +1,365 @@ +AWSCLIV2Command,AWSCLIV2CommandNoDashes,GoV1Package,GoV2Package,ProviderPackageActual,ProviderPackageCorrect,SplitPackageRealPackage,Aliases,ProviderNameUpper,GoV1ClientTypeName,SkipClientGenerate,SDKVersion,ResourcePrefixActual,ResourcePrefixCorrect,FilePrefix,DocPrefix,HumanFriendly,Brand,Exclude,AllowedSubcategory,DeprecatedEnvVar,EnvVar,Note +account,account,account,account,,account,,,Account,Account,,1,,aws_account_,,account_,Account Management,AWS,,,,, +acm,acm,acm,acm,,acm,,,ACM,ACM,,1,,aws_acm_,,acm_,ACM (Certificate Manager),AWS,,,,, +acm-pca,acmpca,acmpca,acmpca,,acmpca,,,ACMPCA,ACMPCA,,1,,aws_acmpca_,,acmpca_,ACM PCA (Certificate Manager Private Certificate Authority),AWS,,,,, +alexaforbusiness,alexaforbusiness,alexaforbusiness,alexaforbusiness,,alexaforbusiness,,,AlexaForBusiness,AlexaForBusiness,,1,,aws_alexaforbusiness_,,alexaforbusiness_,Alexa for Business,,,,,, +amp,amp,prometheusservice,amp,,amp,,prometheus;prometheusservice,AMP,PrometheusService,,1,aws_prometheus_,aws_amp_,,prometheus_,AMP (Managed Prometheus),Amazon,,,,, +amplify,amplify,amplify,amplify,,amplify,,,Amplify,Amplify,,1,,aws_amplify_,,amplify_,Amplify,AWS,,,,, +amplifybackend,amplifybackend,amplifybackend,amplifybackend,,amplifybackend,,,AmplifyBackend,AmplifyBackend,,1,,aws_amplifybackend_,,amplifybackend_,Amplify Backend,AWS,,,,, +amplifyuibuilder,amplifyuibuilder,amplifyuibuilder,amplifyuibuilder,,amplifyuibuilder,,,AmplifyUIBuilder,AmplifyUIBuilder,,1,,aws_amplifyuibuilder_,,amplifyuibuilder_,Amplify UI Builder,AWS,,,,, +,,,,,,,,,,,,,,,,Apache MXNet on AWS,AWS,x,,,,Documentation +apigateway,apigateway,apigateway,apigateway,,apigateway,,,APIGateway,APIGateway,,1,aws_api_gateway_,aws_apigateway_,,api_gateway_,API Gateway,Amazon,,,,, +apigatewaymanagementapi,apigatewaymanagementapi,apigatewaymanagementapi,apigatewaymanagementapi,,apigatewaymanagementapi,,,APIGatewayManagementAPI,ApiGatewayManagementApi,,1,,aws_apigatewaymanagementapi_,,apigatewaymanagementapi_,API Gateway Management API,Amazon,,,,, +apigatewayv2,apigatewayv2,apigatewayv2,apigatewayv2,,apigatewayv2,,,APIGatewayV2,ApiGatewayV2,,1,,aws_apigatewayv2_,,apigatewayv2_,API Gateway V2,Amazon,,,,, +appmesh,appmesh,appmesh,appmesh,,appmesh,,,AppMesh,AppMesh,,1,,aws_appmesh_,,appmesh_,App Mesh,AWS,,,,, +apprunner,apprunner,apprunner,apprunner,,apprunner,,,AppRunner,AppRunner,,1,,aws_apprunner_,,apprunner_,App Runner,AWS,,,,, +,,,,,,,,,,,,,,,,App2Container,AWS,x,,,,No SDK support +appconfig,appconfig,appconfig,appconfig,,appconfig,,,AppConfig,AppConfig,,1,,aws_appconfig_,,appconfig_,AppConfig,AWS,,,,, +appconfigdata,appconfigdata,appconfigdata,appconfigdata,,appconfigdata,,,AppConfigData,AppConfigData,,1,,aws_appconfigdata_,,appconfigdata_,AppConfig Data,AWS,,,,, +appflow,appflow,appflow,appflow,,appflow,,,AppFlow,Appflow,,1,,aws_appflow_,,appflow_,AppFlow,Amazon,,,,, +appintegrations,appintegrations,appintegrationsservice,appintegrations,,appintegrations,,appintegrationsservice,AppIntegrations,AppIntegrationsService,,1,,aws_appintegrations_,,appintegrations_,AppIntegrations,Amazon,,,,, +application-autoscaling,applicationautoscaling,applicationautoscaling,applicationautoscaling,appautoscaling,applicationautoscaling,,applicationautoscaling,AppAutoScaling,ApplicationAutoScaling,,1,aws_appautoscaling_,aws_applicationautoscaling_,,appautoscaling_,Application Auto Scaling,,,,,, +applicationcostprofiler,applicationcostprofiler,applicationcostprofiler,applicationcostprofiler,,applicationcostprofiler,,,ApplicationCostProfiler,ApplicationCostProfiler,,1,,aws_applicationcostprofiler_,,applicationcostprofiler_,Application Cost Profiler,AWS,,,,, +discovery,discovery,applicationdiscoveryservice,applicationdiscoveryservice,,discovery,,applicationdiscovery;applicationdiscoveryservice,Discovery,ApplicationDiscoveryService,,1,,aws_discovery_,,discovery_,Application Discovery,AWS,,,,, +mgn,mgn,mgn,mgn,,mgn,,,Mgn,Mgn,,1,,aws_mgn_,,mgn_,Application Migration (Mgn),AWS,,,,, +appstream,appstream,appstream,appstream,,appstream,,,AppStream,AppStream,,1,,aws_appstream_,,appstream_,AppStream 2.0,Amazon,,,,, +appsync,appsync,appsync,appsync,,appsync,,,AppSync,AppSync,,1,,aws_appsync_,,appsync_,AppSync,AWS,,,,, +,,,,,,,,,,,,,,,,Artifact,AWS,x,,,,No SDK support +athena,athena,athena,athena,,athena,,,Athena,Athena,,1,,aws_athena_,,athena_,Athena,Amazon,,,,, +auditmanager,auditmanager,auditmanager,auditmanager,,auditmanager,,,AuditManager,AuditManager,,1,,aws_auditmanager_,,auditmanager_,Audit Manager,AWS,,,,, +autoscaling,autoscaling,autoscaling,autoscaling,,autoscaling,,,AutoScaling,AutoScaling,,1,aws_(autoscaling_|launch_configuration),aws_autoscaling_,,autoscaling_;launch_configuration,Auto Scaling,,,,,, +autoscaling-plans,autoscalingplans,autoscalingplans,autoscalingplans,,autoscalingplans,,,AutoScalingPlans,AutoScalingPlans,,1,,aws_autoscalingplans_,,autoscalingplans_,Auto Scaling Plans,,,,,, +,,,,,,,,,,,,,,,,Backint Agent for SAP HANA,AWS,x,,,,No SDK support +backup,backup,backup,backup,,backup,,,Backup,Backup,,1,,aws_backup_,,backup_,Backup,AWS,,,,, +backup-gateway,backupgateway,backupgateway,backupgateway,,backupgateway,,,BackupGateway,BackupGateway,,1,,aws_backupgateway_,,backupgateway_,Backup Gateway,AWS,,,,, +batch,batch,batch,batch,,batch,,,Batch,Batch,,1,,aws_batch_,,batch_,Batch,AWS,,,,, +billingconductor,billingconductor,billingconductor,,,billingconductor,,,BillingConductor,BillingConductor,,1,,aws_billingconductor_,,billingconductor_,Billing Conductor,AWS,,,,, +braket,braket,braket,braket,,braket,,,Braket,Braket,,1,,aws_braket_,,braket_,Braket,Amazon,,,,, +ce,ce,costexplorer,costexplorer,,ce,,costexplorer,CE,CostExplorer,,1,,aws_ce_,,ce_,CE (Cost Explorer),AWS,,,,, +,,,,,,,,,,,,,,,,Chatbot,AWS,x,,,,No SDK support +chime,chime,chime,chime,,chime,,,Chime,Chime,,1,,aws_chime_,,chime_,Chime,Amazon,,,,, +chime-sdk-identity,chimesdkidentity,chimesdkidentity,chimesdkidentity,,chimesdkidentity,,,ChimeSDKIdentity,ChimeSDKIdentity,,1,,aws_chimesdkidentity_,,chimesdkidentity_,Chime SDK Identity,Amazon,,,,, +chime-sdk-meetings,chimesdkmeetings,chimesdkmeetings,chimesdkmeetings,,chimesdkmeetings,,,ChimeSDKMeetings,ChimeSDKMeetings,,1,,aws_chimesdkmeetings_,,chimesdkmeetings_,Chime SDK Meetings,Amazon,,,,, +chime-sdk-messaging,chimesdkmessaging,chimesdkmessaging,chimesdkmessaging,,chimesdkmessaging,,,ChimeSDKMessaging,ChimeSDKMessaging,,1,,aws_chimesdkmessaging_,,chimesdkmessaging_,Chime SDK Messaging,Amazon,,,,, +,,,,,,,,,,,,,,,,CLI (Command Line Interface),AWS,x,,,,No SDK support +configure,configure,,,,,,,,,,,,,,,CLI Configure options,AWS,x,,,,CLI only +ddb,ddb,,,,,,,,,,,,,,,CLI High-level DynamoDB commands,AWS,x,,,,Part of DynamoDB +s3,s3,,,,,,,,,,,,,,,CLI High-level S3 commands,AWS,x,,,,CLI only +history,history,,,,,,,,,,,,,,,CLI History of commands,AWS,x,,,,CLI only +importexport,importexport,,,,,,,,,,,,,,,CLI Import/Export,AWS,x,,,,CLI only +cli-dev,clidev,,,,,,,,,,,,,,,CLI Internal commands for development,AWS,x,,,,CLI only +cloudcontrol,cloudcontrol,cloudcontrolapi,cloudcontrol,,cloudcontrol,,cloudcontrolapi,CloudControl,CloudControlApi,,1,aws_cloudcontrolapi_,aws_cloudcontrol_,,cloudcontrolapi_,Cloud Control API,AWS,,,,, +,,,,,,,,,,,,,,,,Cloud Digital Interface SDK,AWS,x,,,,No SDK support +clouddirectory,clouddirectory,clouddirectory,clouddirectory,,clouddirectory,,,CloudDirectory,CloudDirectory,,1,,aws_clouddirectory_,,clouddirectory_,Cloud Directory,Amazon,,,,, +servicediscovery,servicediscovery,servicediscovery,servicediscovery,,servicediscovery,,,ServiceDiscovery,ServiceDiscovery,,1,aws_service_discovery_,aws_servicediscovery_,,service_discovery_,Cloud Map,AWS,,,,, +cloud9,cloud9,cloud9,cloud9,,cloud9,,,Cloud9,Cloud9,,1,,aws_cloud9_,,cloud9_,Cloud9,AWS,,,,, +cloudformation,cloudformation,cloudformation,cloudformation,,cloudformation,,,CloudFormation,CloudFormation,,1,,aws_cloudformation_,,cloudformation_,CloudFormation,AWS,,,,, +cloudfront,cloudfront,cloudfront,cloudfront,,cloudfront,,,CloudFront,CloudFront,,1,,aws_cloudfront_,,cloudfront_,CloudFront,Amazon,,,,, +cloudhsm,cloudhsm,cloudhsm,cloudhsm,,,,,,,,,,,,,CloudHSM,AWS,x,,,,Legacy +cloudhsmv2,cloudhsmv2,cloudhsmv2,cloudhsmv2,,cloudhsmv2,,cloudhsm,CloudHSMV2,CloudHSMV2,,1,aws_cloudhsm_v2_,aws_cloudhsmv2_,,cloudhsm,CloudHSM,AWS,,,,, +cloudsearch,cloudsearch,cloudsearch,cloudsearch,,cloudsearch,,,CloudSearch,CloudSearch,,1,,aws_cloudsearch_,,cloudsearch_,CloudSearch,Amazon,,,,, +cloudsearchdomain,cloudsearchdomain,cloudsearchdomain,cloudsearchdomain,,cloudsearchdomain,,,CloudSearchDomain,CloudSearchDomain,,1,,aws_cloudsearchdomain_,,cloudsearchdomain_,CloudSearch Domain,Amazon,,,,, +,,,,,,,,,,,,,,,,CloudShell,AWS,x,,,,No SDK support +cloudtrail,cloudtrail,cloudtrail,cloudtrail,,cloudtrail,,,CloudTrail,CloudTrail,,1,aws_cloudtrail,aws_cloudtrail_,,cloudtrail,CloudTrail,AWS,,,,, +cloudwatch,cloudwatch,cloudwatch,cloudwatch,,cloudwatch,,,CloudWatch,CloudWatch,,1,aws_cloudwatch_(?!(event_|log_|query_)),aws_cloudwatch_,,cloudwatch_dashboard;cloudwatch_metric_;cloudwatch_composite_,CloudWatch,Amazon,,,,, +application-insights,applicationinsights,applicationinsights,applicationinsights,,applicationinsights,,,ApplicationInsights,ApplicationInsights,,1,,aws_applicationinsights_,,applicationinsights_,CloudWatch Application Insights,Amazon,,,,, +evidently,evidently,cloudwatchevidently,evidently,,evidently,,cloudwatchevidently,Evidently,CloudWatchEvidently,,1,,aws_evidently_,,evidently_,CloudWatch Evidently,Amazon,,,,, +logs,logs,cloudwatchlogs,cloudwatchlogs,,logs,,cloudwatchlog;cloudwatchlogs,Logs,CloudWatchLogs,,1,aws_cloudwatch_(log_|query_),aws_logs_,,cloudwatch_log_;cloudwatch_query_,CloudWatch Logs,Amazon,,,,, +rum,rum,cloudwatchrum,rum,,rum,,cloudwatchrum,RUM,CloudWatchRUM,,1,,aws_rum_,,rum_,CloudWatch RUM,Amazon,,,,, +synthetics,synthetics,synthetics,synthetics,,synthetics,,,Synthetics,Synthetics,,1,,aws_synthetics_,,synthetics_,CloudWatch Synthetics,Amazon,,,,, +codeartifact,codeartifact,codeartifact,codeartifact,,codeartifact,,,CodeArtifact,CodeArtifact,,1,,aws_codeartifact_,,codeartifact_,CodeArtifact,AWS,,,,, +codebuild,codebuild,codebuild,codebuild,,codebuild,,,CodeBuild,CodeBuild,,1,,aws_codebuild_,,codebuild_,CodeBuild,AWS,,,,, +codecommit,codecommit,codecommit,codecommit,,codecommit,,,CodeCommit,CodeCommit,,1,,aws_codecommit_,,codecommit_,CodeCommit,AWS,,,,, +deploy,deploy,codedeploy,codedeploy,,deploy,,codedeploy,Deploy,CodeDeploy,,1,aws_codedeploy_,aws_deploy_,,codedeploy_,CodeDeploy,AWS,,,,, +codeguruprofiler,codeguruprofiler,codeguruprofiler,codeguruprofiler,,codeguruprofiler,,,CodeGuruProfiler,CodeGuruProfiler,,1,,aws_codeguruprofiler_,,codeguruprofiler_,CodeGuru Profiler,Amazon,,,,, +codeguru-reviewer,codegurureviewer,codegurureviewer,codegurureviewer,,codegurureviewer,,,CodeGuruReviewer,CodeGuruReviewer,,1,,aws_codegurureviewer_,,codegurureviewer_,CodeGuru Reviewer,Amazon,,,,, +codepipeline,codepipeline,codepipeline,codepipeline,,codepipeline,,,CodePipeline,CodePipeline,,1,aws_codepipeline,aws_codepipeline_,,codepipeline,CodePipeline,AWS,,,,, +codestar,codestar,codestar,codestar,,codestar,,,CodeStar,CodeStar,,1,,aws_codestar_,,codestar_,CodeStar,AWS,,,,, +codestar-connections,codestarconnections,codestarconnections,codestarconnections,,codestarconnections,,,CodeStarConnections,CodeStarConnections,,1,,aws_codestarconnections_,,codestarconnections_,CodeStar Connections,AWS,,,,, +codestar-notifications,codestarnotifications,codestarnotifications,codestarnotifications,,codestarnotifications,,,CodeStarNotifications,CodeStarNotifications,,1,,aws_codestarnotifications_,,codestarnotifications_,CodeStar Notifications,AWS,,,,, +cognito-identity,cognitoidentity,cognitoidentity,cognitoidentity,,cognitoidentity,,,CognitoIdentity,CognitoIdentity,,1,aws_cognito_identity_(?!provider),aws_cognitoidentity_,,cognito_identity_pool,Cognito Identity,Amazon,,,,, +cognito-idp,cognitoidp,cognitoidentityprovider,cognitoidentityprovider,,cognitoidp,,cognitoidentityprovider,CognitoIDP,CognitoIdentityProvider,,1,aws_cognito_(identity_provider|resource|user|risk),aws_cognitoidp_,,cognito_identity_provider;cognito_resource_;cognito_user;cognito_risk,Cognito IDP (Identity Provider),Amazon,,,,, +cognito-sync,cognitosync,cognitosync,cognitosync,,cognitosync,,,CognitoSync,CognitoSync,,1,,aws_cognitosync_,,cognitosync_,Cognito Sync,Amazon,,,,, +comprehend,comprehend,comprehend,comprehend,,comprehend,,,Comprehend,Comprehend,,1,,aws_comprehend_,,comprehend_,Comprehend,Amazon,,,,, +comprehendmedical,comprehendmedical,comprehendmedical,comprehendmedical,,comprehendmedical,,,ComprehendMedical,ComprehendMedical,,1,,aws_comprehendmedical_,,comprehendmedical_,Comprehend Medical,Amazon,,,,, +compute-optimizer,computeoptimizer,computeoptimizer,computeoptimizer,,computeoptimizer,,,ComputeOptimizer,ComputeOptimizer,,1,,aws_computeoptimizer_,,computeoptimizer_,Compute Optimizer,AWS,,,,, +configservice,configservice,configservice,configservice,,configservice,,config,ConfigService,ConfigService,,1,aws_config_,aws_configservice_,,config_,Config,AWS,,,,, +connect,connect,connect,connect,,connect,,,Connect,Connect,,1,,aws_connect_,,connect_,Connect,Amazon,,,,, +connect-contact-lens,connectcontactlens,connectcontactlens,connectcontactlens,,connectcontactlens,,,ConnectContactLens,ConnectContactLens,,1,,aws_connectcontactlens_,,connectcontactlens_,Connect Contact Lens,Amazon,,,,, +customer-profiles,customerprofiles,customerprofiles,customerprofiles,,customerprofiles,,,CustomerProfiles,CustomerProfiles,,1,,aws_customerprofiles_,,customerprofiles_,Connect Customer Profiles,Amazon,,,,, +connectparticipant,connectparticipant,connectparticipant,connectparticipant,,connectparticipant,,,ConnectParticipant,ConnectParticipant,,1,,aws_connectparticipant_,,connectparticipant_,Connect Participant,Amazon,,,,, +voice-id,voiceid,voiceid,voiceid,,voiceid,,,VoiceID,VoiceID,,1,,aws_voiceid_,,voiceid_,Connect Voice ID,Amazon,,,,, +wisdom,wisdom,connectwisdomservice,wisdom,,wisdom,,connectwisdomservice,Wisdom,ConnectWisdomService,,1,,aws_wisdom_,,wisdom_,Connect Wisdom,Amazon,,,,, +,,,,,,,,,,,,,,,,Console Mobile Application,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,Control Tower,AWS,x,,,,No SDK support +cur,cur,costandusagereportservice,costandusagereportservice,,cur,,costandusagereportservice,CUR,CostandUsageReportService,,1,,aws_cur_,,cur_,Cost and Usage Report,AWS,,,,, +,,,,,,,,,,,,,,,,Crypto Tools,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,Cryptographic Services Overview,AWS,x,,,,No SDK support +dataexchange,dataexchange,dataexchange,dataexchange,,dataexchange,,,DataExchange,DataExchange,,1,,aws_dataexchange_,,dataexchange_,Data Exchange,AWS,,,,, +datapipeline,datapipeline,datapipeline,datapipeline,,datapipeline,,,DataPipeline,DataPipeline,,1,,aws_datapipeline_,,datapipeline_,Data Pipeline,AWS,,,,, +datasync,datasync,datasync,datasync,,datasync,,,DataSync,DataSync,,1,,aws_datasync_,,datasync_,DataSync,AWS,,,,, +,,,,,,,,,,,,,,,,Deep Learning AMIs,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,Deep Learning Containers,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,DeepComposer,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,DeepLens,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,DeepRacer,AWS,x,,,,No SDK support +detective,detective,detective,detective,,detective,,,Detective,Detective,,1,,aws_detective_,,detective_,Detective,Amazon,,,,, +devicefarm,devicefarm,devicefarm,devicefarm,,devicefarm,,,DeviceFarm,DeviceFarm,,1,,aws_devicefarm_,,devicefarm_,Device Farm,AWS,,,,, +devops-guru,devopsguru,devopsguru,devopsguru,,devopsguru,,,DevOpsGuru,DevOpsGuru,,1,,aws_devopsguru_,,devopsguru_,DevOps Guru,Amazon,,,,, +directconnect,directconnect,directconnect,directconnect,,directconnect,,,DirectConnect,DirectConnect,,1,aws_dx_,aws_directconnect_,,dx_,Direct Connect,AWS,,,,, +dlm,dlm,dlm,dlm,,dlm,,,DLM,DLM,,1,,aws_dlm_,,dlm_,DLM (Data Lifecycle Manager),Amazon,,,,, +dms,dms,databasemigrationservice,databasemigrationservice,,dms,,databasemigration;databasemigrationservice,DMS,DatabaseMigrationService,,1,,aws_dms_,,dms_,DMS (Database Migration),AWS,,,,, +docdb,docdb,docdb,docdb,,docdb,,,DocDB,DocDB,,1,,aws_docdb_,,docdb_,DocDB (DocumentDB),Amazon,,,,, +drs,drs,drs,drs,,drs,,,DRS,Drs,,1,,aws_drs_,,drs_,DRS (Elastic Disaster Recovery),AWS,,,,, +ds,ds,directoryservice,directoryservice,,ds,,directoryservice,DS,DirectoryService,,1,aws_directory_service_,aws_ds_,,directory_service_,DS (Directory Service),AWS,,,,, +dynamodb,dynamodb,dynamodb,dynamodb,,dynamodb,,,DynamoDB,DynamoDB,,1,,aws_dynamodb_,,dynamodb_,DynamoDB,Amazon,,,AWS_DYNAMODB_ENDPOINT,TF_AWS_DYNAMODB_ENDPOINT, +dax,dax,dax,dax,,dax,,,DAX,DAX,,1,,aws_dax_,,dax_,DynamoDB Accelerator (DAX),Amazon,,,,, +dynamodbstreams,dynamodbstreams,dynamodbstreams,dynamodbstreams,,dynamodbstreams,,,DynamoDBStreams,DynamoDBStreams,,1,,aws_dynamodbstreams_,,dynamodbstreams_,DynamoDB Streams,Amazon,,,,, +,,,,,ec2ebs,ec2,,EC2EBS,,,,aws_(ebs_|volume_attach|snapshot_create),aws_ec2ebs_,ebs_,ebs_;volume_attachment;snapshot_,EBS (EC2),Amazon,x,x,,,Part of EC2 +ebs,ebs,ebs,ebs,,ebs,,,EBS,EBS,,1,,aws_ebs_,,changewhenimplemented,EBS (Elastic Block Store),Amazon,,,,, +ec2,ec2,ec2,ec2,,ec2,ec2,,EC2,EC2,,1,aws_(ami|availability_zone|ec2_(availability|capacity|fleet|host|instance|serial|spot|tag)|eip|instance|key_pair|launch_template|placement_group|spot),aws_ec2_,ec2_,ami;availability_zone;ec2_availability_;ec2_capacity_;ec2_fleet;ec2_host;ec2_instance_;ec2_serial_;ec2_spot_;ec2_tag;eip;instance;key_pair;launch_template;placement_group;spot_,EC2 (Elastic Compute Cloud),Amazon,,,,, +imagebuilder,imagebuilder,imagebuilder,imagebuilder,,imagebuilder,,,ImageBuilder,Imagebuilder,,1,,aws_imagebuilder_,,imagebuilder_,EC2 Image Builder,Amazon,,,,, +ec2-instance-connect,ec2instanceconnect,ec2instanceconnect,ec2instanceconnect,,ec2instanceconnect,,,EC2InstanceConnect,EC2InstanceConnect,,1,,aws_ec2instanceconnect_,,ec2instanceconnect_,EC2 Instance Connect,AWS,,,,, +ecr,ecr,ecr,ecr,,ecr,,,ECR,ECR,,1,,aws_ecr_,,ecr_,ECR (Elastic Container Registry),Amazon,,,,, +ecr-public,ecrpublic,ecrpublic,ecrpublic,,ecrpublic,,,ECRPublic,ECRPublic,,1,,aws_ecrpublic_,,ecrpublic_,ECR Public,Amazon,,,,, +ecs,ecs,ecs,ecs,,ecs,,,ECS,ECS,,1,,aws_ecs_,,ecs_,ECS (Elastic Container),Amazon,,,,, +efs,efs,efs,efs,,efs,,,EFS,EFS,,1,,aws_efs_,,efs_,EFS (Elastic File System),Amazon,,,,, +eks,eks,eks,eks,,eks,,,EKS,EKS,,1,,aws_eks_,,eks_,EKS (Elastic Kubernetes),Amazon,,,,, +elasticbeanstalk,elasticbeanstalk,elasticbeanstalk,elasticbeanstalk,,elasticbeanstalk,,beanstalk,ElasticBeanstalk,ElasticBeanstalk,,1,aws_elastic_beanstalk_,aws_elasticbeanstalk_,,elastic_beanstalk_,Elastic Beanstalk,AWS,,,,, +elastic-inference,elasticinference,elasticinference,elasticinference,,elasticinference,,,ElasticInference,ElasticInference,,1,,aws_elasticinference_,,elasticinference_,Elastic Inference,Amazon,,,,, +elastictranscoder,elastictranscoder,elastictranscoder,elastictranscoder,,elastictranscoder,,,ElasticTranscoder,ElasticTranscoder,,1,,aws_elastictranscoder_,,elastictranscoder_,Elastic Transcoder,Amazon,,,,, +elasticache,elasticache,elasticache,elasticache,,elasticache,,,ElastiCache,ElastiCache,,1,,aws_elasticache_,,elasticache_,ElastiCache,Amazon,,,,, +es,es,elasticsearchservice,elasticsearchservice,elasticsearch,es,,es;elasticsearchservice,Elasticsearch,ElasticsearchService,,1,aws_elasticsearch_,aws_es_,,elasticsearch_,Elasticsearch,Amazon,,,,, +elbv2,elbv2,elbv2,elasticloadbalancingv2,,elbv2,,elasticloadbalancingv2,ELBV2,ELBV2,,1,aws_a?lb(\b|_listener|_target_group),aws_elbv2_,,lb\.;lb_listener;lb_target_group;lb_hosted,ELB (Elastic Load Balancing),,,,,, +elb,elb,elb,elasticloadbalancing,,elb,,elasticloadbalancing,ELB,ELB,,1,aws_(app_cookie_stickiness_policy|elb|lb_cookie_stickiness_policy|lb_ssl_negotiation_policy|load_balancer_|proxy_protocol_policy),aws_elb_,,app_cookie_stickiness_policy;elb;lb_cookie_stickiness_policy;lb_ssl_negotiation_policy;load_balancer;proxy_protocol_policy,ELB Classic,,,,,, +mediaconnect,mediaconnect,mediaconnect,mediaconnect,,mediaconnect,,,MediaConnect,MediaConnect,,1,,aws_mediaconnect_,,media_connect_,Elemental MediaConnect,AWS,,,,, +mediaconvert,mediaconvert,mediaconvert,mediaconvert,,mediaconvert,,,MediaConvert,MediaConvert,,1,aws_media_convert_,aws_mediaconvert_,,media_convert_,Elemental MediaConvert,AWS,,,,, +medialive,medialive,medialive,medialive,,medialive,,,MediaLive,MediaLive,,1,,aws_medialive_,,media_live_,Elemental MediaLive,AWS,,,,, +mediapackage,mediapackage,mediapackage,mediapackage,,mediapackage,,,MediaPackage,MediaPackage,,1,aws_media_package_,aws_mediapackage_,,media_package_,Elemental MediaPackage,AWS,,,,, +mediapackage-vod,mediapackagevod,mediapackagevod,mediapackagevod,,mediapackagevod,,,MediaPackageVOD,MediaPackageVod,,1,,aws_mediapackagevod_,,mediapackagevod_,Elemental MediaPackage VOD,AWS,,,,, +mediastore,mediastore,mediastore,mediastore,,mediastore,,,MediaStore,MediaStore,,1,aws_media_store_,aws_mediastore_,,media_store_,Elemental MediaStore,AWS,,,,, +mediastore-data,mediastoredata,mediastoredata,mediastoredata,,mediastoredata,,,MediaStoreData,MediaStoreData,,1,,aws_mediastoredata_,,mediastoredata_,Elemental MediaStore Data,AWS,,,,, +mediatailor,mediatailor,mediatailor,mediatailor,,mediatailor,,,MediaTailor,MediaTailor,,1,,aws_mediatailor_,,media_tailor_,Elemental MediaTailor,AWS,,,,, +,,,,,,,,,,,,,,,,Elemental On-Premises,AWS,x,,,,No SDK support +emr,emr,emr,emr,,emr,,,EMR,EMR,,1,,aws_emr_,,emr_,EMR,Amazon,,,,, +emr-containers,emrcontainers,emrcontainers,emrcontainers,,emrcontainers,,,EMRContainers,EMRContainers,,1,,aws_emrcontainers_,,emrcontainers_,EMR Containers,Amazon,,,,, +emr-serverless,emrserverless,emrserverless,emrserverless,,emrserverless,,,EMRServerless,EMRServerless,,1,,aws_emrserverless_,,emrserverless_,EMR Serverless,Amazon,,,,, +,,,,,,,,,,,,,,,,End-of-Support Migration Program (EMP) for Windows Server,AWS,x,,,,No SDK support +events,events,eventbridge,eventbridge,,events,,eventbridge;cloudwatchevents,Events,EventBridge,,1,aws_cloudwatch_event_,aws_events_,,cloudwatch_event_,EventBridge,Amazon,,,,, +schemas,schemas,schemas,schemas,,schemas,,,Schemas,Schemas,,1,,aws_schemas_,,schemas_,EventBridge Schemas,Amazon,,,,, +fis,fis,fis,fis,,fis,,,FIS,FIS,x,2,,aws_fis_,,fis_,FIS (Fault Injection Simulator),AWS,,,,, +finspace,finspace,finspace,finspace,,finspace,,,FinSpace,Finspace,,1,,aws_finspace_,,finspace_,FinSpace,Amazon,,,,, +finspace-data,finspacedata,finspacedata,finspacedata,,finspacedata,,,FinSpaceData,FinSpaceData,,1,,aws_finspacedata_,,finspacedata_,FinSpace Data,Amazon,,,,, +fms,fms,fms,fms,,fms,,,FMS,FMS,,1,,aws_fms_,,fms_,FMS (Firewall Manager),AWS,,,,, +forecast,forecast,forecastservice,forecast,,forecast,,forecastservice,Forecast,ForecastService,,1,,aws_forecast_,,forecast_,Forecast,Amazon,,,,, +forecastquery,forecastquery,forecastqueryservice,forecastquery,,forecastquery,,forecastqueryservice,ForecastQuery,ForecastQueryService,,1,,aws_forecastquery_,,forecastquery_,Forecast Query,Amazon,,,,, +frauddetector,frauddetector,frauddetector,frauddetector,,frauddetector,,,FraudDetector,FraudDetector,,1,,aws_frauddetector_,,frauddetector_,Fraud Detector,Amazon,,,,, +,,,,,,,,,,,,,,,,FreeRTOS,,x,,,,No SDK support +fsx,fsx,fsx,fsx,,fsx,,,FSx,FSx,,1,,aws_fsx_,,fsx_,FSx,Amazon,,,,, +gamelift,gamelift,gamelift,gamelift,,gamelift,,,GameLift,GameLift,,1,,aws_gamelift_,,gamelift_,GameLift,Amazon,,,,, +globalaccelerator,globalaccelerator,globalaccelerator,globalaccelerator,,globalaccelerator,,,GlobalAccelerator,GlobalAccelerator,x,1,,aws_globalaccelerator_,,globalaccelerator_,Global Accelerator,AWS,,,,, +glue,glue,glue,glue,,glue,,,Glue,Glue,,1,,aws_glue_,,glue_,Glue,AWS,,,,, +databrew,databrew,gluedatabrew,databrew,,databrew,,gluedatabrew,DataBrew,GlueDataBrew,,1,,aws_databrew_,,databrew_,Glue DataBrew,AWS,,,,, +groundstation,groundstation,groundstation,groundstation,,groundstation,,,GroundStation,GroundStation,,1,,aws_groundstation_,,groundstation_,Ground Station,AWS,,,,, +guardduty,guardduty,guardduty,guardduty,,guardduty,,,GuardDuty,GuardDuty,,1,,aws_guardduty_,,guardduty_,GuardDuty,Amazon,,,,, +health,health,health,health,,health,,,Health,Health,,1,,aws_health_,,health_,Health,AWS,,,,, +healthlake,healthlake,healthlake,healthlake,,healthlake,,,HealthLake,HealthLake,,1,,aws_healthlake_,,healthlake_,HealthLake,Amazon,,,,, +honeycode,honeycode,honeycode,honeycode,,honeycode,,,Honeycode,Honeycode,,1,,aws_honeycode_,,honeycode_,Honeycode,Amazon,,,,, +iam,iam,iam,iam,,iam,,,IAM,IAM,,1,,aws_iam_,,iam_,IAM (Identity & Access Management),AWS,,,AWS_IAM_ENDPOINT,TF_AWS_IAM_ENDPOINT, +accessanalyzer,accessanalyzer,accessanalyzer,accessanalyzer,,accessanalyzer,,,AccessAnalyzer,AccessAnalyzer,,1,,aws_accessanalyzer_,,accessanalyzer_,IAM Access Analyzer,AWS,,,,, +inspector,inspector,inspector,inspector,,inspector,,,Inspector,Inspector,,1,,aws_inspector_,,inspector_,Inspector,Amazon,,,,, +inspector2,inspector2,inspector2,inspector2,,inspector2,,,Inspector2,Inspector2,,1,,aws_inspector2_,,inspector2_,Inspector V2,Amazon,,,,, +iot1click-devices,iot1clickdevices,iot1clickdevicesservice,iot1clickdevicesservice,,iot1clickdevices,,iot1clickdevicesservice,IoT1ClickDevices,IoT1ClickDevicesService,,1,,aws_iot1clickdevices_,,iot1clickdevices_,IoT 1-Click Devices,AWS,,,,, +iot1click-projects,iot1clickprojects,iot1clickprojects,iot1clickprojects,,iot1clickprojects,,,IoT1ClickProjects,IoT1ClickProjects,,1,,aws_iot1clickprojects_,,iot1clickprojects_,IoT 1-Click Projects,AWS,,,,, +iotanalytics,iotanalytics,iotanalytics,iotanalytics,,iotanalytics,,,IoTAnalytics,IoTAnalytics,,1,,aws_iotanalytics_,,iotanalytics_,IoT Analytics,AWS,,,,, +iot,iot,iot,iot,,iot,,,IoT,IoT,,1,,aws_iot_,,iot_,IoT Core,AWS,,,,, +iot-data,iotdata,iotdataplane,iotdataplane,,iotdata,,iotdataplane,IoTData,IoTDataPlane,,1,,aws_iotdata_,,iotdata_,IoT Data Plane,AWS,,,,, +,,,,,,,,,,,,,,,,IoT Device Defender,AWS,x,,,,Part of IoT +iotdeviceadvisor,iotdeviceadvisor,iotdeviceadvisor,iotdeviceadvisor,,iotdeviceadvisor,,,IoTDeviceAdvisor,IoTDeviceAdvisor,,1,,aws_iotdeviceadvisor_,,iotdeviceadvisor_,IoT Device Management,AWS,,,,, +iotevents,iotevents,iotevents,iotevents,,iotevents,,,IoTEvents,IoTEvents,,1,,aws_iotevents_,,iotevents_,IoT Events,AWS,,,,, +iotevents-data,ioteventsdata,ioteventsdata,ioteventsdata,,ioteventsdata,,,IoTEventsData,IoTEventsData,,1,,aws_ioteventsdata_,,ioteventsdata_,IoT Events Data,AWS,,,,, +,,,,,,,,,,,,,,,,IoT ExpressLink,AWS,x,,,,No SDK support +iotfleethub,iotfleethub,iotfleethub,iotfleethub,,iotfleethub,,,IoTFleetHub,IoTFleetHub,,1,,aws_iotfleethub_,,iotfleethub_,IoT Fleet Hub,AWS,,,,, +,,,,,,,,,,,,,,,,IoT FleetWise,AWS,x,,,,No SDK support +greengrass,greengrass,greengrass,greengrass,,greengrass,,,Greengrass,Greengrass,,1,,aws_greengrass_,,greengrass_,IoT Greengrass,AWS,,,,, +greengrassv2,greengrassv2,greengrassv2,greengrassv2,,greengrassv2,,,GreengrassV2,GreengrassV2,,1,,aws_greengrassv2_,,greengrassv2_,IoT Greengrass V2,AWS,,,,, +iot-jobs-data,iotjobsdata,iotjobsdataplane,iotjobsdataplane,,iotjobsdata,,iotjobsdataplane,IoTJobsData,IoTJobsDataPlane,,1,,aws_iotjobsdata_,,iotjobsdata_,IoT Jobs Data Plane,AWS,,,,, +,,,,,,,,,,,,,,,,IoT RoboRunner,AWS,x,,,,No SDK support +iotsecuretunneling,iotsecuretunneling,iotsecuretunneling,iotsecuretunneling,,iotsecuretunneling,,,IoTSecureTunneling,IoTSecureTunneling,,1,,aws_iotsecuretunneling_,,iotsecuretunneling_,IoT Secure Tunneling,AWS,,,,, +iotsitewise,iotsitewise,iotsitewise,iotsitewise,,iotsitewise,,,IoTSiteWise,IoTSiteWise,,1,,aws_iotsitewise_,,iotsitewise_,IoT SiteWise,AWS,,,,, +iotthingsgraph,iotthingsgraph,iotthingsgraph,iotthingsgraph,,iotthingsgraph,,,IoTThingsGraph,IoTThingsGraph,,1,,aws_iotthingsgraph_,,iotthingsgraph_,IoT Things Graph,AWS,,,,, +iottwinmaker,iottwinmaker,iottwinmaker,iottwinmaker,,iottwinmaker,,,IoTTwinMaker,IoTTwinMaker,,1,,aws_iottwinmaker_,,iottwinmaker_,IoT TwinMaker,AWS,,,,, +iotwireless,iotwireless,iotwireless,iotwireless,,iotwireless,,,IoTWireless,IoTWireless,,1,,aws_iotwireless_,,iotwireless_,IoT Wireless,AWS,,,,, +,,,,,,,,,,,,,,,,IQ,AWS,x,,,,No SDK support +ivs,ivs,ivs,ivs,,ivs,,,IVS,IVS,,1,,aws_ivs_,,ivs_,IVS (Interactive Video),Amazon,,,,, +kendra,kendra,kendra,kendra,,kendra,,,Kendra,Kendra,x,2,,aws_kendra_,,kendra_,Kendra,Amazon,,,,, +keyspaces,keyspaces,keyspaces,,,keyspaces,,,Keyspaces,Keyspaces,,1,,aws_keyspaces_,,keyspaces_,Keyspaces (for Apache Cassandra),Amazon,,,,, +kinesis,kinesis,kinesis,kinesis,,kinesis,,,Kinesis,Kinesis,,1,aws_kinesis_stream,aws_kinesis_,,kinesis_stream,Kinesis,Amazon,,,,, +kinesisanalytics,kinesisanalytics,kinesisanalytics,kinesisanalytics,,kinesisanalytics,,,KinesisAnalytics,KinesisAnalytics,,1,aws_kinesis_analytics_,aws_kinesisanalytics_,,kinesis_analytics_,Kinesis Analytics,Amazon,,,,, +kinesisanalyticsv2,kinesisanalyticsv2,kinesisanalyticsv2,kinesisanalyticsv2,,kinesisanalyticsv2,,,KinesisAnalyticsV2,KinesisAnalyticsV2,,1,,aws_kinesisanalyticsv2_,,kinesisanalyticsv2_,Kinesis Analytics V2,Amazon,,,,, +firehose,firehose,firehose,firehose,,firehose,,,Firehose,Firehose,,1,aws_kinesis_firehose_,aws_firehose_,,kinesis_firehose_,Kinesis Firehose,Amazon,,,,, +kinesisvideo,kinesisvideo,kinesisvideo,kinesisvideo,,kinesisvideo,,,KinesisVideo,KinesisVideo,,1,,aws_kinesisvideo_,,kinesis_video_,Kinesis Video,Amazon,,,,, +kinesis-video-archived-media,kinesisvideoarchivedmedia,kinesisvideoarchivedmedia,kinesisvideoarchivedmedia,,kinesisvideoarchivedmedia,,,KinesisVideoArchivedMedia,KinesisVideoArchivedMedia,,1,,aws_kinesisvideoarchivedmedia_,,kinesisvideoarchivedmedia_,Kinesis Video Archived Media,Amazon,,,,, +kinesis-video-media,kinesisvideomedia,kinesisvideomedia,kinesisvideomedia,,kinesisvideomedia,,,KinesisVideoMedia,KinesisVideoMedia,,1,,aws_kinesisvideomedia_,,kinesisvideomedia_,Kinesis Video Media,Amazon,,,,, +kinesis-video-signaling,kinesisvideosignaling,kinesisvideosignalingchannels,kinesisvideosignaling,,kinesisvideosignaling,,kinesisvideosignalingchannels,KinesisVideoSignaling,KinesisVideoSignalingChannels,,1,,aws_kinesisvideosignaling_,,kinesisvideosignaling_,Kinesis Video Signaling,Amazon,,,,, +kms,kms,kms,kms,,kms,,,KMS,KMS,,1,,aws_kms_,,kms_,KMS (Key Management),AWS,,,,, +lakeformation,lakeformation,lakeformation,lakeformation,,lakeformation,,,LakeFormation,LakeFormation,,1,,aws_lakeformation_,,lakeformation_,Lake Formation,AWS,,,,, +lambda,lambda,lambda,lambda,,lambda,,,Lambda,Lambda,,1,,aws_lambda_,,lambda_,Lambda,AWS,,,,, +,,,,,,,,,,,,,,,,Launch Wizard,AWS,x,,,,No SDK support +lex-models,lexmodels,lexmodelbuildingservice,lexmodelbuildingservice,,lexmodels,,lexmodelbuilding;lexmodelbuildingservice;lex,LexModels,LexModelBuildingService,,1,aws_lex_,aws_lexmodels_,,lex_,Lex Model Building,Amazon,,,,, +lexv2-models,lexv2models,lexmodelsv2,lexmodelsv2,,lexmodelsv2,,lexv2models,LexModelsV2,LexModelsV2,,1,,aws_lexmodelsv2_,,lexmodelsv2_,Lex Models V2,Amazon,,,,, +lex-runtime,lexruntime,lexruntimeservice,lexruntimeservice,,lexruntime,,lexruntimeservice,LexRuntime,LexRuntimeService,,1,,aws_lexruntime_,,lexruntime_,Lex Runtime,Amazon,,,,, +lexv2-runtime,lexv2runtime,lexruntimev2,lexruntimev2,,lexruntimev2,,lexv2runtime,LexRuntimeV2,LexRuntimeV2,,1,,aws_lexruntimev2_,,lexruntimev2_,Lex Runtime V2,Amazon,,,,, +license-manager,licensemanager,licensemanager,licensemanager,,licensemanager,,,LicenseManager,LicenseManager,,1,,aws_licensemanager_,,licensemanager_,License Manager,AWS,,,,, +lightsail,lightsail,lightsail,lightsail,,lightsail,,,Lightsail,Lightsail,,1,,aws_lightsail_,,lightsail_,Lightsail,Amazon,,,,, +location,location,locationservice,location,,location,,locationservice,Location,LocationService,,1,,aws_location_,,location_,Location,Amazon,,,,, +lookoutequipment,lookoutequipment,lookoutequipment,lookoutequipment,,lookoutequipment,,,LookoutEquipment,LookoutEquipment,,1,,aws_lookoutequipment_,,lookoutequipment_,Lookout for Equipment,Amazon,,,,, +lookoutmetrics,lookoutmetrics,lookoutmetrics,lookoutmetrics,,lookoutmetrics,,,LookoutMetrics,LookoutMetrics,,1,,aws_lookoutmetrics_,,lookoutmetrics_,Lookout for Metrics,Amazon,,,,, +lookoutvision,lookoutvision,lookoutforvision,lookoutvision,,lookoutvision,,lookoutforvision,LookoutVision,LookoutForVision,,1,,aws_lookoutvision_,,lookoutvision_,Lookout for Vision,Amazon,,,,, +,,,,,,,,,,,,,,,,Lumberyard,Amazon,x,,,,No SDK support +machinelearning,machinelearning,machinelearning,machinelearning,,machinelearning,,,MachineLearning,MachineLearning,,1,,aws_machinelearning_,,machinelearning_,Machine Learning,Amazon,,,,, +macie2,macie2,macie2,macie2,,macie2,,,Macie2,Macie2,,1,,aws_macie2_,,macie2_,Macie,Amazon,,,,, +macie,macie,macie,macie,,macie,,,Macie,Macie,,1,,aws_macie_,,macie_,Macie Classic,Amazon,,,,, +,,,,,,,,,,,,,,,,Mainframe Modernization,AWS,x,,,,No SDK support +managedblockchain,managedblockchain,managedblockchain,managedblockchain,,managedblockchain,,,ManagedBlockchain,ManagedBlockchain,,1,,aws_managedblockchain_,,managedblockchain_,Managed Blockchain,Amazon,,,,, +grafana,grafana,managedgrafana,grafana,,grafana,,managedgrafana;amg,Grafana,ManagedGrafana,,1,,aws_grafana_,,grafana_,Managed Grafana,Amazon,,,,, +kafka,kafka,kafka,kafka,,kafka,,msk,Kafka,Kafka,,1,aws_msk_,aws_kafka_,,msk_,Managed Streaming for Kafka,Amazon,,,,, +kafkaconnect,kafkaconnect,kafkaconnect,kafkaconnect,,kafkaconnect,,,KafkaConnect,KafkaConnect,,1,aws_mskconnect_,aws_kafkaconnect_,,mskconnect_,Managed Streaming for Kafka Connect,Amazon,,,,, +,,,,,,,,,,,,,,,,Management Console,AWS,x,,,,No SDK support +marketplace-catalog,marketplacecatalog,marketplacecatalog,marketplacecatalog,,marketplacecatalog,,,MarketplaceCatalog,MarketplaceCatalog,,1,,aws_marketplacecatalog_,,marketplace_catalog_,Marketplace Catalog,AWS,,,,, +marketplacecommerceanalytics,marketplacecommerceanalytics,marketplacecommerceanalytics,marketplacecommerceanalytics,,marketplacecommerceanalytics,,,MarketplaceCommerceAnalytics,MarketplaceCommerceAnalytics,,1,,aws_marketplacecommerceanalytics_,,marketplacecommerceanalytics_,Marketplace Commerce Analytics,AWS,,,,, +marketplace-entitlement,marketplaceentitlement,marketplaceentitlementservice,marketplaceentitlementservice,,marketplaceentitlement,,marketplaceentitlementservice,MarketplaceEntitlement,MarketplaceEntitlementService,,1,,aws_marketplaceentitlement_,,marketplaceentitlement_,Marketplace Entitlement,AWS,,,,, +meteringmarketplace,meteringmarketplace,marketplacemetering,marketplacemetering,,marketplacemetering,,meteringmarketplace,MarketplaceMetering,MarketplaceMetering,,1,,aws_marketplacemetering_,,marketplacemetering_,Marketplace Metering,AWS,,,,, +memorydb,memorydb,memorydb,memorydb,,memorydb,,,MemoryDB,MemoryDB,,1,,aws_memorydb_,,memorydb_,MemoryDB for Redis,Amazon,,,,, +,,,,,meta,,,Meta,,,,aws_(arn|billing_service_account|default_tags|ip_ranges|partition|regions?|service)$,aws_meta_,,arn;ip_ranges;billing_service_account;default_tags;partition;region;service\.,Meta Data Sources,,x,x,,,Not an AWS service (metadata) +mgh,mgh,migrationhub,migrationhub,,mgh,,migrationhub,MgH,MigrationHub,,1,,aws_mgh_,,mgh_,MgH (Migration Hub),AWS,,,,, +,,,,,,,,,,,,,,,,Microservice Extractor for .NET,AWS,x,,,,No SDK support +migrationhub-config,migrationhubconfig,migrationhubconfig,migrationhubconfig,,migrationhubconfig,,,MigrationHubConfig,MigrationHubConfig,,1,,aws_migrationhubconfig_,,migrationhubconfig_,Migration Hub Config,AWS,,,,, +migration-hub-refactor-spaces,migrationhubrefactorspaces,migrationhubrefactorspaces,migrationhubrefactorspaces,,migrationhubrefactorspaces,,,MigrationHubRefactorSpaces,MigrationHubRefactorSpaces,,1,,aws_migrationhubrefactorspaces_,,migrationhubrefactorspaces_,Migration Hub Refactor Spaces,AWS,,,,, +migrationhubstrategy,migrationhubstrategy,migrationhubstrategyrecommendations,migrationhubstrategy,,migrationhubstrategy,,migrationhubstrategyrecommendations,MigrationHubStrategy,MigrationHubStrategyRecommendations,,1,,aws_migrationhubstrategy_,,migrationhubstrategy_,Migration Hub Strategy,AWS,,,,, +mobile,mobile,mobile,mobile,,mobile,,,Mobile,Mobile,,1,,aws_mobile_,,mobile_,Mobile,AWS,,,,, +,,mobileanalytics,,,,,,MobileAnalytics,MobileAnalytics,,,,,,,Mobile Analytics,AWS,x,,,,Only in Go SDK v1 +,,,,,,,,,,,,,,,,Mobile SDK for Unity,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,Mobile SDK for Xamarin,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,Monitron,Amazon,x,,,,No SDK support +mq,mq,mq,mq,,mq,,,MQ,MQ,,1,,aws_mq_,,mq_,MQ,Amazon,,,,, +mturk,mturk,mturk,mturk,,mturk,,,MTurk,MTurk,,1,,aws_mturk_,,mturk_,MTurk (Mechanical Turk),Amazon,,,,, +mwaa,mwaa,mwaa,mwaa,,mwaa,,,MWAA,MWAA,,1,,aws_mwaa_,,mwaa_,MWAA (Managed Workflows for Apache Airflow),Amazon,,,,, +neptune,neptune,neptune,neptune,,neptune,,,Neptune,Neptune,,1,,aws_neptune_,,neptune_,Neptune,Amazon,,,,, +network-firewall,networkfirewall,networkfirewall,networkfirewall,,networkfirewall,,,NetworkFirewall,NetworkFirewall,,1,,aws_networkfirewall_,,networkfirewall_,Network Firewall,AWS,,,,, +networkmanager,networkmanager,networkmanager,networkmanager,,networkmanager,,,NetworkManager,NetworkManager,,1,,aws_networkmanager_,,networkmanager_,Network Manager,AWS,,,,, +,,,,,,,,,,,,,,,,NICE DCV,,x,,,,No SDK support +nimble,nimble,nimblestudio,nimble,,nimble,,nimblestudio,Nimble,NimbleStudio,,1,,aws_nimble_,,nimble_,Nimble Studio,Amazon,,,,, +opensearch,opensearch,opensearchservice,opensearch,,opensearch,,opensearchservice,OpenSearch,OpenSearchService,,1,,aws_opensearch_,,opensearch_,OpenSearch,Amazon,,,,, +opsworks,opsworks,opsworks,opsworks,,opsworks,,,OpsWorks,OpsWorks,,1,,aws_opsworks_,,opsworks_,OpsWorks,AWS,,,,, +opsworks-cm,opsworkscm,opsworkscm,opsworkscm,,opsworkscm,,,OpsWorksCM,OpsWorksCM,,1,,aws_opsworkscm_,,opsworkscm_,OpsWorks CM,AWS,,,,, +organizations,organizations,organizations,organizations,,organizations,,,Organizations,Organizations,,1,,aws_organizations_,,organizations_,Organizations,AWS,,,,, +outposts,outposts,outposts,outposts,,outposts,,,Outposts,Outposts,,1,,aws_outposts_,,outposts_,Outposts,AWS,,,,, +,,,,,ec2outposts,ec2,,EC2Outposts,,,,aws_ec2_(coip_pool|local_gateway),aws_ec2outposts_,outposts_,ec2_coip_pool;ec2_local_gateway,Outposts (EC2),AWS,x,x,,,Part of EC2 +panorama,panorama,panorama,panorama,,panorama,,,Panorama,Panorama,,1,,aws_panorama_,,panorama_,Panorama,AWS,,,,, +,,,,,,,,,,,,,,,,ParallelCluster,AWS,x,,,,No SDK support +personalize,personalize,personalize,personalize,,personalize,,,Personalize,Personalize,,1,,aws_personalize_,,personalize_,Personalize,Amazon,,,,, +personalize-events,personalizeevents,personalizeevents,personalizeevents,,personalizeevents,,,PersonalizeEvents,PersonalizeEvents,,1,,aws_personalizeevents_,,personalizeevents_,Personalize Events,Amazon,,,,, +personalize-runtime,personalizeruntime,personalizeruntime,personalizeruntime,,personalizeruntime,,,PersonalizeRuntime,PersonalizeRuntime,,1,,aws_personalizeruntime_,,personalizeruntime_,Personalize Runtime,Amazon,,,,, +pinpoint,pinpoint,pinpoint,pinpoint,,pinpoint,,,Pinpoint,Pinpoint,,1,,aws_pinpoint_,,pinpoint_,Pinpoint,Amazon,,,,, +pinpoint-email,pinpointemail,pinpointemail,pinpointemail,,pinpointemail,,,PinpointEmail,PinpointEmail,,1,,aws_pinpointemail_,,pinpointemail_,Pinpoint Email,Amazon,,,,, +pinpoint-sms-voice,pinpointsmsvoice,pinpointsmsvoice,pinpointsmsvoice,,pinpointsmsvoice,,,PinpointSMSVoice,PinpointSMSVoice,,1,,aws_pinpointsmsvoice_,,pinpointsmsvoice_,Pinpoint SMS and Voice,Amazon,,,,, +polly,polly,polly,polly,,polly,,,Polly,Polly,,1,,aws_polly_,,polly_,Polly,Amazon,,,,, +,,,,,,,,,,,,,,,,Porting Assistant for .NET,,x,,,,No SDK support +pricing,pricing,pricing,pricing,,pricing,,,Pricing,Pricing,,1,,aws_pricing_,,pricing_,Pricing Calculator,AWS,,,,, +proton,proton,proton,proton,,proton,,,Proton,Proton,,1,,aws_proton_,,proton_,Proton,AWS,,,,, +qldb,qldb,qldb,qldb,,qldb,,,QLDB,QLDB,,1,,aws_qldb_,,qldb_,QLDB (Quantum Ledger Database),Amazon,,,,, +qldb-session,qldbsession,qldbsession,qldbsession,,qldbsession,,,QLDBSession,QLDBSession,,1,,aws_qldbsession_,,qldbsession_,QLDB Session,Amazon,,,,, +quicksight,quicksight,quicksight,quicksight,,quicksight,,,QuickSight,QuickSight,,1,,aws_quicksight_,,quicksight_,QuickSight,Amazon,,,,, +ram,ram,ram,ram,,ram,,,RAM,RAM,,1,,aws_ram_,,ram_,RAM (Resource Access Manager),AWS,,,,, +rds,rds,rds,rds,,rds,,,RDS,RDS,,1,aws_(db_|rds_),aws_rds_,,rds_;db_,RDS (Relational Database),Amazon,,,,, +rds-data,rdsdata,rdsdataservice,rdsdata,,rdsdata,,rdsdataservice,RDSData,RDSDataService,,1,,aws_rdsdata_,,rdsdata_,RDS Data,Amazon,,,,, +pi,pi,pi,pi,,pi,,,PI,PI,,1,,aws_pi_,,pi_,RDS Performance Insights (PI),Amazon,,,,, +rbin,rbin,recyclebin,rbin,,rbin,,recyclebin,RBin,RecycleBin,,1,,aws_rbin_,,rbin_,Recycle Bin (RBin),Amazon,,,,, +,,,,,,,,,,,,,,,,Red Hat OpenShift Service on AWS (ROSA),AWS,x,,,,No SDK support +redshift,redshift,redshift,redshift,,redshift,,,Redshift,Redshift,,1,,aws_redshift_,,redshift_,Redshift,Amazon,,,,, +redshift-data,redshiftdata,redshiftdataapiservice,redshiftdata,,redshiftdata,,redshiftdataapiservice,RedshiftData,RedshiftDataAPIService,,1,,aws_redshiftdata_,,redshiftdata_,Redshift Data,Amazon,,,,, +redshift-serverless,redshiftserverless,redshiftserverless,redshiftserverless,,redshiftserverless,,,RedshiftServerless,RedshiftServerless,,1,,aws_redshiftserverless_,,redshiftserverless_,Redshift Serverless,Amazon,,,,, +rekognition,rekognition,rekognition,rekognition,,rekognition,,,Rekognition,Rekognition,,1,,aws_rekognition_,,rekognition_,Rekognition,Amazon,,,,, +resiliencehub,resiliencehub,resiliencehub,resiliencehub,,resiliencehub,,,ResilienceHub,ResilienceHub,,1,,aws_resiliencehub_,,resiliencehub_,Resilience Hub,AWS,,,,, +resource-groups,resourcegroups,resourcegroups,resourcegroups,,resourcegroups,,,ResourceGroups,ResourceGroups,,1,,aws_resourcegroups_,,resourcegroups_,Resource Groups,AWS,,,,, +resourcegroupstaggingapi,resourcegroupstaggingapi,resourcegroupstaggingapi,resourcegroupstaggingapi,,resourcegroupstaggingapi,,resourcegroupstagging,ResourceGroupsTaggingAPI,ResourceGroupsTaggingAPI,,1,,aws_resourcegroupstaggingapi_,,resourcegroupstaggingapi_,Resource Groups Tagging,AWS,,,,, +robomaker,robomaker,robomaker,robomaker,,robomaker,,,RoboMaker,RoboMaker,,1,,aws_robomaker_,,robomaker_,RoboMaker,AWS,,,,, +rolesanywhere,rolesanywhere,rolesanywhere,rolesanywhere,,rolesanywhere,,,RolesAnywhere,RolesAnywhere,x,2,,aws_rolesanywhere_,,rolesanywhere_,Roles Anywhere,AWS,,,,, +route53,route53,route53,route53,,route53,,,Route53,Route53,x,1,aws_route53_(?!resolver_),aws_route53_,,route53_delegation_;route53_health_;route53_hosted_;route53_key_;route53_query_;route53_record;route53_traffic_;route53_vpc_;route53_zone,Route 53,Amazon,,,,, +route53domains,route53domains,route53domains,route53domains,,route53domains,,,Route53Domains,Route53Domains,x,2,,aws_route53domains_,,route53domains_,Route 53 Domains,Amazon,,,,, +route53-recovery-cluster,route53recoverycluster,route53recoverycluster,route53recoverycluster,,route53recoverycluster,,,Route53RecoveryCluster,Route53RecoveryCluster,,1,,aws_route53recoverycluster_,,route53recoverycluster_,Route 53 Recovery Cluster,Amazon,,,,, +route53-recovery-control-config,route53recoverycontrolconfig,route53recoverycontrolconfig,route53recoverycontrolconfig,,route53recoverycontrolconfig,,,Route53RecoveryControlConfig,Route53RecoveryControlConfig,x,1,,aws_route53recoverycontrolconfig_,,route53recoverycontrolconfig_,Route 53 Recovery Control Config,Amazon,,,,, +route53-recovery-readiness,route53recoveryreadiness,route53recoveryreadiness,route53recoveryreadiness,,route53recoveryreadiness,,,Route53RecoveryReadiness,Route53RecoveryReadiness,x,1,,aws_route53recoveryreadiness_,,route53recoveryreadiness_,Route 53 Recovery Readiness,Amazon,,,,, +route53resolver,route53resolver,route53resolver,route53resolver,,route53resolver,,,Route53Resolver,Route53Resolver,,1,aws_route53_resolver_,aws_route53resolver_,,route53_resolver_,Route 53 Resolver,Amazon,,,,, +s3api,s3api,s3,s3,,s3,,s3api,S3,S3,x,1,aws_(canonical_user_id|s3_bucket|s3_object),aws_s3_,,s3_bucket;s3_object;canonical_user_id,S3 (Simple Storage),Amazon,,,AWS_S3_ENDPOINT,TF_AWS_S3_ENDPOINT, +s3control,s3control,s3control,s3control,,s3control,,,S3Control,S3Control,,1,aws_(s3_account_|s3control_|s3_access_),aws_s3control_,,s3control;s3_account_;s3_access_,S3 Control,Amazon,,,,, +glacier,glacier,glacier,glacier,,glacier,,,Glacier,Glacier,,1,,aws_glacier_,,glacier_,S3 Glacier,Amazon,,,,, +s3outposts,s3outposts,s3outposts,s3outposts,,s3outposts,,,S3Outposts,S3Outposts,,1,,aws_s3outposts_,,s3outposts_,S3 on Outposts,Amazon,,,,, +sagemaker,sagemaker,sagemaker,sagemaker,,sagemaker,,,SageMaker,SageMaker,,1,,aws_sagemaker_,,sagemaker_,SageMaker,Amazon,,,,, +sagemaker-a2i-runtime,sagemakera2iruntime,augmentedairuntime,sagemakera2iruntime,,sagemakera2iruntime,,augmentedairuntime,SageMakerA2IRuntime,AugmentedAIRuntime,,1,,aws_sagemakera2iruntime_,,sagemakera2iruntime_,SageMaker A2I (Augmented AI),Amazon,,,,, +sagemaker-edge,sagemakeredge,sagemakeredgemanager,sagemakeredge,,sagemakeredge,,sagemakeredgemanager,SageMakerEdge,SagemakerEdgeManager,,1,,aws_sagemakeredge_,,sagemakeredge_,SageMaker Edge Manager,Amazon,,,,, +sagemaker-featurestore-runtime,sagemakerfeaturestoreruntime,sagemakerfeaturestoreruntime,sagemakerfeaturestoreruntime,,sagemakerfeaturestoreruntime,,,SageMakerFeatureStoreRuntime,SageMakerFeatureStoreRuntime,,1,,aws_sagemakerfeaturestoreruntime_,,sagemakerfeaturestoreruntime_,SageMaker Feature Store Runtime,Amazon,,,,, +sagemaker-runtime,sagemakerruntime,sagemakerruntime,sagemakerruntime,,sagemakerruntime,,,SageMakerRuntime,SageMakerRuntime,,1,,aws_sagemakerruntime_,,sagemakerruntime_,SageMaker Runtime,Amazon,,,,, +,,,,,,,,,,,,,,,,SAM (Serverless Application Model),AWS,x,,,,No SDK support +savingsplans,savingsplans,savingsplans,savingsplans,,savingsplans,,,SavingsPlans,SavingsPlans,,1,,aws_savingsplans_,,savingsplans_,Savings Plans,AWS,,,,, +,,,,,,,,,,,,,,,,Schema Conversion Tool,AWS,x,,,,No SDK support +sdb,sdb,simpledb,,simpledb,sdb,,sdb,SimpleDB,SimpleDB,,1,aws_simpledb_,aws_sdb_,,simpledb_,SDB (SimpleDB),Amazon,,,,, +secretsmanager,secretsmanager,secretsmanager,secretsmanager,,secretsmanager,,,SecretsManager,SecretsManager,,1,,aws_secretsmanager_,,secretsmanager_,Secrets Manager,AWS,,,,, +securityhub,securityhub,securityhub,securityhub,,securityhub,,,SecurityHub,SecurityHub,,1,,aws_securityhub_,,securityhub_,Security Hub,AWS,,,,, +serverlessrepo,serverlessrepo,serverlessapplicationrepository,serverlessapplicationrepository,,serverlessrepo,,serverlessapprepo;serverlessapplicationrepository,ServerlessRepo,ServerlessApplicationRepository,,1,aws_serverlessapplicationrepository_,aws_serverlessrepo_,,serverlessapplicationrepository_,Serverless Application Repository,AWS,,,,, +servicecatalog,servicecatalog,servicecatalog,servicecatalog,,servicecatalog,,,ServiceCatalog,ServiceCatalog,,1,,aws_servicecatalog_,,servicecatalog_,Service Catalog,AWS,,,,, +servicecatalog-appregistry,servicecatalogappregistry,appregistry,servicecatalogappregistry,,servicecatalogappregistry,,appregistry,ServiceCatalogAppRegistry,AppRegistry,,1,,aws_servicecatalogappregistry_,,servicecatalogappregistry_,Service Catalog AppRegistry,AWS,,,,, +service-quotas,servicequotas,servicequotas,servicequotas,,servicequotas,,,ServiceQuotas,ServiceQuotas,,1,,aws_servicequotas_,,servicequotas_,Service Quotas,,,,,, +ses,ses,ses,ses,,ses,,,SES,SES,,1,,aws_ses_,,ses_,SES (Simple Email),Amazon,,,,, +sesv2,sesv2,sesv2,sesv2,,sesv2,,,SESV2,SESV2,,1,,aws_sesv2_,,sesv2_,SESv2 (Simple Email V2),Amazon,,,,, +stepfunctions,stepfunctions,sfn,sfn,,sfn,,stepfunctions,SFN,SFN,,1,,aws_sfn_,,sfn_,SFN (Step Functions),AWS,,,,, +shield,shield,shield,shield,,shield,,,Shield,Shield,x,1,,aws_shield_,,shield_,Shield,AWS,,,,, +signer,signer,signer,signer,,signer,,,Signer,Signer,,1,,aws_signer_,,signer_,Signer,AWS,,,,, +sms,sms,sms,sms,,sms,,,SMS,SMS,,1,,aws_sms_,,sms_,SMS (Server Migration),AWS,,,,, +snow-device-management,snowdevicemanagement,snowdevicemanagement,snowdevicemanagement,,snowdevicemanagement,,,SnowDeviceManagement,SnowDeviceManagement,,1,,aws_snowdevicemanagement_,,snowdevicemanagement_,Snow Device Management,AWS,,,,, +snowball,snowball,snowball,snowball,,snowball,,,Snowball,Snowball,,1,,aws_snowball_,,snowball_,Snow Family,AWS,,,,, +sns,sns,sns,sns,,sns,,,SNS,SNS,,1,,aws_sns_,,sns_,SNS (Simple Notification),Amazon,,,,, +sqs,sqs,sqs,sqs,,sqs,,,SQS,SQS,,1,,aws_sqs_,,sqs_,SQS (Simple Queue),Amazon,,,,, +ssm,ssm,ssm,ssm,,ssm,,,SSM,SSM,,1,,aws_ssm_,,ssm_,SSM (Systems Manager),AWS,,,,, +ssm-contacts,ssmcontacts,ssmcontacts,ssmcontacts,,ssmcontacts,,,SSMContacts,SSMContacts,,1,,aws_ssmcontacts_,,ssmcontacts_,SSM Incident Manager Contacts,AWS,,,,, +ssm-incidents,ssmincidents,ssmincidents,ssmincidents,,ssmincidents,,,SSMIncidents,SSMIncidents,,1,,aws_ssmincidents_,,ssmincidents_,SSM Incident Manager Incidents,AWS,,,,, +sso,sso,sso,sso,,sso,,,SSO,SSO,,1,,aws_sso_,,sso_,SSO (Single Sign-On),AWS,,,,, +sso-admin,ssoadmin,ssoadmin,ssoadmin,,ssoadmin,,,SSOAdmin,SSOAdmin,,1,,aws_ssoadmin_,,ssoadmin_,SSO Admin,AWS,,,,, +identitystore,identitystore,identitystore,identitystore,,identitystore,,,IdentityStore,IdentityStore,,1,,aws_identitystore_,,identitystore_,SSO Identity Store,AWS,,,,, +sso-oidc,ssooidc,ssooidc,ssooidc,,ssooidc,,,SSOOIDC,SSOOIDC,,1,,aws_ssooidc_,,ssooidc_,SSO OIDC,AWS,,,,, +storagegateway,storagegateway,storagegateway,storagegateway,,storagegateway,,,StorageGateway,StorageGateway,,1,,aws_storagegateway_,,storagegateway_,Storage Gateway,AWS,,,,, +sts,sts,sts,sts,,sts,,,STS,STS,x,1,aws_caller_identity,aws_sts_,,caller_identity,STS (Security Token),AWS,,,AWS_STS_ENDPOINT,TF_AWS_STS_ENDPOINT, +,,,,,,,,,,,,,,,,Sumerian,Amazon,x,,,,No SDK support +support,support,support,support,,support,,,Support,Support,,1,,aws_support_,,support_,Support,AWS,,,,, +swf,swf,swf,swf,,swf,,,SWF,SWF,,1,,aws_swf_,,swf_,SWF (Simple Workflow),Amazon,,,,, +,,,,,,,,,,,,,,,,Tag Editor,AWS,x,,,,Part of Resource Groups Tagging +textract,textract,textract,textract,,textract,,,Textract,Textract,,1,,aws_textract_,,textract_,Textract,Amazon,,,,, +timestream-query,timestreamquery,timestreamquery,timestreamquery,,timestreamquery,,,TimestreamQuery,TimestreamQuery,,1,,aws_timestreamquery_,,timestreamquery_,Timestream Query,Amazon,,,,, +timestream-write,timestreamwrite,timestreamwrite,timestreamwrite,,timestreamwrite,,,TimestreamWrite,TimestreamWrite,,1,,aws_timestreamwrite_,,timestreamwrite_,Timestream Write,Amazon,,,,, +,,,,,,,,,,,,,,,,Tools for PowerShell,AWS,x,,,,No SDK support +,,,,,,,,,,,,,,,,Training and Certification,AWS,x,,,,No SDK support +transcribe,transcribe,transcribeservice,transcribe,,transcribe,,transcribeservice,Transcribe,TranscribeService,x,2,,aws_transcribe_,,transcribe_,Transcribe,Amazon,,,,, +,,transcribestreamingservice,transcribestreaming,,transcribestreaming,,transcribestreamingservice,TranscribeStreaming,TranscribeStreamingService,,1,,aws_transcribestreaming_,,transcribestreaming_,Transcribe Streaming,Amazon,,,,, +transfer,transfer,transfer,transfer,,transfer,,,Transfer,Transfer,,1,,aws_transfer_,,transfer_,Transfer Family,AWS,,,,, +,,,,,transitgateway,ec2,,TransitGateway,,,,aws_ec2_transit_gateway,aws_transitgateway_,transitgateway_,ec2_transit_gateway,Transit Gateway,AWS,x,x,,,Part of EC2 +translate,translate,translate,translate,,translate,,,Translate,Translate,,1,,aws_translate_,,translate_,Translate,Amazon,,,,, +,,,,,,,,,,,,,,,,Trusted Advisor,AWS,x,,,,Part of Support +,,,,,vpc,ec2,,VPC,,,,aws_((default_)?(network_acl|route_table|security_group|subnet|vpc(?!_ipam))|ec2_(managed|network|subnet|traffic)|egress_only_internet|flow_log|internet_gateway|main_route_table_association|nat_gateway|network_interface|prefix_list|route\b),aws_vpc_,vpc_,default_network_;default_route_;default_security_;default_subnet;default_vpc;ec2_managed_;ec2_network_;ec2_subnet_;ec2_traffic_;egress_only_;flow_log;internet_gateway;main_route_;nat_;network_;prefix_list;route_;route\.;security_group;subnet;vpc_dhcp_;vpc_endpoint;vpc_ipv;vpc_peering_;vpc\.;vpcs\.,VPC (Virtual Private Cloud),Amazon,x,x,,,Part of EC2 +,,,,,ipam,ec2,,IPAM,,,,aws_vpc_ipam,aws_ipam_,ipam_,vpc_ipam,VPC IPAM (IP Address Manager),Amazon,x,x,,,Part of EC2 +,,,,,vpnclient,ec2,,ClientVPN,,,,aws_ec2_client_vpn,aws_vpnclient_,vpnclient_,ec2_client_vpn_,VPN (Client),AWS,x,x,,,Part of EC2 +,,,,,vpnsite,ec2,,SiteVPN,,,,aws_(customer_gateway|vpn_),aws_vpnsite_,vpnsite_,customer_gateway;vpn_,VPN (Site-to-Site),AWS,x,x,,,Part of EC2 +wafv2,wafv2,wafv2,wafv2,,wafv2,,,WAFV2,WAFV2,,1,,aws_wafv2_,,wafv2_,WAF,AWS,,,,, +waf,waf,waf,waf,,waf,,,WAF,WAF,,1,,aws_waf_,,waf_,WAF Classic,AWS,,,,, +waf-regional,wafregional,wafregional,wafregional,,wafregional,,,WAFRegional,WAFRegional,,1,,aws_wafregional_,,wafregional_,WAF Classic Regional,AWS,,,,, +,,,,,,,,,,,,,,,,WAM (WorkSpaces Application Manager),Amazon,x,,,,No SDK support +,,,,,wavelength,ec2,,Wavelength,,,,aws_ec2_carrier_gateway,aws_wavelength_,wavelength_,ec2_carrier_,Wavelength,AWS,x,x,,,Part of EC2 +budgets,budgets,budgets,budgets,,budgets,,,Budgets,Budgets,,1,,aws_budgets_,,budgets_,Web Services Budgets,Amazon,,,,, +wellarchitected,wellarchitected,wellarchitected,wellarchitected,,wellarchitected,,,WellArchitected,WellArchitected,,1,,aws_wellarchitected_,,wellarchitected_,Well-Architected Tool,AWS,,,,, +workdocs,workdocs,workdocs,workdocs,,workdocs,,,WorkDocs,WorkDocs,,1,,aws_workdocs_,,workdocs_,WorkDocs,Amazon,,,,, +worklink,worklink,worklink,worklink,,worklink,,,WorkLink,WorkLink,,1,,aws_worklink_,,worklink_,WorkLink,Amazon,,,,, +workmail,workmail,workmail,workmail,,workmail,,,WorkMail,WorkMail,,1,,aws_workmail_,,workmail_,WorkMail,Amazon,,,,, +workmailmessageflow,workmailmessageflow,workmailmessageflow,workmailmessageflow,,workmailmessageflow,,,WorkMailMessageFlow,WorkMailMessageFlow,,1,,aws_workmailmessageflow_,,workmailmessageflow_,WorkMail Message Flow,Amazon,,,,, +workspaces,workspaces,workspaces,workspaces,,workspaces,,,WorkSpaces,WorkSpaces,,1,,aws_workspaces_,,workspaces_,WorkSpaces,Amazon,,,,, +workspaces-web,workspacesweb,workspacesweb,workspacesweb,,workspacesweb,,,WorkSpacesWeb,WorkSpacesWeb,,1,,aws_workspacesweb_,,workspacesweb_,WorkSpaces Web,Amazon,,,,, +xray,xray,xray,xray,,xray,,,XRay,XRay,,1,,aws_xray_,,xray_,X-Ray,AWS,,,,, \ No newline at end of file