Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f83ebd5a39 | ||
|
|
c8c29eea51 | ||
|
|
11c036e28b | ||
|
|
69c24e0ade | ||
|
|
e87d7be2d1 | ||
|
|
77484a3fc9 | ||
|
|
1ad95dc698 |
No files matched your search
@@ -14,7 +14,7 @@ require (
|
|||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/boundary/sdk v0.0.59
|
github.com/hashicorp/boundary/sdk v0.0.59
|
||||||
github.com/hashicorp/go-multierror v1.1.1
|
github.com/hashicorp/go-multierror v1.1.1
|
||||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2
|
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.3
|
||||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0
|
||||||
github.com/hashicorp/terraform-json v0.23.0
|
github.com/hashicorp/terraform-json v0.23.0
|
||||||
github.com/mitchellh/mapstructure v1.5.0
|
github.com/mitchellh/mapstructure v1.5.0
|
||||||
|
|||||||
@@ -8,12 +8,8 @@ github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew
|
|||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
|
||||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE=
|
|
||||||
github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
|
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1/go.mod h1:ddqbooRZYNoJ2dsTwOty16rM+/Aqmk/GOXrK8cg7V00=
|
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
|
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
|
||||||
@@ -22,42 +18,26 @@ github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgb
|
|||||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20/go.mod h1:WZ/c+w0ofps+/OUqMwWgnfrgzZH1DZO1RIkktICsqnY=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8/go.mod h1:KcGkXFVU8U28qS4KvLEcPxytPZPBcRawaH2Pf/0jptE=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8/go.mod h1:JnA+hPWeYAVbDssp83tv+ysAG8lTfLVXvSsyKg/7xNA=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1/go.mod h1:FbtygfRFze9usAadmnGJNc8KsP346kEe+y2/oyhGAGc=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8 h1:1/bT9kDdLQzfZ1e6J6hpW+SfNDd6xrV8F3M2CuGyUz8=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8/go.mod h1:RbdwTONAIi59ej/+1H+QzZORt5bcyAtbrS7FQb2pvz0=
|
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0 h1:fTLR6dLDTGChAjecRPlVrKeznT0rVdzR4yn9Z68MTGk=
|
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0 h1:fTLR6dLDTGChAjecRPlVrKeznT0rVdzR4yn9Z68MTGk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0/go.mod h1:V0jbRy1/IPapnkqgXSwVOFB+u5pnCwd9S+R3pKWULC4=
|
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0/go.mod h1:V0jbRy1/IPapnkqgXSwVOFB+u5pnCwd9S+R3pKWULC4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6 h1:EWehQXACWr+6hzfZPwZChlfoVhiUCfLHE0Xh3kAfzWQ=
|
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6 h1:EWehQXACWr+6hzfZPwZChlfoVhiUCfLHE0Xh3kAfzWQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6/go.mod h1:qRXgEBWPIltrWHQwU+HkyBvwh1QgeigFcaCGCIVrWk0=
|
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6/go.mod h1:qRXgEBWPIltrWHQwU+HkyBvwh1QgeigFcaCGCIVrWk0=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1 h1:oegbebPEMA/1Jny7kvwejowCaHz1FWZAQ94WXFNCyTM=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.1/go.mod h1:kemo5Myr9ac0U9JfSjMo9yHLtw+pECEHsFtJ9tqCEI8=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8 h1:tIN8MFT1z5STK5kTdOT1TCfMN/bn5fSEnlKsTL8qBOU=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8/go.mod h1:VKS56txtNWjKI8FqD/hliL0BcshyF4ZaLBa1rm2Y+5s=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8/go.mod h1:Fw+MyTwlwjFsSTE31mH211Np+CUslml8mzc0AFEG09s=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.8 h1:AgYCo1Rb8XChJXA871BXHDNxNWOTAr6V5YdsRIBbgv0=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.8/go.mod h1:Au9dvIGm1Hbqnt29d3VakOCQuN9l0WrkDDTRq8biWS4=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8=
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.2 h1:T7b3qniouutV5Wwa9B1q7gW+Y8s1B3g9RE9qa7zLBIM=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.2/go.mod h1:tW9TsLb6t1eaTdBE6LITyJW1m/+DjQPU78Q/jT2FJu8=
|
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4=
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 h1:3zu537oLmsPfDMyjnUS2g+F2vITgy5pB74tHI+JBNoM=
|
||||||
@@ -66,8 +46,6 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3
|
|||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5/go.mod h1:ORITg+fyuMoeiQFiVGoqB3OydVTLkClw/ljbblMq6Cc=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5 h1:+LVB0xBqEgjQoqr9bGZbRzvg212B0f17JdflleJRNR4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0=
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5/go.mod h1:xoaxeqnnUaZjPjaICgIy5B+MHCSb/ZSOn4MvkFNOUA0=
|
||||||
github.com/aws/smithy-go v1.23.0 h1:8n6I3gXzWJB2DxBDnfxgBaSX6oe0d/t10qGz7OKqMCE=
|
|
||||||
github.com/aws/smithy-go v1.23.0/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
|
|
||||||
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
|
github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI=
|
||||||
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
||||||
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
|
||||||
@@ -116,8 +94,8 @@ github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+l
|
|||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||||
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
|
||||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2 h1:KlDrZWx/wYpnUcFPB0s9uhPjyNZoeIfhrV2MN/Ly76s=
|
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.3 h1:mLe7IMysLSedhuu00W7AXsbfb90AA7UKfZmw3OkJ8n4=
|
||||||
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.2/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8=
|
github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.1.3/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2 h1:ET4pqyjiGmY09R5y+rSd70J2w45CtbWDNvGqWp/R3Ng=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
github.com/hashicorp/go-secure-stdlib/base62 v0.1.2/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
||||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk=
|
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk=
|
||||||
|
|||||||
@@ -112,10 +112,35 @@ func NewAwsCredentialPersistedState(opts ...AwsCredentialPersistedStateOption) (
|
|||||||
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
// and runs sts.GetCallerIdentity for the current credentials, which is done
|
||||||
// to check that the credentials are valid. This method returns a status error
|
// to check that the credentials are valid. This method returns a status error
|
||||||
// with PluginError details.
|
// with PluginError details.
|
||||||
|
//
|
||||||
|
// When RoleARN is set and no static key is present (ambient role path),
|
||||||
|
// credentials are eagerly retrieved before calling GetCallerIdentity. This
|
||||||
|
// forces sts:AssumeRole to be called immediately so that any role errors
|
||||||
|
// (e.g. no such role, access denied) surface here rather than being deferred
|
||||||
|
// to the first real AWS API call.
|
||||||
func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
func (s *AwsCredentialPersistedState) ValidateCreds(ctx context.Context) error {
|
||||||
if s.CredentialsConfig == nil {
|
if s.CredentialsConfig == nil {
|
||||||
return errors.BadRequestStatus("missing credentials config")
|
return errors.BadRequestStatus("missing credentials config")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For the ambient role path (RoleARN set, no static key), eagerly resolve
|
||||||
|
// the credential chain so that sts:AssumeRole is called now. Without this,
|
||||||
|
// the AssumeRoleProvider inside the chain is lazy and GetCallerIdentity
|
||||||
|
// would succeed even with an invalid role ARN (it uses the mock/ambient
|
||||||
|
// identity directly rather than the assumed one).
|
||||||
|
cc := s.CredentialsConfig
|
||||||
|
if cc.RoleARN != "" && cc.AccessKey == "" {
|
||||||
|
cfg, err := s.GenerateCredentialChain(ctx)
|
||||||
|
if err != nil {
|
||||||
|
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||||
|
return st.Err()
|
||||||
|
}
|
||||||
|
if _, err := cfg.Credentials.Retrieve(ctx); err != nil {
|
||||||
|
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||||
|
return st.Err()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
if _, err := s.CredentialsConfig.GetCallerIdentity(ctx, s.testOpts...); err != nil {
|
||||||
st, _ := errors.ParseAWSError("validating credentials", err)
|
st, _ := errors.ParseAWSError("validating credentials", err)
|
||||||
return st.Err()
|
return st.Err()
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ const (
|
|||||||
// the owner. This error cannot be resolved by retrying the request.
|
// the owner. This error cannot be resolved by retrying the request.
|
||||||
awsErrorAccessDenied = "AccessDenied"
|
awsErrorAccessDenied = "AccessDenied"
|
||||||
|
|
||||||
|
// awsErrorUnauthorizedOperation is returned by EC2 when the caller is not
|
||||||
|
// authorized to perform the operation. This includes DryRun permission
|
||||||
|
// checks that fail (the counterpart to DryRunOperation success).
|
||||||
|
awsErrorUnauthorizedOperation = "UnauthorizedOperation"
|
||||||
|
|
||||||
// awsErrorInvalidAccessKeyId is returned when the credential does
|
// awsErrorInvalidAccessKeyId is returned when the credential does
|
||||||
// not exist. This error will persist until the credentials attached
|
// not exist. This error will persist until the credentials attached
|
||||||
// to the storage bucket is updated. This error can occur when the
|
// to the storage bucket is updated. This error can occur when the
|
||||||
@@ -66,8 +71,26 @@ const (
|
|||||||
|
|
||||||
// RequestTimeoutException is returned when an http request takes longer than allowed
|
// RequestTimeoutException is returned when an http request takes longer than allowed
|
||||||
awsErrorRequestTimeoutException = "RequestTimeoutException"
|
awsErrorRequestTimeoutException = "RequestTimeoutException"
|
||||||
|
|
||||||
|
// dryRunOperationErrorCode is returned by AWS when a DryRun request would have
|
||||||
|
// succeeded. It is a success signal disguised as an API error (typically HTTP 412).
|
||||||
|
dryRunOperationErrorCode = "DryRunOperation"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// IsDryRunSuccess reports whether err is the AWS DryRunOperation success signal.
|
||||||
|
// A true result means the dry-run validated credentials, permissions, and parameters;
|
||||||
|
// it must not be treated as a failure.
|
||||||
|
func IsDryRunSuccess(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var apiErr smithy.APIError
|
||||||
|
if !errors.As(err, &apiErr) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return apiErr.ErrorCode() == dryRunOperationErrorCode
|
||||||
|
}
|
||||||
|
|
||||||
// InvalidArgumentError returns an grpc invalid argument status error.
|
// InvalidArgumentError returns an grpc invalid argument status error.
|
||||||
func InvalidArgumentError(msg string, f map[string]string) error {
|
func InvalidArgumentError(msg string, f map[string]string) error {
|
||||||
var fieldMsgs []string
|
var fieldMsgs []string
|
||||||
@@ -172,6 +195,8 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
|
|||||||
switch apiErr.ErrorCode() {
|
switch apiErr.ErrorCode() {
|
||||||
case awsErrorAccessDenied:
|
case awsErrorAccessDenied:
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case awsErrorUnauthorizedOperation:
|
||||||
|
fallthrough
|
||||||
case awsErrorInvalidAccessKeyId:
|
case awsErrorInvalidAccessKeyId:
|
||||||
fallthrough
|
fallthrough
|
||||||
case awsErrorExpiredToken:
|
case awsErrorExpiredToken:
|
||||||
|
|||||||
@@ -300,6 +300,17 @@ func Test_ParseAWSError(t *testing.T) {
|
|||||||
CheckedAt: timestamppb.Now(),
|
CheckedAt: timestamppb.Now(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "aws-request-unauthorized-operation",
|
||||||
|
err: TestAwsError(awsErrorUnauthorizedOperation, "You are not authorized to perform this operation."),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "You are not authorized to perform this operation.",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "aws-request-invalid-access-key-id",
|
name: "aws-request-invalid-access-key-id",
|
||||||
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
|
err: TestAwsError(awsErrorInvalidAccessKeyId, "The AWS Access Key Id you provided does not exist in our records."),
|
||||||
@@ -390,6 +401,46 @@ func Test_ParseAWSError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Test_IsDryRunSuccess(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
err error
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "plain error",
|
||||||
|
err: fmt.Errorf("not an aws error"),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "other api error",
|
||||||
|
err: TestAwsError(awsErrorAccessDenied, "not authorized"),
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dry run operation",
|
||||||
|
err: TestAwsError(dryRunOperationErrorCode, "Request would have succeeded, but DryRun flag is set"),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped dry run operation",
|
||||||
|
err: fmt.Errorf("describe instances: %w", TestAwsError(dryRunOperationErrorCode, "Request would have succeeded, but DryRun flag is set")),
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range cases {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
assert.Equal(t, tt.want, IsDryRunSuccess(tt.err))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func Test_BadRequestStatusf(t *testing.T) {
|
func Test_BadRequestStatusf(t *testing.T) {
|
||||||
require, assert := require.New(t), assert.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
err := BadRequestStatusf("test: %s", "hello world")
|
err := BadRequestStatusf("test: %s", "hello world")
|
||||||
|
|||||||
+107
-64
@@ -5,7 +5,6 @@ package host
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
goerrors "errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -15,7 +14,6 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/sts"
|
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
||||||
"github.com/aws/smithy-go"
|
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
"github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
@@ -44,8 +42,8 @@ type hostSetQuery struct {
|
|||||||
OutputHosts []*pb.ListHostsResponseHost
|
OutputHosts []*pb.ListHostsResponseHost
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultSessionName = "boundary-default"
|
const errBuildDescribeInstancesInput = "error building DescribeInstances input: %s"
|
||||||
const dryRunOperationErrorCode = "DryRunOperation"
|
const errDryRunFailed = "error performing DescribeInstances dry run: %s"
|
||||||
|
|
||||||
// Ensure that we are implementing HostPluginServiceServer
|
// Ensure that we are implementing HostPluginServiceServer
|
||||||
var _ pb.HostPluginServiceServer = (*HostPlugin)(nil)
|
var _ pb.HostPluginServiceServer = (*HostPlugin)(nil)
|
||||||
@@ -88,6 +86,15 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate assumed-role credentials at catalog-creation time so that an
|
||||||
|
// invalid role_arn is rejected immediately rather than silently falling
|
||||||
|
// back to ambient credentials.
|
||||||
|
if credential.GetCredentialType(credState.CredentialsConfig) == credential.DynamicAWS {
|
||||||
|
if err := credState.ValidateCreds(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
catalogState, err := newAwsCatalogPersistedState(
|
catalogState, err := newAwsCatalogPersistedState(
|
||||||
append([]awsCatalogPersistedStateOption{
|
append([]awsCatalogPersistedStateOption{
|
||||||
withCredentials(credState),
|
withCredentials(credState),
|
||||||
@@ -97,13 +104,21 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
|||||||
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform dry run to ensure we can interact with AWS as expected.
|
|
||||||
opts := []ec2Option{}
|
opts := []ec2Option{}
|
||||||
if catalogAttributes.DualStack {
|
if catalogAttributes.DualStack {
|
||||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||||
}
|
}
|
||||||
if st := dryRunValidation(ctx, catalogState, opts); st != nil {
|
input, err := buildDescribeInstancesInput(&SetAttributes{}, true)
|
||||||
return nil, st.Err()
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||||
|
}
|
||||||
|
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||||
|
// we can interact with AWS resources as expected.
|
||||||
|
if _, err := checkHosts(ctx, catalogState, input, opts, catalogAttributes.TargetAccount); err != nil {
|
||||||
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
persistedProto, err := catalogState.toProto()
|
persistedProto, err := catalogState.toProto()
|
||||||
@@ -178,6 +193,15 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate assumed-role credentials when the role_arn changes so that
|
||||||
|
// an invalid ARN is caught before replacing the existing credential state.
|
||||||
|
if credential.GetCredentialType(newCredState.CredentialsConfig) == credential.DynamicAWS {
|
||||||
|
if err := newCredState.ValidateCreds(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
newCatalogState, err := newAwsCatalogPersistedState(
|
newCatalogState, err := newAwsCatalogPersistedState(
|
||||||
append([]awsCatalogPersistedStateOption{
|
append([]awsCatalogPersistedStateOption{
|
||||||
withCredentials(newCredState),
|
withCredentials(newCredState),
|
||||||
@@ -186,12 +210,22 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []ec2Option{}
|
opts := []ec2Option{}
|
||||||
if newCatalogAttributes.DualStack {
|
if newCatalogAttributes.DualStack {
|
||||||
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
||||||
}
|
}
|
||||||
if st := dryRunValidation(ctx, newCatalogState, opts); st != nil {
|
input, err := buildDescribeInstancesInput(&SetAttributes{}, true)
|
||||||
return nil, st.Err()
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||||
|
}
|
||||||
|
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||||
|
// we can interact with AWS resources as expected.
|
||||||
|
if _, err := checkHosts(ctx, newCatalogState, input, opts, newCatalogAttributes.TargetAccount); err != nil {
|
||||||
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace the existing credential state.
|
// Replace the existing credential state.
|
||||||
@@ -231,13 +265,21 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// perform dry run to ensure we can interact with AWS as expected.
|
|
||||||
opts := []ec2Option{}
|
opts := []ec2Option{}
|
||||||
if newCatalogAttributes.DualStack {
|
if newCatalogAttributes.DualStack {
|
||||||
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
opts = append(opts, WithDualStack(newCatalogAttributes.DualStack))
|
||||||
}
|
}
|
||||||
if st := dryRunValidation(ctx, catalogState, opts); st != nil {
|
input, err := buildDescribeInstancesInput(&SetAttributes{}, true)
|
||||||
return nil, st.Err()
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||||
|
}
|
||||||
|
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||||
|
// we can interact with AWS resources as expected.
|
||||||
|
if _, err := checkHosts(ctx, catalogState, input, opts, newCatalogAttributes.TargetAccount); err != nil {
|
||||||
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
persistedProto, err := catalogState.toProto()
|
persistedProto, err := catalogState.toProto()
|
||||||
@@ -381,18 +423,23 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
describeInstanceFilters, err := buildFilters(setAttrs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.BadRequestStatusf("error building set filters: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := []ec2Option{}
|
opts := []ec2Option{}
|
||||||
if catalogAttributes.DualStack {
|
if catalogAttributes.DualStack {
|
||||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||||
}
|
}
|
||||||
if st := dryRunValidation(ctx, catalogState, opts, describeInstanceFilters...); st != nil {
|
input, err := buildDescribeInstancesInput(setAttrs, true)
|
||||||
return nil, st.Err()
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||||
}
|
}
|
||||||
|
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||||
|
// we can interact with AWS resources as expected.
|
||||||
|
if _, err := checkHosts(ctx, catalogState, input, opts, catalogAttributes.TargetAccount); err != nil {
|
||||||
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||||
|
}
|
||||||
|
|
||||||
return &pb.OnCreateSetResponse{}, nil
|
return &pb.OnCreateSetResponse{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -446,17 +493,21 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
describeInstanceFilters, err := buildFilters(setAttrs)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.BadRequestStatusf("error building set filters: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := []ec2Option{}
|
opts := []ec2Option{}
|
||||||
if catalogAttributes.DualStack {
|
if catalogAttributes.DualStack {
|
||||||
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
opts = append(opts, WithDualStack(catalogAttributes.DualStack))
|
||||||
}
|
}
|
||||||
if st := dryRunValidation(ctx, catalogState, opts, describeInstanceFilters...); st != nil {
|
input, err := buildDescribeInstancesInput(setAttrs, true)
|
||||||
return nil, st.Err()
|
if err != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, errBuildDescribeInstancesInput, err)
|
||||||
|
}
|
||||||
|
// Run a DescribeInstances request with DryRun set to true to ensure
|
||||||
|
// we can interact with AWS resources as expected.
|
||||||
|
if _, err := checkHosts(ctx, catalogState, input, opts, catalogAttributes.TargetAccount); err != nil {
|
||||||
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf(errDryRunFailed, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.OnUpdateSetResponse{}, nil
|
return &pb.OnUpdateSetResponse{}, nil
|
||||||
@@ -545,10 +596,14 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
for i, query := range queries {
|
for i, query := range queries {
|
||||||
output, err := checkHosts(ctx, catalogState, query.Input, opts, catalogAttributes.TargetAccount)
|
output, err := checkHosts(ctx, catalogState, query.Input, opts, catalogAttributes.TargetAccount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if catalogAttributes.TargetAccount != nil {
|
// Mapped AWS failures are already gRPC statuses from checkHosts.
|
||||||
return nil, errors.BadRequestStatusf("unable to list hosts for host set %q: %s", query.Id, err)
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
return nil, errors.BadRequestStatusf("error retrieving host results for host set id %q: %s", query.Id, err)
|
if catalogAttributes.TargetAccount != nil {
|
||||||
|
return nil, status.Errorf(codes.Unknown, "unable to list hosts for host set %q: %v", query.Id, err)
|
||||||
|
}
|
||||||
|
return nil, status.Errorf(codes.Unknown, "error retrieving host results for host set id %q: %v", query.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries[i].Output = output
|
queries[i].Output = output
|
||||||
@@ -562,7 +617,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
for _, instance := range reservation.Instances {
|
for _, instance := range reservation.Instances {
|
||||||
host, err := awsInstanceToHost(instance, catalogAttributes)
|
host, err := awsInstanceToHost(instance, catalogAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatusf("error processing host results for host set id %q: %s", query.Id, err)
|
return nil, status.Errorf(codes.Unknown, "error processing host results for host set id %q: %v", query.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
||||||
@@ -611,11 +666,11 @@ func buildFilters(attrs *SetAttributes) ([]types.Filter, error) {
|
|||||||
splitFilter := strings.Split(filterAttr, "=")
|
splitFilter := strings.Split(filterAttr, "=")
|
||||||
switch {
|
switch {
|
||||||
case len(splitFilter) != 2:
|
case len(splitFilter) != 2:
|
||||||
return nil, fmt.Errorf("expected filter %q to contain a single equal sign", filterAttr)
|
return nil, errors.BadRequestStatusf("expected filter %q to contain a single equal sign", filterAttr)
|
||||||
case len(splitFilter[0]) == 0:
|
case len(splitFilter[0]) == 0:
|
||||||
return nil, fmt.Errorf("filter %q contains an empty filter key", filterAttr)
|
return nil, errors.BadRequestStatusf("filter %q contains an empty filter key", filterAttr)
|
||||||
case len(splitFilter[1]) == 0:
|
case len(splitFilter[1]) == 0:
|
||||||
return nil, fmt.Errorf("filter %q contains an empty value", filterAttr)
|
return nil, errors.BadRequestStatusf("filter %q contains an empty value", filterAttr)
|
||||||
}
|
}
|
||||||
|
|
||||||
filterKey, filterValue := splitFilter[0], splitFilter[1]
|
filterKey, filterValue := splitFilter[0], splitFilter[1]
|
||||||
@@ -644,9 +699,13 @@ func buildFilters(attrs *SetAttributes) ([]types.Filter, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func buildDescribeInstancesInput(attrs *SetAttributes, dryRun bool) (*ec2.DescribeInstancesInput, error) {
|
func buildDescribeInstancesInput(attrs *SetAttributes, dryRun bool) (*ec2.DescribeInstancesInput, error) {
|
||||||
|
if attrs == nil {
|
||||||
|
attrs = &SetAttributes{}
|
||||||
|
}
|
||||||
|
|
||||||
filters, err := buildFilters(attrs)
|
filters, err := buildFilters(attrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("error building filters: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ec2.DescribeInstancesInput{
|
return &ec2.DescribeInstancesInput{
|
||||||
@@ -733,25 +792,31 @@ func checkHosts(ctx context.Context,
|
|||||||
// use the principal account to execute the DescribeInstances command
|
// use the principal account to execute the DescribeInstances command
|
||||||
ec2Client, err = principalCatalogState.EC2Client(ctx, opts...)
|
ec2Client, err = principalCatalogState.EC2Client(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if st, _ := errors.ParseAWSError("EC2 client setup", err); st.Code() != codes.Unknown {
|
||||||
|
return nil, st.Err()
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// use the target account to execute the DescribeInstances command
|
// use the target account to execute the DescribeInstances command
|
||||||
ec2Client, err = ec2ClientForTarget(ctx, principalCatalogState, target, opts)
|
ec2Client, err = ec2ClientForTarget(ctx, principalCatalogState, target, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if _, ok := status.FromError(err); ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("EC2 client setup failed for target account: %w", err)
|
return nil, fmt.Errorf("EC2 client setup failed for target account: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := ec2Client.DescribeInstances(ctx, input)
|
output, err := ec2Client.DescribeInstances(ctx, input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
var awsErr smithy.APIError
|
if errors.IsDryRunSuccess(err) {
|
||||||
ok := goerrors.As(err, &awsErr)
|
// DryRunOperation: request would have succeeded; not a failure.
|
||||||
if ok && awsErr.ErrorCode() == dryRunOperationErrorCode {
|
|
||||||
// successful DryRun validation, no response necessary, no error thrown
|
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
if st, _ := errors.ParseAWSError("DescribeInstances", err); st.Code() != codes.Unknown {
|
||||||
|
return nil, st.Err()
|
||||||
|
}
|
||||||
if target != nil {
|
if target != nil {
|
||||||
return nil, fmt.Errorf("EC2 DescribeInstances failed for target account: %w", err)
|
return nil, fmt.Errorf("EC2 DescribeInstances failed for target account: %w", err)
|
||||||
}
|
}
|
||||||
@@ -774,6 +839,9 @@ func ec2ClientForTarget(
|
|||||||
// principal identity).
|
// principal identity).
|
||||||
principalCfg, err := principalCatalogState.GenerateCredentialChain(ctx)
|
principalCfg, err := principalCatalogState.GenerateCredentialChain(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if st, _ := errors.ParseAWSError("EC2 client setup for target account", err); st.Code() != codes.Unknown {
|
||||||
|
return nil, st.Err()
|
||||||
|
}
|
||||||
return nil, fmt.Errorf("principal AWS configuration could not be loaded: %w", err)
|
return nil, fmt.Errorf("principal AWS configuration could not be loaded: %w", err)
|
||||||
}
|
}
|
||||||
if principalCfg == nil {
|
if principalCfg == nil {
|
||||||
@@ -787,9 +855,6 @@ func ec2ClientForTarget(
|
|||||||
provider := stscreds.NewAssumeRoleProvider(stsClient, target.RoleArn, func(o *stscreds.AssumeRoleOptions) {
|
provider := stscreds.NewAssumeRoleProvider(stsClient, target.RoleArn, func(o *stscreds.AssumeRoleOptions) {
|
||||||
if target.RoleSessionName != "" {
|
if target.RoleSessionName != "" {
|
||||||
o.RoleSessionName = target.RoleSessionName
|
o.RoleSessionName = target.RoleSessionName
|
||||||
} else {
|
|
||||||
// STS may reject the AssumeRole request if session name is not provided.
|
|
||||||
o.RoleSessionName = defaultSessionName
|
|
||||||
}
|
}
|
||||||
if target.RoleExternalId != "" {
|
if target.RoleExternalId != "" {
|
||||||
o.ExternalID = &target.RoleExternalId
|
o.ExternalID = &target.RoleExternalId
|
||||||
@@ -828,28 +893,6 @@ func ec2ClientForTarget(
|
|||||||
return ec2.NewFromConfig(targetCfg, ec2Opts...), nil
|
return ec2.NewFromConfig(targetCfg, ec2Opts...), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dryRunValidation performs an AWS DescribeInstances call to verify the state's
|
|
||||||
// credentials, the host listing functionality as well as the filters, if any
|
|
||||||
// are passed in. This function can therefore be used for both host catalog and
|
|
||||||
// host set validation.
|
|
||||||
func dryRunValidation(ctx context.Context, state *awsCatalogPersistedState, ec2Opts []ec2Option, filters ...types.Filter) *status.Status {
|
|
||||||
if state == nil {
|
|
||||||
return status.New(codes.InvalidArgument, "persisted state is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
ec2Client, err := state.EC2Client(ctx, ec2Opts...)
|
|
||||||
if err != nil {
|
|
||||||
return status.New(codes.InvalidArgument, fmt.Sprintf("error getting EC2 client: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ec2Client.DescribeInstances(ctx, &ec2.DescribeInstancesInput{Filters: filters})
|
|
||||||
if err != nil {
|
|
||||||
return status.New(codes.FailedPrecondition, fmt.Sprintf("aws describe instances failed: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// appendDistinct will append the elements to the slice
|
// appendDistinct will append the elements to the slice
|
||||||
// if an element is not nil, empty, and does not exist in slice.
|
// if an element is not nil, empty, and does not exist in slice.
|
||||||
func appendDistinct(slice []string, elems ...*string) []string {
|
func appendDistinct(slice []string, elems ...*string) []string {
|
||||||
|
|||||||
@@ -16,8 +16,11 @@ import (
|
|||||||
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
"github.com/aws/aws-sdk-go-v2/service/ec2/types"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/iam"
|
"github.com/aws/aws-sdk-go-v2/service/iam"
|
||||||
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/sts"
|
||||||
|
stsTypes "github.com/aws/aws-sdk-go-v2/service/sts/types"
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
|
awserrors "github.com/hashicorp/boundary-plugin-aws/internal/errors"
|
||||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
@@ -28,6 +31,38 @@ import (
|
|||||||
"google.golang.org/protobuf/testing/protocmp"
|
"google.golang.org/protobuf/testing/protocmp"
|
||||||
"google.golang.org/protobuf/types/known/structpb"
|
"google.golang.org/protobuf/types/known/structpb"
|
||||||
)
|
)
|
||||||
|
// testRoleARN is a syntactically valid ARN used for role_arn test cases.
|
||||||
|
// All STS calls against it are handled by injected mocks — no real AWS
|
||||||
|
// account is contacted.
|
||||||
|
const testRoleARN = "arn:aws:iam::123456789012:role/BoundaryTestRole"
|
||||||
|
|
||||||
|
// validAssumeRoleOutput returns a mock AssumeRoleOutput with synthetic
|
||||||
|
// temporary credentials for use with WithSTSAPIFunc.
|
||||||
|
func validAssumeRoleOutput() *sts.AssumeRoleOutput {
|
||||||
|
return &sts.AssumeRoleOutput{
|
||||||
|
Credentials: &stsTypes.Credentials{
|
||||||
|
AccessKeyId: aws.String("ASIAmockassumedkey"),
|
||||||
|
SecretAccessKey: aws.String("mockassumedsecret"),
|
||||||
|
SessionToken: aws.String("mockassumedsessiontoken"),
|
||||||
|
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// catalogAttrsStruct builds a catalog attributes structpb for the given
|
||||||
|
// region. roleArn is written only when non-empty. disableRotation must be
|
||||||
|
// set explicitly to avoid implicit rotation behaviour in tests.
|
||||||
|
func catalogAttrsStruct(region, roleArn string, disableRotation bool) *structpb.Struct {
|
||||||
|
fields := map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue(region),
|
||||||
|
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(disableRotation),
|
||||||
|
}
|
||||||
|
if roleArn != "" {
|
||||||
|
fields[credential.ConstRoleArn] = structpb.NewStringValue(roleArn)
|
||||||
|
}
|
||||||
|
return &structpb.Struct{Fields: fields}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@@ -138,17 +173,13 @@ func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
// role_arn set, no static keys. AssumeRole mock succeeds.
|
||||||
|
// Persisted secrets are empty — dynamic credentials are not stored.
|
||||||
name: "usingAssumeRole",
|
name: "usingAssumeRole",
|
||||||
req: &pb.OnCreateCatalogRequest{
|
req: &pb.OnCreateCatalogRequest{
|
||||||
Catalog: &hostcatalogs.HostCatalog{
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: catalogAttrsStruct("us-east-1", testRoleARN, true),
|
||||||
Fields: map[string]*structpb.Value{
|
|
||||||
credential.ConstRegion: structpb.NewStringValue("us-east-1"),
|
|
||||||
credential.ConstRoleArn: structpb.NewStringValue("arn:0123:test:rolearn"),
|
|
||||||
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -158,6 +189,13 @@ func TestPluginOnCreateCatalogSuccess(t *testing.T) {
|
|||||||
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||||
|
credential.WithStateTestOpts([]awsutil.Option{
|
||||||
|
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||||
|
awsutil.WithAssumeRoleOutput(validAssumeRoleOutput()),
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
expRsp: &pb.OnCreateCatalogResponse{Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}}},
|
expRsp: &pb.OnCreateCatalogResponse{Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}}},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -315,6 +353,20 @@ func TestPluginOnUpdateCatalogSuccess(t *testing.T) {
|
|||||||
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
|
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||||
|
credential.WithStateTestOpts([]awsutil.Option{
|
||||||
|
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||||
|
awsutil.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||||
|
Credentials: &stsTypes.Credentials{
|
||||||
|
AccessKeyId: aws.String("ASIAfoobar"),
|
||||||
|
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||||
|
SessionToken: aws.String("sessiontokenfoobar"),
|
||||||
|
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
expRsp: &pb.OnUpdateCatalogResponse{
|
expRsp: &pb.OnUpdateCatalogResponse{
|
||||||
Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}},
|
Persisted: &pb.HostCatalogPersisted{Secrets: &structpb.Struct{}},
|
||||||
},
|
},
|
||||||
@@ -421,6 +473,16 @@ func TestPluginOnUpdateCatalogSuccess(t *testing.T) {
|
|||||||
credOpts: []credential.AwsCredentialPersistedStateOption{
|
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||||
credential.WithStateTestOpts([]awsutil.Option{
|
credential.WithStateTestOpts([]awsutil.Option{
|
||||||
awsutil.WithIAMAPIFunc(awsutil.NewMockIAM()),
|
awsutil.WithIAMAPIFunc(awsutil.NewMockIAM()),
|
||||||
|
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||||
|
awsutil.WithAssumeRoleOutput(&sts.AssumeRoleOutput{
|
||||||
|
Credentials: &stsTypes.Credentials{
|
||||||
|
AccessKeyId: aws.String("ASIAfoobar"),
|
||||||
|
SecretAccessKey: aws.String("secretkeyfoobar"),
|
||||||
|
SessionToken: aws.String("sessiontokenfoobar"),
|
||||||
|
Expiration: aws.Time(time.Now().Add(time.Hour)),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
)),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expRsp: &pb.OnUpdateCatalogResponse{
|
expRsp: &pb.OnUpdateCatalogResponse{
|
||||||
@@ -742,8 +804,72 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "aws describe instances failed: DescribeInstances error",
|
// checkHosts returns the DescribeInstances error as-is rather than
|
||||||
expectedErrCode: codes.FailedPrecondition,
|
// wrapping it in a gRPC FailedPrecondition status like the old
|
||||||
|
// dryRunValidation did. A plain Go error maps to codes.Unknown.
|
||||||
|
expectedErrContains: testDescribeInstancesError,
|
||||||
|
expectedErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dry run status error preserves gRPC code",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
// checkHosts wraps the AWS error via ParseAWSError into a gRPC
|
||||||
|
// PermissionDenied status. OnCreateCatalog's dry-run error handler
|
||||||
|
// should preserve that status code, not re-wrap it as Unknown.
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// BUG FIX: role_arn set, no static keys. AssumeRole mock returns
|
||||||
|
// an error. ValidateCreds now eagerly calls Retrieve() which forces
|
||||||
|
// sts:AssumeRole, so the error surfaces here at creation time.
|
||||||
|
// Previously this succeeded silently via the ambient credential chain.
|
||||||
|
name: "role_arn AssumeRole error — correctly rejected",
|
||||||
|
req: &pb.OnCreateCatalogRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: catalogAttrsStruct("us-east-1", testRoleARN, true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||||
|
credential.WithStateTestOpts([]awsutil.Option{
|
||||||
|
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||||
|
awsutil.WithAssumeRoleError(errors.New("simulated AssumeRole failure")),
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{}),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
expectedErrContains: "simulated AssumeRole failure",
|
||||||
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -994,8 +1120,11 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
|||||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "aws describe instances failed: DescribeInstances error",
|
//checkHosts returns the DesribeInstances errror as-is rather than wrapping it
|
||||||
expectedErrCode: codes.FailedPrecondition,
|
// in a gRpc FailedPrecodndition status like the old dryRunValidiation did.
|
||||||
|
// A plain Go error maps to codeds.Unknown.
|
||||||
|
expectedErrContains: testDescribeInstancesError,
|
||||||
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "new incoming dynamic credential update dry run error",
|
name: "new incoming dynamic credential update dry run error",
|
||||||
@@ -1030,14 +1159,27 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
// ValidateCreds is called for DynamicAWS credentials before the dry-run
|
||||||
|
// DescribeInstances. Provide a mock STS that returns a successful AssumeRole
|
||||||
|
// so the validate step passes and execution reaches the EC2 mock.
|
||||||
|
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||||
|
credential.WithStateTestOpts([]awsutil.Option{
|
||||||
|
awsutil.WithSTSAPIFunc(awsutil.NewMockSTS(
|
||||||
|
awsutil.WithAssumeRoleOutput(validAssumeRoleOutput()),
|
||||||
|
)),
|
||||||
|
}),
|
||||||
|
},
|
||||||
catalogOpts: []awsCatalogPersistedStateOption{
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
withTestEC2APIFunc(newTestMockEC2(
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
nil,
|
nil,
|
||||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "aws describe instances failed: DescribeInstances error",
|
// checkHosts returns the DescribeInstances error as-is rather than wrapping it
|
||||||
expectedErrCode: codes.FailedPrecondition,
|
// in a gRPC FailedPrecondition status like the old dryRunValidiation did.
|
||||||
|
// A plain Go error maps to codes.Unknown.
|
||||||
|
expectedErrContains: testDescribeInstancesError,
|
||||||
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "replace creds error",
|
name: "replace creds error",
|
||||||
@@ -1211,8 +1353,53 @@ func TestPluginOnUpdateCatalogErr(t *testing.T) {
|
|||||||
testMockEC2WithDescribeInstancesError(fmt.Errorf("oops there was an error")),
|
testMockEC2WithDescribeInstancesError(fmt.Errorf("oops there was an error")),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "aws describe instances failed: oops there was an error",
|
// checkHosts returns the DescribeInstances error as-is rather than wrapping it
|
||||||
expectedErrCode: codes.FailedPrecondition,
|
// in a gRPC FailedPrecondition status like the old dryRunValidiation did.
|
||||||
|
// A plain Go error maps to codes.Unknown.
|
||||||
|
expectedErrContains: "oops there was an error",
|
||||||
|
expectedErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "final dry run gRPC status error preserves code",
|
||||||
|
req: &pb.OnUpdateCatalogRequest{
|
||||||
|
CurrentCatalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewCatalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
|
credential.ConstCredsLastRotatedTime: structpb.NewStringValue("2006-01-02T15:04:05+07:00"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
// ParseAWSError maps AccessDenied to PermissionDenied. The dry-run
|
||||||
|
// error handler should preserve that gRPC status, not wrap it as Unknown.
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1565,8 +1752,57 @@ func TestPluginOnCreateSetErr(t *testing.T) {
|
|||||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("aws describe instances failed: %s", testDescribeInstancesError),
|
expectedErrContains: testDescribeInstancesError,
|
||||||
expectedErrCode: codes.FailedPrecondition,
|
expectedErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dry run gRPC status error preserves code",
|
||||||
|
req: &pb.OnCreateSetRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Set: &hostsets.HostSet{
|
||||||
|
Id: "foobar",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstDescribeInstancesFilters: structpb.NewListValue(
|
||||||
|
&structpb.ListValue{
|
||||||
|
Values: []*structpb.Value{
|
||||||
|
structpb.NewStringValue("tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
// ParseAWSError maps AccessDenied to PermissionDenied. The dry-run
|
||||||
|
// error handler should preserve that gRPC status, not wrap it as Unknown.
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1828,8 +2064,56 @@ func TestPluginOnUpdateSetErr(t *testing.T) {
|
|||||||
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
testMockEC2WithDescribeInstancesError(errors.New(testDescribeInstancesError)),
|
||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("aws describe instances failed: %s", testDescribeInstancesError),
|
expectedErrContains: testDescribeInstancesError,
|
||||||
expectedErrCode: codes.FailedPrecondition,
|
expectedErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "dry run gRPC status error preserves code",
|
||||||
|
req: &pb.OnUpdateSetRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
NewSet: &hostsets.HostSet{
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
ConstDescribeInstancesFilters: structpb.NewListValue(
|
||||||
|
&structpb.ListValue{
|
||||||
|
Values: []*structpb.Value{
|
||||||
|
structpb.NewStringValue("tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
// ParseAWSError maps AccessDenied to PermissionDenied. The dry-run
|
||||||
|
// error handler should preserve that gRPC status, not wrap it as Unknown.
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2287,7 +2571,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: fmt.Sprintf("error retrieving host results for host set id \"foobar\": %s", testDescribeInstancesError),
|
expectedErrContains: fmt.Sprintf("error retrieving host results for host set id \"foobar\": %s", testDescribeInstancesError),
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "target attributes missing target_role_arn",
|
name: "target attributes missing target_role_arn",
|
||||||
@@ -2353,7 +2637,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: test EC2 client error",
|
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: test EC2 client error",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "target DescribeInstances error",
|
name: "target DescribeInstances error",
|
||||||
@@ -2381,7 +2665,7 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 DescribeInstances failed for target account: DescribeInstances error",
|
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 DescribeInstances failed for target account: DescribeInstances error",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "awsInstanceToHost error",
|
name: "awsInstanceToHost error",
|
||||||
@@ -2442,7 +2726,115 @@ func TestPluginListHostsErr(t *testing.T) {
|
|||||||
)),
|
)),
|
||||||
},
|
},
|
||||||
expectedErrContains: "error processing host results for host set id \"foobar\": response integrity error: missing instance id",
|
expectedErrContains: "error processing host results for host set id \"foobar\": response integrity error: missing instance id",
|
||||||
expectedErrCode: codes.InvalidArgument,
|
expectedErrCode: codes.Unknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DescribeInstances AccessDenied",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "DescribeInstances UnauthorizedOperation",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("UnauthorizedOperation", "You are not authorized to perform this operation.")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target DescribeInstances AccessDenied",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
credential.ConstTargetRoleArn: structpb.NewStringValue("arn:aws:iam::222222222222:role/Target"),
|
||||||
|
credential.ConstTargetRegion: structpb.NewStringValue("eu-west-1"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(newTestMockEC2(
|
||||||
|
nil,
|
||||||
|
testMockEC2WithDescribeInstancesError(awserrors.TestAwsError("AccessDenied", "not authorized to perform ec2:DescribeInstances")),
|
||||||
|
)),
|
||||||
|
},
|
||||||
|
expectedErrContains: "invalid credentials",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target EC2 client AccessDenied",
|
||||||
|
req: &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
credential.ConstTargetRoleArn: structpb.NewStringValue("arn:aws:iam::222222222222:role/Target"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Persisted: testListHostsPersisted(),
|
||||||
|
Sets: []*hostsets.HostSet{
|
||||||
|
testListHostsSet("foobar", "tag-key=foo"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogOpts: []awsCatalogPersistedStateOption{
|
||||||
|
withTestEC2APIFunc(func(...aws.Config) (EC2API, error) {
|
||||||
|
return nil, awserrors.TestAwsError("AccessDenied", "not authorized to assume role")
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
// ec2ClientForTarget returns testEC2APIFunc errors unparsed; checkHosts
|
||||||
|
// only preserves existing gRPC statuses, so this surfaces as Unknown.
|
||||||
|
expectedErrContains: "unable to list hosts for host set \"foobar\": EC2 client setup failed for target account: api error AccessDenied: not authorized to assume role",
|
||||||
|
expectedErrCode: codes.Unknown,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2633,6 +3025,21 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
|
|||||||
require.Equal(tc.expected, actual)
|
require.Equal(tc.expected, actual)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
t.Run("nil attrs, dry run", func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
actual, err := buildDescribeInstancesInput(nil, true)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Equal(&ec2.DescribeInstancesInput{
|
||||||
|
DryRun: aws.Bool(true),
|
||||||
|
Filters: []types.Filter{
|
||||||
|
{
|
||||||
|
Name: aws.String("instance-state-name"),
|
||||||
|
Values: []string{string(types.InstanceStateNameRunning)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, actual)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAwsInstanceToHost(t *testing.T) {
|
func TestAwsInstanceToHost(t *testing.T) {
|
||||||
@@ -3071,54 +3478,3 @@ func TestAppendDistinct(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDryRunValidation(t *testing.T) {
|
|
||||||
t.Run("nil credential state", func(t *testing.T) {
|
|
||||||
st := dryRunValidation(context.Background(), nil, nil)
|
|
||||||
require.NotNil(t, st)
|
|
||||||
require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
|
|
||||||
require.Equal(t, "persisted state is required", st.Message())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("ec2ClientErr", func(t *testing.T) {
|
|
||||||
st := dryRunValidation(context.Background(), &awsCatalogPersistedState{
|
|
||||||
AwsCredentialPersistedState: &credential.AwsCredentialPersistedState{
|
|
||||||
CredentialsConfig: &awsutil.CredentialsConfig{},
|
|
||||||
},
|
|
||||||
testEC2APIFunc: func(...aws.Config) (EC2API, error) {
|
|
||||||
return nil, fmt.Errorf("oops ec2 client err")
|
|
||||||
},
|
|
||||||
}, []ec2Option{})
|
|
||||||
require.NotNil(t, st)
|
|
||||||
require.Equal(t, codes.InvalidArgument.String(), st.Code().String())
|
|
||||||
require.Equal(t, "error getting EC2 client: oops ec2 client err", st.Message())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("describeInstancesErr", func(t *testing.T) {
|
|
||||||
st := dryRunValidation(context.Background(), &awsCatalogPersistedState{
|
|
||||||
AwsCredentialPersistedState: &credential.AwsCredentialPersistedState{
|
|
||||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
|
||||||
AccessKey: "AKIAfoo",
|
|
||||||
SecretKey: "baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesError(fmt.Errorf("oops describe instances error"))),
|
|
||||||
}, []ec2Option{})
|
|
||||||
require.NotNil(t, st)
|
|
||||||
require.Equal(t, codes.FailedPrecondition.String(), st.Code().String())
|
|
||||||
require.Equal(t, "aws describe instances failed: oops describe instances error", st.Message())
|
|
||||||
})
|
|
||||||
|
|
||||||
t.Run("success", func(t *testing.T) {
|
|
||||||
st := dryRunValidation(context.Background(), &awsCatalogPersistedState{
|
|
||||||
AwsCredentialPersistedState: &credential.AwsCredentialPersistedState{
|
|
||||||
CredentialsConfig: &awsutil.CredentialsConfig{
|
|
||||||
AccessKey: "AKIAfoo",
|
|
||||||
SecretKey: "baz",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
testEC2APIFunc: newTestMockEC2(nil, testMockEC2WithDescribeInstancesOutput(&ec2.DescribeInstancesOutput{})),
|
|
||||||
}, []ec2Option{})
|
|
||||||
require.Nil(t, st)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user