Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c8e95fcd8 | ||
|
|
01f8da94e4 | ||
|
|
241901319d | ||
|
|
56f89a5dcf |
No files matched your search
@@ -1,56 +1,56 @@
|
|||||||
module github.com/hashicorp/boundary-plugin-aws
|
module github.com/hashicorp/boundary-plugin-aws
|
||||||
|
|
||||||
go 1.24.0
|
go 1.25.7
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.5
|
github.com/aws/aws-sdk-go-v2 v1.41.7
|
||||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0
|
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0
|
||||||
github.com/aws/aws-sdk-go-v2/service/iam v1.52.2
|
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3
|
github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2
|
github.com/aws/aws-sdk-go-v2/service/sts v1.38.5
|
||||||
github.com/aws/smithy-go v1.24.2
|
github.com/aws/smithy-go v1.25.1
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.0
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/hashicorp/boundary/sdk v0.0.51
|
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.2
|
||||||
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
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.11.1
|
||||||
google.golang.org/grpc v1.79.3
|
google.golang.org/grpc v1.79.3
|
||||||
google.golang.org/protobuf v1.36.10
|
google.golang.org/protobuf v1.36.11
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 // indirect
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.2 // indirect
|
github.com/aws/aws-sdk-go-v2/config v1.28.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.2 // indirect
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14 // indirect
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22 // indirect
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 // indirect
|
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/signin v1.0.2 // indirect
|
github.com/aws/aws-sdk-go-v2/service/sso v1.24.6 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5 // indirect
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 // indirect
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10 // indirect
|
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
github.com/fatih/color v1.18.0 // indirect
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||||
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 // indirect
|
github.com/hashicorp/eventlogger v0.2.11 // indirect
|
||||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
github.com/hashicorp/go-hclog v1.6.3 // indirect
|
||||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 // indirect
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 // indirect
|
||||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||||
github.com/hashicorp/go-sockaddr v1.0.6 // indirect
|
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||||
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
github.com/hashicorp/go-uuid v1.0.3 // indirect
|
||||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||||
@@ -59,11 +59,11 @@ require (
|
|||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||||
github.com/zclconf/go-cty v1.15.0 // indirect
|
github.com/zclconf/go-cty v1.15.0 // indirect
|
||||||
golang.org/x/crypto v0.46.0 // indirect
|
golang.org/x/crypto v0.48.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/net v0.49.0 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/text v0.34.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
@@ -8,48 +8,68 @@ 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.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
|
github.com/aws/aws-sdk-go-v2 v1.39.1 h1:fWZhGAwVRK/fAN2tmt7ilH4PPAE11rDj7HytrmbZ2FE=
|
||||||
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
|
github.com/aws/aws-sdk-go-v2 v1.39.1/go.mod h1:sDioUELIUO9Znk23YVmIk86/9DOpkbyyVb1i/gUNFXY=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8 h1:eBMB84YGghSocM7PsjmmPffTa+1FBUeNvGvFou6V/4o=
|
github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8=
|
||||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.8/go.mod h1:lyw7GFp3qENLh7kwzf7iMzAxDn+NzjXEAGjKS2UOKqI=
|
github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.2 h1:4liUsdEpUUPZs5WVapsJLx5NPmQhQdez7nYFcovrytk=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.1 h1:i8p8P4diljCr60PpJp6qZXNlgX4m2yQFpYk+9ZT+J4E=
|
||||||
github.com/aws/aws-sdk-go-v2/config v1.32.2/go.mod h1:l0hs06IFz1eCT+jTacU/qZtC33nvcnLADAPL/XyrkZI=
|
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/credentials v1.19.2 h1:qZry8VUyTK4VIo5aEdUcBjPZHL2v4FyQ3QEOaWcFLu4=
|
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho=
|
||||||
github.com/aws/aws-sdk-go-v2/credentials v1.19.2/go.mod h1:YUqm5a1/kBnoK+/NY5WEiMocZihKSo15/tJdmdXnM5g=
|
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/feature/ec2/imds v1.18.14 h1:WZVR5DbDgxzA0BJeudId89Kmgy6DIU4ORpxwsVHz0qA=
|
github.com/aws/aws-sdk-go-v2/config v1.28.5 h1:Za41twdCXbuyyWv9LndXxZZv3QhTG1DinqlFsSuvtI0=
|
||||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.14/go.mod h1:Dadl9QO0kHgbrH1GRqGiZdYtW5w+IXXaBNCHTIaheM4=
|
github.com/aws/aws-sdk-go-v2/config v1.28.5/go.mod h1:4VsPbHP8JdcdUDmbTVgNL/8w9SqOkM5jyY8ljIxLO3o=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.46 h1:AU7RcriIo2lXjUfHFnFKYsLCwgbz1E7Mm95ieIRDNUg=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
|
github.com/aws/aws-sdk-go-v2/credentials v1.17.46/go.mod h1:1FmYyLGL08KQXQ6mcTlifyFXfJVCNJTVGuQP4m0d/UA=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
|
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.20 h1:sDSXIrlsFSFJtWKLQS4PUWRvrT580rrnuLydJrCQ/yA=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
|
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/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.8 h1:6bgAZgRyT4RoFWhxS+aoGMFyE0cD1bSzFnEEi4bFPGI=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc=
|
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/v4a v1.4.22 h1:rWyie/PxDRIdhNf4DzRk0lvjVOqFJuNnO8WwaIRVxzQ=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0=
|
||||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.22/go.mod h1:zd/JsJ4P7oGfUhXn1VyLqaRZwPmZwg44Jf2dS84Dm3Y=
|
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0 h1:RhSoBFT5/8tTmIseJUXM6INTXTQDF8+0oyxWBnozIms=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.8 h1:HhJYoES3zOz34yWEpGENqJvRVPqpmJyR3+AFg9ybhdY=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ec2 v1.193.0/go.mod h1:mzj8EEjIHSN2oZRXiw1Dd+uB4HZTl7hC8nBzX9IZMWw=
|
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/service/iam v1.52.2 h1:li0ooCUfHIivHn8nB3LstP6HgdNefwu5gnXE4MLVz/U=
|
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo=
|
||||||
github.com/aws/aws-sdk-go-v2/service/iam v1.52.2/go.mod h1:PuHz5kGh1jtsNpjezdYhRp7xgn6DzCNJJfQt7O7U9Aw=
|
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/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
|
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.1 h1:VaRN3TlFdd6KxX1x3ILT5ynH6HvKgqdiXoTxAF4HQcQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
|
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/service/internal/checksum v1.9.13 h1:JRaIgADQS/U6uXDqlPiefP32yXTda7Kqfx+LgspooZM=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.8 h1:1/bT9kDdLQzfZ1e6J6hpW+SfNDd6xrV8F3M2CuGyUz8=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.13/go.mod h1:CEuVn5WqOMilYl+tbccq8+N2ieCy0gVn3OtRb0vBNNM=
|
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/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
|
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21 h1:ZlvrNcHSFFWURB8avufQq9gFsheUgjVD9536obIknfM=
|
github.com/aws/aws-sdk-go-v2/service/ec2 v1.254.0 h1:fTLR6dLDTGChAjecRPlVrKeznT0rVdzR4yn9Z68MTGk=
|
||||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.21/go.mod h1:cv3TNhVrssKR0O/xxLJVRfd2oazSnZnkUeTf6ctUwfQ=
|
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/s3 v1.97.3 h1:HwxWTbTrIHm5qY+CAEur0s/figc3qwvLWsNkF4RPToo=
|
github.com/aws/aws-sdk-go-v2/service/iam v1.47.6 h1:EWehQXACWr+6hzfZPwZChlfoVhiUCfLHE0Xh3kAfzWQ=
|
||||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.97.3/go.mod h1:uoA43SdFwacedBfSgfFSjjCvYe8aYBS7EnU5GZ/YKMM=
|
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/signin v1.0.2 h1:MxMBdKTYBjPQChlJhi4qlEueqB1p1KcbTEa7tD5aqPs=
|
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/signin v1.0.2/go.mod h1:iS6EPmNeqCsGo+xQmXv0jIMjyYtQfnwg36zl2FwEouk=
|
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/sso v1.30.5 h1:ksUT5KtgpZd3SAiFJNJ0AFEJVva3gjBmN7eXUZjzUwQ=
|
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sso v1.30.5/go.mod h1:av+ArJpoYf3pgyrj6tcehSFW+y9/QvAY8kMooR9bZCw=
|
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/ssooidc v1.35.10 h1:GtsxyiF3Nd3JahRBJbxLCCdYW9ltGQYrFWg8XdkGDd8=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.8.8 h1:tIN8MFT1z5STK5kTdOT1TCfMN/bn5fSEnlKsTL8qBOU=
|
||||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.10/go.mod h1:/j67Z5XBVDx8nZVp9EuFM9/BS5dvBznbqILGuu73hug=
|
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/sts v1.41.2 h1:a5UTtD4mHBU3t0o6aHQZFJTNKVfxFWfPX7J0Lr7G+uY=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4=
|
||||||
github.com/aws/aws-sdk-go-v2/service/sts v1.41.2/go.mod h1:6TxbXoDSgBQ225Qd8Q+MbxUxUh6TtNKwbRt/EPS9xso=
|
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM=
|
||||||
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
|
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.8 h1:M6JI2aGFEzYxsF6CXIuRBnkge9Wf9a2xU39rNeXgu10=
|
||||||
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
|
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/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/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/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/go.mod h1:WJSZH2ZvepM6t6jwu4w/Z45Eoi75lPN7DcydSRtJg6Y=
|
||||||
|
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.28.5 h1:K0OQAsDywb0ltlFrZm0JHPY3yZp/S9OaoLU33S7vPS8=
|
||||||
|
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/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/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=
|
||||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
@@ -65,60 +85,57 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho=
|
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
|
||||||
github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
|
||||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
|
||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/go-cmp v0.5.4/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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/hashicorp/boundary/sdk v0.0.51 h1:a7XpHNTYaeBXRhJkc8MC0siPtfJELYH/ibjw/0UbJEg=
|
github.com/hashicorp/boundary/sdk v0.0.59 h1:XNniO0rwgihLdF1+4qDHKN3gdta3AU1JteZResCJ8Oc=
|
||||||
github.com/hashicorp/boundary/sdk v0.0.51/go.mod h1:gSrMTppD30IPVsUqs4SwQowu+OlR5/+58draey++SkQ=
|
github.com/hashicorp/boundary/sdk v0.0.59/go.mod h1:VYwVGAEaHTzihRXpzKZL6EhWBhll59Iul3PGLVL63ao=
|
||||||
|
github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU=
|
||||||
|
github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
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 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
|
||||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 h1:f9oX8/3zxiQrfrWnBeyjDm4S02GAU02OBtCRoZOUwlo=
|
github.com/hashicorp/eventlogger v0.2.11 h1:lkK8ARM/DrCeL5deH2yVS6p40337dESi+77p9pIfLqo=
|
||||||
github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM=
|
github.com/hashicorp/eventlogger v0.2.11/go.mod h1:Rmc3MEopz7jUnLokZVTWAsXaEP5rqd20ObGS+pcau3U=
|
||||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc=
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0 h1:iAb287bq0TaWTnhDYuN/zVqdD2EwanQg9ncVelC60Xc=
|
||||||
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
|
github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20231025104552-802587e608f0/go.mod h1:tMywUTIvdB/FXhwm6HMTt61C8/eODY6gitCHhXtyojg=
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
|
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-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
|
||||||
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
|
||||||
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
|
||||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.7 h1:gM4OwbF16Cmfxt2QMkoGMQbRTfYFZLvDMPgU3rM3KIo=
|
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8 h1:/GIUjn9GkFXMk/8/irRdbdtmx8CcyeyWdVy/E5LvzyA=
|
||||||
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.7/go.mod h1:7ZMHVluyqgHgEuTADeDzFNWoA9mnyPfdiK8Tk2Bct1c=
|
github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.8/go.mod h1:JDc9UOD4EVRDIwPVethJcT5Ibi/Nas6eQDPtA60iwP0=
|
||||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16 h1:WZeXfD26QMWYC35at25KgE021SF9L3u9UMHK8fJAdV0=
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.20 h1:afreZ1WJd0WI7v4NsMZ1aL7V/T59sxPuKmQDgGUja20=
|
||||||
github.com/hashicorp/go-kms-wrapping/v2 v2.0.16/go.mod h1:ZiKZctjRTLEppuRwrttWkp71VYMbTTCkazK4xT7U/NQ=
|
github.com/hashicorp/go-kms-wrapping/v2 v2.0.20/go.mod h1:NeK2Ul15t1zutp/dZzt28XQrGZHosbxE/QLNNfaWObM=
|
||||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
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-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0UUrwg=
|
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
|
||||||
github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0=
|
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.2 h1:KlDrZWx/wYpnUcFPB0s9uhPjyNZoeIfhrV2MN/Ly76s=
|
||||||
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.2/go.mod h1:6+rVulOPNCQbL3Xv2iLCqM0JmU2WO2wRzP1C6hBKeB8=
|
||||||
github.com/hashicorp/go-secure-stdlib/base62 v0.1.1/go.mod h1:EdWO6czbmthiwZ3/PUsDV+UD1D5IRU4ActiaWGwt0Yw=
|
|
||||||
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.11 h1:uPW2Wn0YlmI9RGSkZpcIplnVRwJ7BCiGpk1vnF2TMw4=
|
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13 h1:TayxZ5drfMP0G6T++WvnLESGLOWeHtdWDWWTAi2e3Qk=
|
||||||
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8=
|
github.com/hashicorp/go-secure-stdlib/configutil/v2 v2.0.13/go.mod h1:NDRQ/F3DXTylqjORAP0cA+puH/JFrLlT+NlqxHK2/e8=
|
||||||
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY=
|
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10 h1:2iDz+t0JLl1W0tJhvmhsh/UBgT1JgC8Qxz8HxYMWXQo=
|
||||||
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M=
|
github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.10/go.mod h1:eZkXE+osawMrAWR4wJRmyKauUwH6mNGbjFuiDujnbPk=
|
||||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRctKAPTloZdMU5ZJxaesJ/VM=
|
||||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0=
|
||||||
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY=
|
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8 h1:pgSicufBI+MvaIG9Keykb3k9B3sWtDNjmsoyrl2/8Qw=
|
||||||
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg=
|
github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.8/go.mod h1:sBcjk+paCXCMR9HHLcrYSfPz2FsskD8MQuotKiyM2aA=
|
||||||
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
|
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1 h1:SMGUnbpAcat8rIKHkBPjfv81yC46a8eCNZ2hsR2l1EI=
|
||||||
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
|
github.com/hashicorp/go-secure-stdlib/reloadutil v0.1.1/go.mod h1:Ch/bf00Qnx77MZd49JRgHYqHQjtEmTgGU2faufpVZb0=
|
||||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.1/go.mod h1:gKOamz3EwoIoJq7mlMIRBpVTAUn8qPCrEclOKKWhD3U=
|
|
||||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts=
|
||||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4=
|
||||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
|
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3 h1:xbrxd0U9XQW8qL1BAz2XrAjAF/P2vcqUTAues9c24B8=
|
||||||
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
|
github.com/hashicorp/go-secure-stdlib/tlsutil v0.1.3/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.6 h1:RSG8rKU28VTUTvEKghe5gIhIQpv8evvNpnDEyqO4u9I=
|
github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw=
|
||||||
github.com/hashicorp/go-sockaddr v1.0.6/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
|
github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw=
|
||||||
github.com/hashicorp/go-uuid v1.0.2/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 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
|
||||||
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
|
||||||
@@ -127,20 +144,16 @@ github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
|||||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
|
github.com/hashicorp/terraform-json v0.23.0 h1:sniCkExU4iKtTADReHzACkk8fnpQXrdD2xoR+lppBkI=
|
||||||
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
|
github.com/hashicorp/terraform-json v0.23.0/go.mod h1:MHdXbBAbSg0GvzuWazEGKAn/cyNfIB7mN6y7KJN6y2c=
|
||||||
github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE=
|
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
|
||||||
github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ=
|
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
|
||||||
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU=
|
||||||
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE=
|
||||||
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk=
|
||||||
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg=
|
||||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
|
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f h1:E87tDTVS5W65euzixn7clSzK66puSt1H4I5SC0EmHH4=
|
||||||
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
|
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00=
|
||||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
|
||||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
|
||||||
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
|
||||||
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
@@ -151,8 +164,6 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky
|
|||||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mitchellh/cli v1.1.5 h1:OxRIeJXpAMztws/XHlN2vu6imG5Dpq+j61AzAX5fLng=
|
|
||||||
github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2cLtRR4=
|
|
||||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||||
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
@@ -168,7 +179,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
|||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo=
|
||||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||||
github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk=
|
||||||
@@ -178,12 +188,9 @@ github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFR
|
|||||||
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
|
||||||
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
|
||||||
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
|
||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
|
||||||
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
|
github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ=
|
||||||
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
@@ -198,69 +205,34 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
|||||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||||
go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
|
golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts=
|
||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos=
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
|
||||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
|
||||||
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
|
||||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
|
||||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
|
||||||
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
|
||||||
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
|
|
||||||
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
|
|
||||||
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
|
|
||||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
|
||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
|
||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
|
||||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/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-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
|
||||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
|
||||||
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
|
||||||
golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
|
||||||
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
|
||||||
golang.org/x/tools v0.0.0-20210101214203-2dba1e4ea05c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57 h1:JLQynH/LBHfCTSbDWl+py8C+Rg/k1OVH3xfcaiANuF0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
google.golang.org/genproto/googleapis/api v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:kSJwQxqmFXeo79zOmbrALdflXQeAYcUbgS7PbpMknCY=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217 h1:gRkg/vSppuSQoDjxyiGfN4Upv/h/DQmIR10ZU8dh4Ww=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57 h1:mWPCjDEyshlQYzBpMNHaEof6UX1PmHcaUODUywQ0uac=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20260209200024-4cfbd4190f57/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||||
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE=
|
||||||
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ=
|
||||||
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
|
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||||
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
||||||
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
mvdan.cc/gofumpt v0.1.1/go.mod h1:yXG1r1WqZVKWbVRtBWKWX9+CxGYfA51nSomhM0woR48=
|
|
||||||
@@ -130,7 +130,7 @@ func GetCredentialsConfig(secrets *structpb.Struct, attrs *CredentialAttributes,
|
|||||||
|
|
||||||
cfg, err := awsutil.NewCredentialsConfig(opts...)
|
cfg, err := awsutil.NewCredentialsConfig(opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("%s", err.Error())
|
return nil, errors.BadRequestStatus(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
|
|||||||
+73
-10
@@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
||||||
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
"github.com/aws/smithy-go"
|
"github.com/aws/smithy-go"
|
||||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
"google.golang.org/protobuf/types/known/timestamppb"
|
"google.golang.org/protobuf/types/known/timestamppb"
|
||||||
@@ -52,6 +53,10 @@ const (
|
|||||||
// whose key does not exist
|
// whose key does not exist
|
||||||
awsErrorNoSuchKey = "NoSuchKey"
|
awsErrorNoSuchKey = "NoSuchKey"
|
||||||
|
|
||||||
|
// NotFound is returned when attempting to interact with an S3 object
|
||||||
|
// whose key does not exist
|
||||||
|
awsErrorNotFound = "NotFound"
|
||||||
|
|
||||||
// The InvalidObjectState error is returned when trying to
|
// The InvalidObjectState error is returned when trying to
|
||||||
// access an object that was moved into cold storage.
|
// access an object that was moved into cold storage.
|
||||||
awsErrorInvalidObjectState = "InvalidObjectState"
|
awsErrorInvalidObjectState = "InvalidObjectState"
|
||||||
@@ -73,7 +78,7 @@ func InvalidArgumentError(msg string, f map[string]string) error {
|
|||||||
sort.Strings(fieldMsgs)
|
sort.Strings(fieldMsgs)
|
||||||
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
|
msg = fmt.Sprintf("%s: [%s]", msg, strings.Join(fieldMsgs, ", "))
|
||||||
}
|
}
|
||||||
return BadRequestStatus("%s", msg)
|
return BadRequestStatus(msg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseAWSError converts an aws service error into a RPC status. This method
|
// ParseAWSError converts an aws service error into a RPC status. This method
|
||||||
@@ -118,7 +123,50 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
|
|||||||
return status.New(codes.Unavailable, statusMsg), nil
|
return status.New(codes.Unavailable, statusMsg), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse some specific aws error codes that we have special cred states for
|
// match error type against NotFound
|
||||||
|
var notFoundErr *types.NotFound
|
||||||
|
if errors.As(err, ¬FoundErr) {
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||||
|
return status.New(codes.NotFound, statusMsg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match error type against NoSuchkey
|
||||||
|
var noSuchKeyErr *types.NoSuchKey
|
||||||
|
if errors.As(err, &noSuchKeyErr) {
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||||
|
return status.New(codes.NotFound, statusMsg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match error type against InvalidObjectState
|
||||||
|
var invalidObjectStateErr *types.InvalidObjectState
|
||||||
|
if errors.As(err, &invalidObjectStateErr) {
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||||
|
return status.New(codes.NotFound, statusMsg), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// match error type against NoSuchBucket
|
||||||
|
var noSuchBucketErr *types.NoSuchBucket
|
||||||
|
if errors.As(err, &noSuchBucketErr) {
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||||
|
return status.New(codes.NotFound, statusMsg), &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: noSuchBucketErr.ErrorMessage(),
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// match error type against AccessDenied
|
||||||
|
var accessDeniedErr *types.AccessDenied
|
||||||
|
if errors.As(err, &accessDeniedErr) {
|
||||||
|
statusMsg := fmt.Sprintf("aws service %s: invalid credentials: %s", serviceName, msg)
|
||||||
|
return status.New(codes.PermissionDenied, statusMsg), &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: accessDeniedErr.ErrorMessage(),
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fallback to evaluating the smithy APIError code
|
||||||
var apiErr smithy.APIError
|
var apiErr smithy.APIError
|
||||||
if errors.As(err, &apiErr) {
|
if errors.As(err, &apiErr) {
|
||||||
switch apiErr.ErrorCode() {
|
switch apiErr.ErrorCode() {
|
||||||
@@ -145,6 +193,8 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
|
|||||||
return status.New(codes.Aborted, statusMsg), nil
|
return status.New(codes.Aborted, statusMsg), nil
|
||||||
case awsErrorNoSuchKey:
|
case awsErrorNoSuchKey:
|
||||||
fallthrough
|
fallthrough
|
||||||
|
case awsErrorNotFound:
|
||||||
|
fallthrough
|
||||||
case awsErrorInvalidObjectState:
|
case awsErrorInvalidObjectState:
|
||||||
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
statusMsg := fmt.Sprintf("aws service %s: %s", serviceName, msg)
|
||||||
return status.New(codes.NotFound, statusMsg), nil
|
return status.New(codes.NotFound, statusMsg), nil
|
||||||
@@ -156,8 +206,9 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// default to evaluating the http status code
|
// fallback to evaluating the http status code
|
||||||
if httpErr, ok := err.(*awshttp.ResponseError); ok {
|
var httpErr *awshttp.ResponseError
|
||||||
|
if errors.As(err, &httpErr) {
|
||||||
buf := new(bytes.Buffer)
|
buf := new(bytes.Buffer)
|
||||||
_, err = buf.ReadFrom(httpErr.Response.Body)
|
_, err = buf.ReadFrom(httpErr.Response.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -206,14 +257,26 @@ func ParseAWSError(op string, err error) (st *status.Status, permission *pb.Perm
|
|||||||
return st, nil
|
return st, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// BadRequestStatus returns a status error with an invalid
|
// BadRequestStatusf returns a status error with an invalid
|
||||||
// argument code
|
// argument code and a formatted error message.
|
||||||
func BadRequestStatus(format string, args ...any) error {
|
func BadRequestStatusf(format string, args ...any) error {
|
||||||
return status.New(codes.InvalidArgument, fmt.Sprintf(format, args...)).Err()
|
return status.New(codes.InvalidArgument, fmt.Sprintf(format, args...)).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnknownStatus returns a status error with an internal
|
// BadRequestStatus returns a status error with an invalid
|
||||||
// error code
|
// argument code and the provided message.
|
||||||
func UnknownStatus(format string, args ...any) error {
|
func BadRequestStatus(msg string) error {
|
||||||
|
return status.New(codes.InvalidArgument, msg).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnknownStatusf returns a status error with an internal
|
||||||
|
// error code and a formatted error message.
|
||||||
|
func UnknownStatusf(format string, args ...any) error {
|
||||||
return status.New(codes.Internal, fmt.Sprintf(format, args...)).Err()
|
return status.New(codes.Internal, fmt.Sprintf(format, args...)).Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UnknownStatus returns a status error with an internal
|
||||||
|
// error code and the provided message.
|
||||||
|
func UnknownStatus(msg string) error {
|
||||||
|
return status.New(codes.Internal, msg).Err()
|
||||||
|
}
|
||||||
@@ -134,6 +134,17 @@ func Test_ParseAWSError(t *testing.T) {
|
|||||||
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "wrapped-http-status-code-forbidden",
|
||||||
|
err: fmt.Errorf("wrap: %w", TestAwsHttpResponseError(http.StatusForbidden, "forbidden request")),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "forbidden request",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "aws-throttling",
|
name: "aws-throttling",
|
||||||
err: TestAwsError("Throttling", "throttling error"),
|
err: TestAwsError("Throttling", "throttling error"),
|
||||||
@@ -311,6 +322,55 @@ func Test_ParseAWSError(t *testing.T) {
|
|||||||
CheckedAt: timestamppb.Now(),
|
CheckedAt: timestamppb.Now(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "aws-s3-typed-not-found",
|
||||||
|
err: TestAwsS3NotFoundError("object was not found"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws service unknown: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-s3-typed-no-such-key",
|
||||||
|
err: TestAwsS3NoSuchKeyError("key does not exist"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws service unknown: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-s3-typed-invalid-object-state",
|
||||||
|
err: TestAwsS3InvalidObjectStateError("object is archived"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws service unknown: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-s3-typed-no-such-bucket",
|
||||||
|
err: TestAwsS3NoSuchBucketError("bucket does not exist"),
|
||||||
|
expectedStatusCode: codes.NotFound,
|
||||||
|
expectedStatusMsg: "aws service unknown: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "bucket does not exist",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "aws-s3-typed-access-denied",
|
||||||
|
err: TestAwsS3AccessDeniedError("access denied"),
|
||||||
|
expectedStatusCode: codes.PermissionDenied,
|
||||||
|
expectedStatusMsg: "aws service unknown: invalid credentials: test",
|
||||||
|
expectedPermission: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "access denied",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range cases {
|
for _, tt := range cases {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
@@ -330,18 +390,36 @@ func Test_ParseAWSError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_BadRequestStatus(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 := BadRequestStatus("test: %s", "hello world")
|
err := BadRequestStatusf("test: %s", "hello world")
|
||||||
assert.ErrorContains(err, "test: hello world")
|
assert.ErrorContains(err, "test: hello world")
|
||||||
assert.Equal(status.Code(err), codes.InvalidArgument)
|
assert.Equal(status.Code(err), codes.InvalidArgument)
|
||||||
_, ok := status.FromError(err)
|
_, ok := status.FromError(err)
|
||||||
require.True(ok)
|
require.True(ok)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_UnknownStatus(t *testing.T) {
|
func Test_BadRequestStatus(t *testing.T) {
|
||||||
require, assert := require.New(t), assert.New(t)
|
require, assert := require.New(t), assert.New(t)
|
||||||
err := UnknownStatus("test: %s", "hello world")
|
err := BadRequestStatus("test: hello world")
|
||||||
|
assert.ErrorContains(err, "test: hello world")
|
||||||
|
assert.Equal(status.Code(err), codes.InvalidArgument)
|
||||||
|
_, ok := status.FromError(err)
|
||||||
|
require.True(ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UnknownStatusf(t *testing.T) {
|
||||||
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
err := UnknownStatusf("test: %s", "hello world")
|
||||||
|
assert.ErrorContains(err, "test: hello world")
|
||||||
|
assert.Equal(status.Code(err), codes.Internal)
|
||||||
|
_, ok := status.FromError(err)
|
||||||
|
require.True(ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_UnknownStatus(t *testing.T) {
|
||||||
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
err := UnknownStatusf("test: hello world")
|
||||||
assert.ErrorContains(err, "test: hello world")
|
assert.ErrorContains(err, "test: hello world")
|
||||||
assert.Equal(status.Code(err), codes.Internal)
|
assert.Equal(status.Code(err), codes.Internal)
|
||||||
_, ok := status.FromError(err)
|
_, ok := status.FromError(err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/service/s3/types"
|
||||||
"github.com/aws/smithy-go"
|
"github.com/aws/smithy-go"
|
||||||
smithyhttp "github.com/aws/smithy-go/transport/http"
|
smithyhttp "github.com/aws/smithy-go/transport/http"
|
||||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||||
@@ -39,6 +40,31 @@ func TestAwsError(code, msg string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestAwsS3NotFoundError returns an S3 NotFound error.
|
||||||
|
func TestAwsS3NotFoundError(msg string) error {
|
||||||
|
return &types.NotFound{Message: &msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAwsS3NoSuchKeyError returns an S3 NoSuchKey error.
|
||||||
|
func TestAwsS3NoSuchKeyError(msg string) error {
|
||||||
|
return &types.NoSuchKey{Message: &msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAwsS3InvalidObjectStateError returns an S3 InvalidObjectState error.
|
||||||
|
func TestAwsS3InvalidObjectStateError(msg string) error {
|
||||||
|
return &types.InvalidObjectState{Message: &msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAwsS3NoSuchBucketError returns an S3 NoSuchBucket error.
|
||||||
|
func TestAwsS3NoSuchBucketError(msg string) error {
|
||||||
|
return &types.NoSuchBucket{Message: &msg}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestAwsS3AccessDeniedError returns an S3 AccessDenied error.
|
||||||
|
func TestAwsS3AccessDeniedError(msg string) error {
|
||||||
|
return &types.AccessDenied{Message: &msg}
|
||||||
|
}
|
||||||
|
|
||||||
func CheckSimilarPermission(assert *assert.Assertions, expected, actual *pb.Permission, substituteNilWithDefaultOk bool) {
|
func CheckSimilarPermission(assert *assert.Assertions, expected, actual *pb.Permission, substituteNilWithDefaultOk bool) {
|
||||||
if expected == nil && substituteNilWithDefaultOk {
|
if expected == nil && substituteNilWithDefaultOk {
|
||||||
expected = &pb.Permission{
|
expected = &pb.Permission{
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ type CatalogAttributes struct {
|
|||||||
|
|
||||||
// DualStack is used for configuring how the aws client will resolve requests.
|
// DualStack is used for configuring how the aws client will resolve requests.
|
||||||
DualStack bool
|
DualStack bool
|
||||||
|
|
||||||
|
// InstanceAddressesOnly specifies whether only the instance addresses (private IPv4/DNS,
|
||||||
|
// public IPv4/DNS, and IPv6) are synced, or all addresses, including those from
|
||||||
|
// secondary ENIs, are synced.
|
||||||
|
InstanceAddressesOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
||||||
@@ -40,6 +45,12 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
|||||||
}
|
}
|
||||||
delete(unknownFields, ConstAwsDualStack)
|
delete(unknownFields, ConstAwsDualStack)
|
||||||
|
|
||||||
|
instanceAddressesOnly, err := values.GetBoolValue(in, ConstInstanceAddressesOnly, false)
|
||||||
|
if err != nil {
|
||||||
|
badFields[fmt.Sprintf("attributes.%s", ConstInstanceAddressesOnly)] = err.Error()
|
||||||
|
}
|
||||||
|
delete(unknownFields, ConstInstanceAddressesOnly)
|
||||||
|
|
||||||
for s := range unknownFields {
|
for s := range unknownFields {
|
||||||
switch s {
|
switch s {
|
||||||
// Ignore knownFields from CredentialAttributes
|
// Ignore knownFields from CredentialAttributes
|
||||||
@@ -65,8 +76,9 @@ func getCatalogAttributes(in *structpb.Struct) (*CatalogAttributes, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &CatalogAttributes{
|
return &CatalogAttributes{
|
||||||
CredentialAttributes: credAttributes,
|
CredentialAttributes: credAttributes,
|
||||||
DualStack: dualStack,
|
DualStack: dualStack,
|
||||||
|
InstanceAddressesOnly: instanceAddressesOnly,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,22 @@ func TestGetCatalogAttributes(t *testing.T) {
|
|||||||
DualStack: true,
|
DualStack: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "withInstanceAddressesOnly",
|
||||||
|
in: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
"region": structpb.NewStringValue("us-west-2"),
|
||||||
|
"instance_addresses_only": structpb.NewBoolValue(true),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &CatalogAttributes{
|
||||||
|
CredentialAttributes: &credential.CredentialAttributes{
|
||||||
|
Region: "us-west-2",
|
||||||
|
DisableCredentialRotation: false,
|
||||||
|
},
|
||||||
|
InstanceAddressesOnly: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "default",
|
name: "default",
|
||||||
in: &structpb.Struct{
|
in: &structpb.Struct{
|
||||||
|
|||||||
@@ -13,4 +13,9 @@ const (
|
|||||||
|
|
||||||
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
|
// ConstAwsDualStack is the key for the dualstack flag in the aws s3 client.
|
||||||
ConstAwsDualStack = "dual_stack"
|
ConstAwsDualStack = "dual_stack"
|
||||||
|
|
||||||
|
// ConstInstanceAddressesOnly is the key (instance_addresses_only) that specifies whether
|
||||||
|
// only the instance addresses (private IPv4/DNS, public IPv4/DNS, and IPv6) are synced,
|
||||||
|
// or all addresses, including those from secondary ENIs, are synced.
|
||||||
|
ConstInstanceAddressesOnly = "instance_addresses_only"
|
||||||
)
|
)
|
||||||
@@ -63,7 +63,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to rotate AWS static credentials
|
// Try to rotate AWS static credentials
|
||||||
@@ -79,7 +79,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
|||||||
}, p.testCatalogStateOpts...)...,
|
}, p.testCatalogStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("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.
|
// perform dry run to ensure we can interact with AWS as expected.
|
||||||
@@ -93,7 +93,7 @@ func (p *HostPlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatalo
|
|||||||
|
|
||||||
persistedProto, err := catalogState.toProto()
|
persistedProto, err := catalogState.toProto()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("%s", err.Error())
|
return nil, errors.BadRequestStatusf("error converting persisted state to proto: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.OnCreateCatalogResponse{
|
return &pb.OnCreateCatalogResponse{
|
||||||
@@ -138,7 +138,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
oldCatalogAttributes.DualStack,
|
oldCatalogAttributes.DualStack,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the incoming credentials are valid and return any errors to the
|
// Verify the incoming credentials are valid and return any errors to the
|
||||||
@@ -161,7 +161,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up new credential persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
|
||||||
}
|
}
|
||||||
newCatalogState, err := newAwsCatalogPersistedState(
|
newCatalogState, err := newAwsCatalogPersistedState(
|
||||||
append([]awsCatalogPersistedStateOption{
|
append([]awsCatalogPersistedStateOption{
|
||||||
@@ -169,7 +169,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
}, p.testCatalogStateOpts...)...,
|
}, p.testCatalogStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("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 {
|
||||||
@@ -213,7 +213,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
}, p.testCatalogStateOpts...)...,
|
}, p.testCatalogStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("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.
|
// perform dry run to ensure we can interact with AWS as expected.
|
||||||
@@ -227,7 +227,7 @@ func (p *HostPlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatalo
|
|||||||
|
|
||||||
persistedProto, err := catalogState.toProto()
|
persistedProto, err := catalogState.toProto()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("%s", err.Error())
|
return nil, errors.BadRequestStatusf("error converting persisted state to proto: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.OnUpdateCatalogResponse{
|
return &pb.OnUpdateCatalogResponse{
|
||||||
@@ -264,7 +264,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
|
|||||||
catalogAttributes.DualStack,
|
catalogAttributes.DualStack,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = newAwsCatalogPersistedState(
|
_, err = newAwsCatalogPersistedState(
|
||||||
@@ -273,7 +273,7 @@ func (p *HostPlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatalo
|
|||||||
}, p.testCatalogStateOpts...)...,
|
}, p.testCatalogStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to delete static credentials
|
// try to delete static credentials
|
||||||
@@ -341,7 +341,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
|
|||||||
catalogAttributes.DualStack,
|
catalogAttributes.DualStack,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogState, err := newAwsCatalogPersistedState(
|
catalogState, err := newAwsCatalogPersistedState(
|
||||||
@@ -350,7 +350,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
|
|||||||
}, p.testCatalogStateOpts...)...,
|
}, p.testCatalogStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
set := req.GetSet()
|
set := req.GetSet()
|
||||||
@@ -368,7 +368,7 @@ func (p *HostPlugin) OnCreateSet(ctx context.Context, req *pb.OnCreateSetRequest
|
|||||||
|
|
||||||
describeInstanceFilters, err := buildFilters(setAttrs)
|
describeInstanceFilters, err := buildFilters(setAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error building set filters: %s", err)
|
return nil, errors.BadRequestStatusf("error building set filters: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []ec2Option{}
|
opts := []ec2Option{}
|
||||||
@@ -404,7 +404,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
|
|||||||
catalogAttributes.DualStack,
|
catalogAttributes.DualStack,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogState, err := newAwsCatalogPersistedState(
|
catalogState, err := newAwsCatalogPersistedState(
|
||||||
@@ -413,7 +413,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
|
|||||||
}, p.testCatalogStateOpts...)...,
|
}, p.testCatalogStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// As with catalog, we don't need to really look at the old host
|
// As with catalog, we don't need to really look at the old host
|
||||||
@@ -433,7 +433,7 @@ func (p *HostPlugin) OnUpdateSet(ctx context.Context, req *pb.OnUpdateSetRequest
|
|||||||
|
|
||||||
describeInstanceFilters, err := buildFilters(setAttrs)
|
describeInstanceFilters, err := buildFilters(setAttrs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error building set filters: %s", err)
|
return nil, errors.BadRequestStatusf("error building set filters: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []ec2Option{}
|
opts := []ec2Option{}
|
||||||
@@ -477,7 +477,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
catalogAttributes.DualStack,
|
catalogAttributes.DualStack,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catalogState, err := newAwsCatalogPersistedState(
|
catalogState, err := newAwsCatalogPersistedState(
|
||||||
@@ -486,7 +486,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
}, p.testCatalogStateOpts...)...,
|
}, p.testCatalogStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sets := req.GetSets()
|
sets := req.GetSets()
|
||||||
@@ -510,7 +510,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
}
|
}
|
||||||
|
|
||||||
if set.GetAttributes() == nil {
|
if set.GetAttributes() == nil {
|
||||||
return nil, errors.BadRequestStatus("set %s attributes are required", set.GetId())
|
return nil, errors.BadRequestStatusf("set %s attributes are required", set.GetId())
|
||||||
}
|
}
|
||||||
setAttrs, err := getSetAttributes(set.GetAttributes())
|
setAttrs, err := getSetAttributes(set.GetAttributes())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -519,7 +519,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
|
|
||||||
input, err := buildDescribeInstancesInput(setAttrs, false)
|
input, err := buildDescribeInstancesInput(setAttrs, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error building DescribeInstances parameters for host set id %q: %s", set.GetId(), err)
|
return nil, errors.BadRequestStatusf("error building DescribeInstances parameters for host set id %q: %s", set.GetId(), err)
|
||||||
}
|
}
|
||||||
queries[i] = hostSetQuery{
|
queries[i] = hostSetQuery{
|
||||||
Id: set.GetId(),
|
Id: set.GetId(),
|
||||||
@@ -533,7 +533,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
}
|
}
|
||||||
ec2Client, err := catalogState.EC2Client(ctx, opts...)
|
ec2Client, err := catalogState.EC2Client(ctx, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error getting EC2 client: %s", err)
|
return nil, errors.BadRequestStatusf("error getting EC2 client: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run all queries now and assemble output.
|
// Run all queries now and assemble output.
|
||||||
@@ -541,7 +541,7 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
for i, query := range queries {
|
for i, query := range queries {
|
||||||
output, err := ec2Client.DescribeInstances(ctx, query.Input)
|
output, err := ec2Client.DescribeInstances(ctx, query.Input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error running DescribeInstances for host set id %q: %s", query.Id, err)
|
return nil, errors.BadRequestStatusf("error running DescribeInstances for host set id %q: %s", query.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries[i].Output = output
|
queries[i].Output = output
|
||||||
@@ -550,9 +550,9 @@ func (p *HostPlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (*
|
|||||||
// set of hosts afterwards (possibly removing duplicates).
|
// set of hosts afterwards (possibly removing duplicates).
|
||||||
for _, reservation := range output.Reservations {
|
for _, reservation := range output.Reservations {
|
||||||
for _, instance := range reservation.Instances {
|
for _, instance := range reservation.Instances {
|
||||||
host, err := awsInstanceToHost(instance)
|
host, err := awsInstanceToHost(instance, catalogAttributes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error processing host results for host set id %q: %s", query.Id, err)
|
return nil, errors.BadRequestStatusf("error processing host results for host set id %q: %s", query.Id, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
queries[i].OutputHosts = append(queries[i].OutputHosts, host)
|
||||||
@@ -648,10 +648,13 @@ func buildDescribeInstancesInput(attrs *SetAttributes, dryRun bool) (*ec2.Descri
|
|||||||
// awsInstanceToHost processes data from an ec2.Instance and returns
|
// awsInstanceToHost processes data from an ec2.Instance and returns
|
||||||
// a ListHostsResponseHost with the ID and network addresses
|
// a ListHostsResponseHost with the ID and network addresses
|
||||||
// populated.
|
// populated.
|
||||||
func awsInstanceToHost(instance types.Instance) (*pb.ListHostsResponseHost, error) {
|
func awsInstanceToHost(instance types.Instance, catalogAttributes *CatalogAttributes) (*pb.ListHostsResponseHost, error) {
|
||||||
if aws.ToString(instance.InstanceId) == "" {
|
if aws.ToString(instance.InstanceId) == "" {
|
||||||
return nil, fmt.Errorf("response integrity error: missing instance id")
|
return nil, fmt.Errorf("response integrity error: missing instance id")
|
||||||
}
|
}
|
||||||
|
if catalogAttributes == nil {
|
||||||
|
return nil, fmt.Errorf("response integrity error: missing catalog attributes")
|
||||||
|
}
|
||||||
|
|
||||||
result := new(pb.ListHostsResponseHost)
|
result := new(pb.ListHostsResponseHost)
|
||||||
|
|
||||||
@@ -666,10 +669,16 @@ func awsInstanceToHost(instance types.Instance) (*pb.ListHostsResponseHost, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PrivateIpAddress, instance.PublicIpAddress)
|
// First let's get the instance's Private IPv4/DNS, Public IPv4/DNS, and IPv6 addresses
|
||||||
|
result.IpAddresses = appendDistinct(result.IpAddresses, instance.PrivateIpAddress, instance.PublicIpAddress, instance.Ipv6Address)
|
||||||
result.DnsNames = appendDistinct(result.DnsNames, instance.PrivateDnsName, instance.PublicDnsName)
|
result.DnsNames = appendDistinct(result.DnsNames, instance.PrivateDnsName, instance.PublicDnsName)
|
||||||
|
|
||||||
// Now go through all of the interfaces and log the IP address of
|
if catalogAttributes.InstanceAddressesOnly {
|
||||||
|
// return early as we already have the instance addresses
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now go through all of the interfaces and sync the IP address of
|
||||||
// every interface.
|
// every interface.
|
||||||
for _, iface := range instance.NetworkInterfaces {
|
for _, iface := range instance.NetworkInterfaces {
|
||||||
// Populate default IP addresses/DNS name similar to how we do
|
// Populate default IP addresses/DNS name similar to how we do
|
||||||
|
|||||||
@@ -638,7 +638,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -664,7 +665,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -694,7 +696,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -721,7 +724,8 @@ func TestPluginOnCreateCatalogErr(t *testing.T) {
|
|||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
credential.ConstAccessKeyId: structpb.NewStringValue("AKIA_foobar"),
|
||||||
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
credential.ConstSecretAccessKey: structpb.NewStringValue("bazqux"),
|
||||||
}},
|
},
|
||||||
|
},
|
||||||
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
Attributes: &structpb.Struct{
|
Attributes: &structpb.Struct{
|
||||||
Fields: map[string]*structpb.Value{
|
Fields: map[string]*structpb.Value{
|
||||||
@@ -2383,15 +2387,25 @@ func TestBuildDescribeInstancesInput(t *testing.T) {
|
|||||||
|
|
||||||
func TestAwsInstanceToHost(t *testing.T) {
|
func TestAwsInstanceToHost(t *testing.T) {
|
||||||
cases := []struct {
|
cases := []struct {
|
||||||
name string
|
name string
|
||||||
instance types.Instance
|
instance types.Instance
|
||||||
expected *pb.ListHostsResponseHost
|
catalogAttrs *CatalogAttributes
|
||||||
expectedErr string
|
expected *pb.ListHostsResponseHost
|
||||||
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing instance id",
|
name: "missing instance id",
|
||||||
instance: types.Instance{},
|
instance: types.Instance{},
|
||||||
expectedErr: "response integrity error: missing instance id",
|
catalogAttrs: &CatalogAttributes{},
|
||||||
|
expectedErr: "response integrity error: missing instance id",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "missing catalog attributes",
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("foobar"),
|
||||||
|
},
|
||||||
|
catalogAttrs: nil,
|
||||||
|
expectedErr: "response integrity error: missing catalog attributes",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "good, single IP w/public addr",
|
name: "good, single IP w/public addr",
|
||||||
@@ -2418,6 +2432,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
IpAddresses: []string{"10.0.0.1", "1.1.1.1"},
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1"},
|
||||||
@@ -2443,6 +2458,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
IpAddresses: []string{"10.0.0.1"},
|
IpAddresses: []string{"10.0.0.1"},
|
||||||
@@ -2484,6 +2500,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"},
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"},
|
||||||
@@ -2529,6 +2546,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2", "1.1.1.2"},
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2", "1.1.1.2"},
|
||||||
@@ -2564,6 +2582,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"},
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "10.0.0.2"},
|
||||||
@@ -2578,6 +2597,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
PrivateDnsName: aws.String("test.example.internal"),
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
PublicIpAddress: aws.String("1.1.1.1"),
|
PublicIpAddress: aws.String("1.1.1.1"),
|
||||||
PublicDnsName: aws.String("test.example.com"),
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
Ipv6Address: aws.String("instance::ipv6::address"),
|
||||||
NetworkInterfaces: []types.InstanceNetworkInterface{
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
{
|
{
|
||||||
PrivateIpAddress: aws.String("10.0.0.1"),
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
@@ -2596,9 +2616,63 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "some::fake::address"},
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "instance::ipv6::address", "some::fake::address"},
|
||||||
|
DnsNames: []string{"test.example.internal", "test.example.com"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "good, instance addresses only excludes interface secondary addresses",
|
||||||
|
instance: types.Instance{
|
||||||
|
InstanceId: aws.String("foobar"),
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PublicIpAddress: aws.String("1.1.1.1"),
|
||||||
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
Ipv6Address: aws.String("instance::ipv6::address"),
|
||||||
|
NetworkInterfaces: []types.InstanceNetworkInterface{
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("1.1.1.1"),
|
||||||
|
PublicDnsName: aws.String("test.example.com"),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.1"),
|
||||||
|
PrivateDnsName: aws.String("test.example.internal"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.0.2"),
|
||||||
|
PrivateDnsName: aws.String("test2.example.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("iface::ipv6::address")}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
PrivateIpAddress: aws.String("10.0.1.1"),
|
||||||
|
PrivateDnsName: aws.String("secondary.example.internal"),
|
||||||
|
PrivateIpAddresses: []types.InstancePrivateIpAddress{
|
||||||
|
{
|
||||||
|
Association: &types.InstanceNetworkInterfaceAssociation{
|
||||||
|
PublicIp: aws.String("2.2.2.2"),
|
||||||
|
PublicDnsName: aws.String("secondary.example.com"),
|
||||||
|
},
|
||||||
|
PrivateIpAddress: aws.String("10.0.1.1"),
|
||||||
|
PrivateDnsName: aws.String("secondary.example.internal"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Ipv6Addresses: []types.InstanceIpv6Address{{Ipv6Address: aws.String("secondary::ipv6::address")}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{InstanceAddressesOnly: true},
|
||||||
|
expected: &pb.ListHostsResponseHost{
|
||||||
|
ExternalId: "foobar",
|
||||||
|
IpAddresses: []string{"10.0.0.1", "1.1.1.1", "instance::ipv6::address"},
|
||||||
DnsNames: []string{"test.example.internal", "test.example.com"},
|
DnsNames: []string{"test.example.internal", "test.example.com"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -2632,6 +2706,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
ExternalName: "test-instance-actual-name",
|
ExternalName: "test-instance-actual-name",
|
||||||
@@ -2669,6 +2744,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
catalogAttrs: &CatalogAttributes{},
|
||||||
expected: &pb.ListHostsResponseHost{
|
expected: &pb.ListHostsResponseHost{
|
||||||
ExternalId: "foobar",
|
ExternalId: "foobar",
|
||||||
ExternalName: "test-instance-actual-name",
|
ExternalName: "test-instance-actual-name",
|
||||||
@@ -2682,7 +2758,7 @@ func TestAwsInstanceToHost(t *testing.T) {
|
|||||||
tc := tc
|
tc := tc
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
require := require.New(t)
|
require := require.New(t)
|
||||||
actual, err := awsInstanceToHost(tc.instance)
|
actual, err := awsInstanceToHost(tc.instance, tc.catalogAttrs)
|
||||||
if tc.expectedErr != "" {
|
if tc.expectedErr != "" {
|
||||||
require.EqualError(err, tc.expectedErr)
|
require.EqualError(err, tc.expectedErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -11,5 +11,5 @@ const (
|
|||||||
ConstAwsDualStack = "dual_stack"
|
ConstAwsDualStack = "dual_stack"
|
||||||
|
|
||||||
// defaultStreamChunkSize is the recommened chunk size for sending data through a stream
|
// defaultStreamChunkSize is the recommened chunk size for sending data through a stream
|
||||||
defaultStreamChunkSize = 65536 // 64 KiB
|
defaultStreamChunkSize = 1 << 20 // 1 MiB
|
||||||
)
|
)
|
||||||
@@ -40,6 +40,10 @@ func setStatePermission(req any, permission *pb.Permission) *pb.StorageBucketCre
|
|||||||
state.State.Delete = permission
|
state.State.Delete = permission
|
||||||
case *pb.DeleteObjectsRequest:
|
case *pb.DeleteObjectsRequest:
|
||||||
state.State.Delete = permission
|
state.State.Delete = permission
|
||||||
|
case pb.ListObjectsRequest:
|
||||||
|
state.State.Read = permission
|
||||||
|
case *pb.ListObjectsRequest:
|
||||||
|
state.State.Read = permission
|
||||||
}
|
}
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
stderr "errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
@@ -76,7 +77,7 @@ func (p *StoragePlugin) getClient(ctx context.Context,
|
|||||||
// No storage bucket ID to key cache on, create new client and return
|
// No storage bucket ID to key cache on, create new client and return
|
||||||
client, err := storageState.s3Client(ctx, opt...)
|
client, err := storageState.s3Client(ctx, opt...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error creating S3 client: %s", err)
|
return nil, errors.BadRequestStatusf("error creating S3 client: %s", err)
|
||||||
}
|
}
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
@@ -92,7 +93,7 @@ func (p *StoragePlugin) getClient(ctx context.Context,
|
|||||||
defer p.clients.Unlock()
|
defer p.clients.Unlock()
|
||||||
client, err := storageState.s3Client(ctx, opt...)
|
client, err := storageState.s3Client(ctx, opt...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error creating S3 client: %s", err)
|
return nil, errors.BadRequestStatusf("error creating S3 client: %s", err)
|
||||||
}
|
}
|
||||||
p.clients.cache[storageBucketId] = client
|
p.clients.cache[storageBucketId] = client
|
||||||
return client, nil
|
return client, nil
|
||||||
@@ -118,7 +119,7 @@ func (p *StoragePlugin) getClient(ctx context.Context,
|
|||||||
var err error
|
var err error
|
||||||
client, err = storageState.s3Client(ctx, opt...)
|
client, err = storageState.s3Client(ctx, opt...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error creating S3 client: %s", err)
|
return nil, errors.BadRequestStatusf("error creating S3 client: %s", err)
|
||||||
}
|
}
|
||||||
p.clients.cache[storageBucketId] = client
|
p.clients.cache[storageBucketId] = client
|
||||||
}
|
}
|
||||||
@@ -138,7 +139,7 @@ func (p *StoragePlugin) call(
|
|||||||
|
|
||||||
s3Client, err := p.getClient(ctx, storageBucketId, storageState, opts...)
|
s3Client, err := p.getClient(ctx, storageBucketId, storageState, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
|
return nil, errors.BadRequestStatusf("error getting S3 client: %s", err)
|
||||||
}
|
}
|
||||||
firstAttempt := true
|
firstAttempt := true
|
||||||
for {
|
for {
|
||||||
@@ -151,7 +152,7 @@ func (p *StoragePlugin) call(
|
|||||||
opts = append(opts, WithCacheRefresh(true))
|
opts = append(opts, WithCacheRefresh(true))
|
||||||
s3Client, err = p.getClient(ctx, storageBucketId, storageState, opts...)
|
s3Client, err = p.getClient(ctx, storageBucketId, storageState, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err)
|
return nil, errors.BadRequestStatusf("error refreshing S3 client: %s", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -218,7 +219,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to rotate AWS static credentials
|
// Try to rotate AWS static credentials
|
||||||
@@ -234,7 +235,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("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 the bucket as expected.
|
// perform dry run to ensure we can interact with the bucket as expected.
|
||||||
@@ -244,7 +245,7 @@ func (p *StoragePlugin) OnCreateStorageBucket(ctx context.Context, req *pb.OnCre
|
|||||||
|
|
||||||
persistedProto, err := storageState.toProto()
|
persistedProto, err := storageState.toProto()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("%s", err.Error())
|
return nil, errors.BadRequestStatus(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.OnCreateStorageBucketResponse{
|
return &pb.OnCreateStorageBucketResponse{
|
||||||
@@ -292,7 +293,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
oldStorageAttributes.DualStack,
|
oldStorageAttributes.DualStack,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify the incoming credentials are valid and return any errors to the
|
// Verify the incoming credentials are valid and return any errors to the
|
||||||
@@ -315,7 +316,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up new credential persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up new credential persisted state: %s", err)
|
||||||
}
|
}
|
||||||
newStorageState, err := newAwsStoragePersistedState(
|
newStorageState, err := newAwsStoragePersistedState(
|
||||||
append([]awsStoragePersistedStateOption{
|
append([]awsStoragePersistedStateOption{
|
||||||
@@ -323,7 +324,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
if st := dryRunValidation(ctx, newStorageState, newStorageAttributes, newBucket); st != nil {
|
if st := dryRunValidation(ctx, newStorageState, newStorageAttributes, newBucket); st != nil {
|
||||||
return nil, st.Err()
|
return nil, st.Err()
|
||||||
@@ -363,7 +364,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("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 the bucket as expected.
|
// perform dry run to ensure we can interact with the bucket as expected.
|
||||||
@@ -373,7 +374,7 @@ func (p *StoragePlugin) OnUpdateStorageBucket(ctx context.Context, req *pb.OnUpd
|
|||||||
|
|
||||||
persistedProto, err := storageState.toProto()
|
persistedProto, err := storageState.toProto()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("%s", err.Error())
|
return nil, errors.BadRequestStatus(err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.OnUpdateStorageBucketResponse{
|
return &pb.OnUpdateStorageBucketResponse{
|
||||||
@@ -410,7 +411,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
|||||||
storageAttributes.DualStack,
|
storageAttributes.DualStack,
|
||||||
p.testCredStateOpts...)
|
p.testCredStateOpts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = newAwsStoragePersistedState(
|
_, err = newAwsStoragePersistedState(
|
||||||
@@ -419,7 +420,7 @@ func (p *StoragePlugin) OnDeleteStorageBucket(ctx context.Context, req *pb.OnDel
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error loading persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error loading persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// try to delete static credentials
|
// try to delete static credentials
|
||||||
@@ -474,7 +475,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -483,7 +484,7 @@ func (p *StoragePlugin) HeadObject(ctx context.Context, req *pb.HeadObjectReques
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -546,7 +547,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -555,7 +556,7 @@ func (p *StoragePlugin) ValidatePermissions(ctx context.Context, req *pb.Validat
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("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 the bucket as expected.
|
// perform dry run to ensure we can interact with the bucket as expected.
|
||||||
@@ -604,7 +605,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -613,7 +614,7 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -647,13 +648,13 @@ func (p *StoragePlugin) GetObject(req *pb.GetObjectRequest, stream pb.StoragePlu
|
|||||||
buffer := make([]byte, chunkSize)
|
buffer := make([]byte, chunkSize)
|
||||||
n, err := reader.Read(buffer)
|
n, err := reader.Read(buffer)
|
||||||
if err != nil && err != io.EOF {
|
if err != nil && err != io.EOF {
|
||||||
return errors.UnknownStatus("error reading chunk from s3: %s", err)
|
return errors.UnknownStatusf("error reading chunk from s3: %s", err)
|
||||||
}
|
}
|
||||||
if n > 0 {
|
if n > 0 {
|
||||||
if err := stream.Send(&pb.GetObjectResponse{
|
if err := stream.Send(&pb.GetObjectResponse{
|
||||||
FileChunk: buffer[:n],
|
FileChunk: buffer[:n],
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return errors.UnknownStatus("error sending chunk to client: %s", err)
|
return errors.UnknownStatusf("error sending chunk to client: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err == io.EOF {
|
if err == io.EOF {
|
||||||
@@ -718,7 +719,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -727,7 +728,7 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest)
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -817,7 +818,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
}, p.testCredStateOpts...)...,
|
}, p.testCredStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
storageState, err := newAwsStoragePersistedState(
|
storageState, err := newAwsStoragePersistedState(
|
||||||
@@ -826,7 +827,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
}, p.testStorageStateOpts...)...,
|
}, p.testStorageStateOpts...)...,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error setting up persisted state: %s", err)
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
opts := []s3Option{}
|
opts := []s3Option{}
|
||||||
@@ -861,7 +862,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
objects := []types.ObjectIdentifier{}
|
objects := []types.ObjectIdentifier{}
|
||||||
client, err := p.getClient(ctx, bucket.GetId(), storageState, opts...)
|
client, err := p.getClient(ctx, bucket.GetId(), storageState, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error getting S3 client: %s", err)
|
return nil, errors.BadRequestStatusf("error getting S3 client: %s", err)
|
||||||
}
|
}
|
||||||
var conToken *string
|
var conToken *string
|
||||||
truncated, firstAttempt := true, true
|
truncated, firstAttempt := true, true
|
||||||
@@ -880,7 +881,7 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
opts = append(opts, WithCacheRefresh(true))
|
opts = append(opts, WithCacheRefresh(true))
|
||||||
client, err = p.getClient(ctx, bucket.GetId(), storageState, opts...)
|
client, err = p.getClient(ctx, bucket.GetId(), storageState, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.BadRequestStatus("error refreshing S3 client: %s", err)
|
return nil, errors.BadRequestStatusf("error refreshing S3 client: %s", err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -917,6 +918,191 @@ func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjects
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListObjects retrieves files and directories from an S3 bucket under a given prefix.
|
||||||
|
// If the "recursive" flag in the request is false (default), it returns only the top-level objects directly under the prefix.
|
||||||
|
// If the "recursive" is true, it returns all objects under the prefix recursively.
|
||||||
|
func (p *StoragePlugin) ListObjects(ctx context.Context, req *pb.ListObjectsRequest) (*pb.ListObjectsResponse, error) {
|
||||||
|
bucket := req.GetBucket()
|
||||||
|
if bucket == nil {
|
||||||
|
return nil, errors.BadRequestStatus("bucket is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
if bucket.BucketName == "" {
|
||||||
|
return nil, errors.BadRequestStatus("bucketName is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs := bucket.GetAttributes()
|
||||||
|
if attrs == nil {
|
||||||
|
return nil, errors.BadRequestStatus("attributes is required")
|
||||||
|
}
|
||||||
|
|
||||||
|
storageAttributes, err := getStorageAttributes(attrs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
credConfig, err := cred.GetCredentialsConfig(bucket.GetSecrets(), storageAttributes.CredentialAttributes, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up the creds in our persisted state.
|
||||||
|
credState, err := cred.NewAwsCredentialPersistedState(
|
||||||
|
append([]cred.AwsCredentialPersistedStateOption{
|
||||||
|
cred.WithCredentialsConfig(credConfig),
|
||||||
|
}, p.testCredStateOpts...)...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
storageState, err := newAwsStoragePersistedState(
|
||||||
|
append([]awsStoragePersistedStateOption{
|
||||||
|
withCredentials(credState),
|
||||||
|
}, p.testStorageStateOpts...)...,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.BadRequestStatusf("error setting up persisted state: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var opts []s3Option
|
||||||
|
if storageAttributes.EndpointUrl != "" {
|
||||||
|
opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl))
|
||||||
|
}
|
||||||
|
s3Client, err := storageState.s3Client(ctx, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.BadRequestStatusf("error getting S3 client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix := path.Join(bucket.GetBucketPrefix(), req.GetKeyPrefix())
|
||||||
|
if strings.HasSuffix(req.GetKeyPrefix(), "/") {
|
||||||
|
// path.Join ends by "cleaning" the path, including removing a trailing slash, if
|
||||||
|
// it exists. given that a slash is used to denote a folder, it is required here.
|
||||||
|
prefix += "/"
|
||||||
|
}
|
||||||
|
var objects []*pb.Object
|
||||||
|
|
||||||
|
if req.GetRecursive() {
|
||||||
|
objects, err = listObjectsRecursive(ctx, s3Client, bucket.GetBucketName(), prefix, req)
|
||||||
|
} else {
|
||||||
|
objects, err = listObjectsTopLevel(ctx, s3Client, bucket.GetBucketName(), prefix, req)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.ListObjectsResponse{
|
||||||
|
Objects: objects,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listObjectsTopLevel lists only the objects that exist directly under the specified prefix (This does not traverse subdirectories)
|
||||||
|
// It uses the S3 "Delimiter" option to group results so that subdirectories appear as CommonPrefixes.
|
||||||
|
func listObjectsTopLevel(ctx context.Context, s3Client S3API, bucketName, prefix string, req *pb.ListObjectsRequest) ([]*pb.Object, error) {
|
||||||
|
var objects []*pb.Object
|
||||||
|
paginator := s3.NewListObjectsV2Paginator(s3Client, &s3.ListObjectsV2Input{
|
||||||
|
Bucket: aws.String(bucketName),
|
||||||
|
Prefix: aws.String(prefix),
|
||||||
|
Delimiter: aws.String("/"),
|
||||||
|
})
|
||||||
|
|
||||||
|
for paginator.HasMorePages() {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
switch {
|
||||||
|
case stderr.Is(err, context.Canceled):
|
||||||
|
return nil, status.Error(codes.Canceled, "request canceled")
|
||||||
|
case stderr.Is(err, context.DeadlineExceeded):
|
||||||
|
return nil, status.Error(codes.DeadlineExceeded, "request deadline exceeded")
|
||||||
|
default:
|
||||||
|
return nil, status.Error(codes.Internal, "unknown context error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page, err := paginator.NextPage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, parseS3Error("failed to list objects", err, req).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range page.Contents {
|
||||||
|
if c.Key != nil {
|
||||||
|
objects = append(objects, &pb.Object{Key: *c.Key, IsDir: false})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, cp := range page.CommonPrefixes {
|
||||||
|
if cp.Prefix != nil {
|
||||||
|
objects = append(objects, &pb.Object{Key: *cp.Prefix, IsDir: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// listObjectsRecursive lists all objects under the specified prefix, traversing all subdirectories.
|
||||||
|
// It does not use the S3 "Delimiter" option, so every object key that matches the prefix is returned.
|
||||||
|
// This mode is useful for fully scanning all objects in a folder tree.
|
||||||
|
func listObjectsRecursive(ctx context.Context, s3Client S3API, bucketName, prefix string, req *pb.ListObjectsRequest) ([]*pb.Object, error) {
|
||||||
|
var objects []*pb.Object
|
||||||
|
folderSet := make(map[string]struct{})
|
||||||
|
paginator := s3.NewListObjectsV2Paginator(s3Client, &s3.ListObjectsV2Input{
|
||||||
|
Bucket: aws.String(bucketName),
|
||||||
|
Prefix: aws.String(prefix),
|
||||||
|
})
|
||||||
|
|
||||||
|
for paginator.HasMorePages() {
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
switch {
|
||||||
|
case stderr.Is(err, context.Canceled):
|
||||||
|
return nil, status.Error(codes.Canceled, "request canceled")
|
||||||
|
case stderr.Is(err, context.DeadlineExceeded):
|
||||||
|
return nil, status.Error(codes.DeadlineExceeded, "request deadline exceeded")
|
||||||
|
default:
|
||||||
|
return nil, status.Error(codes.Internal, "unknown context error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
page, err := paginator.NextPage(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, parseS3Error("failed to list objects", err, req).Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, c := range page.Contents {
|
||||||
|
// We intentionally ignore "empty objects" that simulate folders.
|
||||||
|
// S3 is not a traditional file system and does not have real directories.
|
||||||
|
// Folders are an abstraction; they are represented by zero-byte objects
|
||||||
|
// whose keys end with a slash (e.g., "myfolder/"). These objects exist
|
||||||
|
// solely as placeholders to give the appearance of directories in S3 tools.
|
||||||
|
// Here, we only include actual objects with content (non-zero length).
|
||||||
|
if c.Key != nil && !strings.HasSuffix(*c.Key, "/") {
|
||||||
|
objects = append(objects, &pb.Object{Key: *c.Key, IsDir: false})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract all parent folder paths from the object key.
|
||||||
|
// We walk up the directory hierarchy using path.Dir,
|
||||||
|
// adding each parent folder to folderSet until we reach
|
||||||
|
// the root ("/" or ".") or leave the current prefix subtree.
|
||||||
|
// This ensures all intermediate "folders" appear in the result even if
|
||||||
|
// no explicit zero-byte object exists for them.
|
||||||
|
dir := path.Dir(*c.Key)
|
||||||
|
for dir != "." && dir != "/" {
|
||||||
|
folder := dir + "/"
|
||||||
|
if !strings.HasPrefix(folder, prefix) {
|
||||||
|
break // left the subtree, stop climbing up
|
||||||
|
}
|
||||||
|
if folder != prefix {
|
||||||
|
folderSet[folder] = struct{}{}
|
||||||
|
}
|
||||||
|
dir = path.Dir(dir)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Add folders
|
||||||
|
for folder := range folderSet {
|
||||||
|
objects = append(objects, &pb.Object{
|
||||||
|
Key: folder,
|
||||||
|
IsDir: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return objects, nil
|
||||||
|
}
|
||||||
|
|
||||||
// dryRunValidation validates that the IAM role policy attached to the given secrets of the
|
// dryRunValidation validates that the IAM role policy attached to the given secrets of the
|
||||||
// storage bucket has the minimum required permissions needed for this plugin to function
|
// storage bucket has the minimum required permissions needed for this plugin to function
|
||||||
// as expected. This function will create an object in the s3 bucket to validate it has
|
// as expected. This function will create an object in the s3 bucket to validate it has
|
||||||
|
|||||||
@@ -4202,3 +4202,391 @@ func TestS3ClientCache(t *testing.T) {
|
|||||||
assert.Equal(cred3.AccessKey, client.Credentials().AccessKeyID)
|
assert.Equal(cred3.AccessKey, client.Credentials().AccessKeyID)
|
||||||
assert.Equal(cred3.SecretKey, client.Credentials().SecretAccessKey)
|
assert.Equal(cred3.SecretKey, client.Credentials().SecretAccessKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStoragePlugin_ListObjects(t *testing.T) {
|
||||||
|
validRequest := func() *pb.ListObjectsRequest {
|
||||||
|
return &pb.ListObjectsRequest{
|
||||||
|
KeyPrefix: "foo/bar/",
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: "foo",
|
||||||
|
Secrets: credential.MockStaticCredentialSecrets(),
|
||||||
|
Attributes: &structpb.Struct{
|
||||||
|
Fields: map[string]*structpb.Value{
|
||||||
|
credential.ConstRegion: structpb.NewStringValue("us-west-2"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
req *pb.ListObjectsRequest
|
||||||
|
credOpts []credential.AwsCredentialPersistedStateOption
|
||||||
|
storageOpts []awsStoragePersistedStateOption
|
||||||
|
ctx context.Context
|
||||||
|
expectedObjects []*pb.Object
|
||||||
|
expectedErrContains string
|
||||||
|
expectedErrCode codes.Code
|
||||||
|
expectedDetails *pb.StorageBucketCredentialState
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil bucket",
|
||||||
|
req: &pb.ListObjectsRequest{KeyPrefix: "foo/bar"},
|
||||||
|
expectedErrContains: "bucket is required",
|
||||||
|
expectedErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty bucket name",
|
||||||
|
req: &pb.ListObjectsRequest{
|
||||||
|
KeyPrefix: "foo/bar",
|
||||||
|
Bucket: &storagebuckets.StorageBucket{},
|
||||||
|
},
|
||||||
|
expectedErrContains: "bucketName is required",
|
||||||
|
expectedErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil attributes",
|
||||||
|
req: &pb.ListObjectsRequest{
|
||||||
|
KeyPrefix: "foo/bar",
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: "foo",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrContains: "attributes is required",
|
||||||
|
expectedErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "error reading attributes",
|
||||||
|
req: &pb.ListObjectsRequest{
|
||||||
|
KeyPrefix: "foo/bar",
|
||||||
|
Bucket: &storagebuckets.StorageBucket{
|
||||||
|
BucketName: "foo",
|
||||||
|
Attributes: new(structpb.Struct),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrContains: "missing required value \"region\"",
|
||||||
|
expectedErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "credential persisted state setup error",
|
||||||
|
req: validRequest(),
|
||||||
|
credOpts: []credential.AwsCredentialPersistedStateOption{
|
||||||
|
func(s *credential.AwsCredentialPersistedState) error {
|
||||||
|
return errors.New(testOptionErr)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrContains: fmt.Sprintf("error setting up persisted state: %s", testOptionErr),
|
||||||
|
expectedErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "storage persisted state setup error",
|
||||||
|
req: validRequest(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
func(s *awsStoragePersistedState) error {
|
||||||
|
return errors.New(testOptionErr)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedErrContains: fmt.Sprintf("error setting up persisted state: %s", testOptionErr),
|
||||||
|
expectedErrCode: codes.InvalidArgument,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "listObjectsV2 access denied error",
|
||||||
|
req: validRequest(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
testMockS3WithListObjectsV2Error(TestAwsS3Error("AccessDenied", "ListObjectsV2", "not authorized")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedErrContains: "aws service s3: invalid credentials: failed to list objects",
|
||||||
|
expectedErrCode: codes.PermissionDenied,
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_ERROR,
|
||||||
|
ErrorDetails: "not authorized",
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "listObjectsV2 throttling error",
|
||||||
|
req: validRequest(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
testMockS3WithListObjectsV2Error(TestAwsS3Error("ThrottlingException", "ListObjectsV2", "throttling exception")),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedErrContains: "aws service s3: throttling: failed to list objects",
|
||||||
|
expectedErrCode: codes.Unavailable,
|
||||||
|
expectedDetails: &pb.StorageBucketCredentialState{
|
||||||
|
State: &pb.Permissions{
|
||||||
|
Read: &pb.Permission{
|
||||||
|
State: pb.StateType_STATE_TYPE_UNKNOWN,
|
||||||
|
CheckedAt: timestamppb.Now(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "context canceled - non recursive",
|
||||||
|
req: validRequest(),
|
||||||
|
ctx: func() context.Context {
|
||||||
|
c, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
// Return an error that simulates context cancellation being detected at the AWS level
|
||||||
|
testMockS3WithListObjectsV2Error(context.Canceled),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedErrContains: "request canceled",
|
||||||
|
expectedErrCode: codes.Canceled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "context deadline exceeded - non recursive",
|
||||||
|
req: validRequest(),
|
||||||
|
ctx: func() context.Context {
|
||||||
|
c, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
|
||||||
|
time.Sleep(2 * time.Nanosecond) // Ensure deadline is exceeded
|
||||||
|
cancel()
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
// Return an error that simulates deadline exceeded being detected at the AWS level
|
||||||
|
testMockS3WithListObjectsV2Error(context.DeadlineExceeded),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedErrContains: "request deadline exceeded",
|
||||||
|
expectedErrCode: codes.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "context canceled - recursive",
|
||||||
|
req: func() *pb.ListObjectsRequest {
|
||||||
|
r := validRequest()
|
||||||
|
r.Recursive = true
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
ctx: func() context.Context {
|
||||||
|
c, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel() // Cancel immediately
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
// Return an error that simulates context cancellation being detected at the AWS level
|
||||||
|
testMockS3WithListObjectsV2Error(context.Canceled),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedErrContains: "request canceled",
|
||||||
|
expectedErrCode: codes.Canceled,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "context deadline exceeded - recursive",
|
||||||
|
req: func() *pb.ListObjectsRequest {
|
||||||
|
r := validRequest()
|
||||||
|
r.Recursive = true
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
ctx: func() context.Context {
|
||||||
|
c, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
|
||||||
|
// Ensure the deadline is exceeded
|
||||||
|
time.Sleep(2 * time.Nanosecond)
|
||||||
|
cancel()
|
||||||
|
return c
|
||||||
|
}(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
// Return an error that simulates deadline exceeded being detected at the AWS level
|
||||||
|
testMockS3WithListObjectsV2Error(context.DeadlineExceeded),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedErrContains: "request deadline exceeded",
|
||||||
|
expectedErrCode: codes.DeadlineExceeded,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success - single page top-level",
|
||||||
|
req: validRequest(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
|
||||||
|
CommonPrefixes: []s3types.CommonPrefix{
|
||||||
|
{Prefix: aws.String("foo/bar/key/")},
|
||||||
|
},
|
||||||
|
Contents: []s3types.Object{
|
||||||
|
{Key: aws.String("foo/bar/file1.bsr")},
|
||||||
|
{Key: aws.String("foo/bar/file2.bsr")},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedObjects: []*pb.Object{
|
||||||
|
{Key: "foo/bar/key/", IsDir: true},
|
||||||
|
{Key: "foo/bar/file1.bsr", IsDir: false},
|
||||||
|
{Key: "foo/bar/file2.bsr", IsDir: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success - recursive listing with nested dirs",
|
||||||
|
req: func() *pb.ListObjectsRequest {
|
||||||
|
r := validRequest()
|
||||||
|
r.Recursive = true
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{
|
||||||
|
Contents: []s3types.Object{
|
||||||
|
{Key: aws.String("foo/bar/file1.bsr")},
|
||||||
|
{Key: aws.String("foo/bar/nested/file2.bsr")},
|
||||||
|
{Key: aws.String("foo/bar/nested/deeper/file3.bsr")},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedObjects: []*pb.Object{
|
||||||
|
{Key: "foo/bar/file1.bsr", IsDir: false},
|
||||||
|
{Key: "foo/bar/nested/", IsDir: true},
|
||||||
|
{Key: "foo/bar/nested/file2.bsr", IsDir: false},
|
||||||
|
{Key: "foo/bar/nested/deeper/", IsDir: true},
|
||||||
|
{Key: "foo/bar/nested/deeper/file3.bsr", IsDir: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success - recursive multi-page",
|
||||||
|
req: func() *pb.ListObjectsRequest {
|
||||||
|
r := validRequest()
|
||||||
|
r.Recursive = true
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(
|
||||||
|
nil,
|
||||||
|
testMockS3WithListObjectsV2OutputFunc(func(input *s3.ListObjectsV2Input) *s3.ListObjectsV2Output {
|
||||||
|
if input.ContinuationToken == nil {
|
||||||
|
return &s3.ListObjectsV2Output{
|
||||||
|
Contents: []s3types.Object{
|
||||||
|
{Key: aws.String("foo/bar/page1/file1.bsr")},
|
||||||
|
},
|
||||||
|
NextContinuationToken: aws.String("token123"),
|
||||||
|
IsTruncated: aws.Bool(true),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if aws.ToString(input.ContinuationToken) == "token123" {
|
||||||
|
return &s3.ListObjectsV2Output{
|
||||||
|
Contents: []s3types.Object{
|
||||||
|
{Key: aws.String("foo/bar/page2/file2.bsr")},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &s3.ListObjectsV2Output{}
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedObjects: []*pb.Object{
|
||||||
|
{Key: "foo/bar/page1/", IsDir: true},
|
||||||
|
{Key: "foo/bar/page1/file1.bsr", IsDir: false},
|
||||||
|
{Key: "foo/bar/page2/", IsDir: true},
|
||||||
|
{Key: "foo/bar/page2/file2.bsr", IsDir: false},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "success - recursive empty",
|
||||||
|
req: func() *pb.ListObjectsRequest {
|
||||||
|
r := validRequest()
|
||||||
|
r.Recursive = true
|
||||||
|
return r
|
||||||
|
}(),
|
||||||
|
credOpts: validSTSMock(),
|
||||||
|
storageOpts: []awsStoragePersistedStateOption{
|
||||||
|
withTestS3APIFunc(
|
||||||
|
newTestMockS3(nil, testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{})),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
expectedObjects: []*pb.Object(nil),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require, assert := require.New(t), assert.New(t)
|
||||||
|
p := &StoragePlugin{
|
||||||
|
testCredStateOpts: tc.credOpts,
|
||||||
|
testStorageStateOpts: tc.storageOpts,
|
||||||
|
}
|
||||||
|
ctx := tc.ctx
|
||||||
|
if ctx == nil {
|
||||||
|
ctx = context.Background()
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, tc.req)
|
||||||
|
|
||||||
|
if tc.expectedErrContains != "" {
|
||||||
|
require.Error(err)
|
||||||
|
assert.Contains(err.Error(), tc.expectedErrContains)
|
||||||
|
assert.Equal(tc.expectedErrCode, status.Code(err))
|
||||||
|
|
||||||
|
st, ok := status.FromError(err)
|
||||||
|
require.True(ok)
|
||||||
|
if tc.expectedDetails != nil {
|
||||||
|
var state *pb.StorageBucketCredentialState
|
||||||
|
for _, detail := range st.Details() {
|
||||||
|
if errDetail, ok := detail.(*pb.StorageBucketCredentialState); ok {
|
||||||
|
state = errDetail
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require.NotNil(state)
|
||||||
|
internal.CheckSimilarPermission(assert, tc.expectedDetails.State.Read, state.State.Read, false)
|
||||||
|
internal.CheckSimilarPermission(assert, tc.expectedDetails.State.Write, state.State.Write, false)
|
||||||
|
internal.CheckSimilarPermission(assert, tc.expectedDetails.State.Delete, state.State.Delete, false)
|
||||||
|
} else {
|
||||||
|
assert.Len(st.Details(), 0)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(err)
|
||||||
|
require.NotNil(resp)
|
||||||
|
require.ElementsMatch(resp.Objects, tc.expectedObjects)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,9 +7,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/aws/aws-sdk-go-v2/aws"
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||||||
|
"github.com/aws/aws-sdk-go-v2/aws/retry"
|
||||||
|
awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
|
||||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||||
smithyEndpoints "github.com/aws/smithy-go/endpoints"
|
smithyEndpoints "github.com/aws/smithy-go/endpoints"
|
||||||
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
"github.com/hashicorp/boundary-plugin-aws/internal/credential"
|
||||||
@@ -118,7 +122,10 @@ func (s *awsStoragePersistedState) s3Client(ctx context.Context, opt ...s3Option
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to retrieve credentials: %v\n", err)
|
return nil, fmt.Errorf("failed to retrieve credentials: %v\n", err)
|
||||||
}
|
}
|
||||||
|
err = newTransport(awsCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to configure new http transport: %v", err)
|
||||||
|
}
|
||||||
c := s3.NewFromConfig(*awsCfg, s3Opts...)
|
c := s3.NewFromConfig(*awsCfg, s3Opts...)
|
||||||
return &s3Client{Client: c, creds: creds}, nil
|
return &s3Client{Client: c, creds: creds}, nil
|
||||||
}
|
}
|
||||||
@@ -202,3 +209,30 @@ func (e *endpointResolver) ResolveEndpoint(ctx context.Context, params s3.Endpoi
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// newTransport returns an http Transport object. The http transport object utilizes the
|
||||||
|
// default transport created by minio. The following transport fields are updated to provide
|
||||||
|
// more reliable connectivity when handling larger objects:
|
||||||
|
// MaxResponseHeaderBytes is set to 1MiB to prevent header reads from stalling.
|
||||||
|
// WriteBufferSize is set to 1MiB, matching the default buffer size used in Boundary storage.
|
||||||
|
// ReadBufferSize is set to 1MiB, matching the default buffer size used in Boundary storage.
|
||||||
|
// ForceAttemptHTTP2 is set to true, supporting more resilient multiplexing and avoids HOL blocking on retries.
|
||||||
|
// IdleConnTimeout is set to 2 minutes.
|
||||||
|
func newTransport(cfg *aws.Config) error {
|
||||||
|
if cfg == nil {
|
||||||
|
return fmt.Errorf("missing aws configuration")
|
||||||
|
}
|
||||||
|
cfg.HTTPClient = awshttp.NewBuildableClient().WithTransportOptions(func(transport *http.Transport) {
|
||||||
|
transport.MaxResponseHeaderBytes = 1 << 20 // 1 MiB prevents header read from stalling
|
||||||
|
transport.WriteBufferSize = 1 << 20 // 1 MiB write buffer (default is 4 KiB)
|
||||||
|
transport.ReadBufferSize = 1 << 20 // 1 MiB read buffer (default is 4 KiB)
|
||||||
|
transport.ForceAttemptHTTP2 = true // enables HTTP/2 where supported more resilient multiplexing, avoids HOL blocking on retries
|
||||||
|
transport.IdleConnTimeout = 2 * time.Minute // double the idle connection timeout, default is 1 minute
|
||||||
|
})
|
||||||
|
cfg.Retryer = func() aws.Retryer {
|
||||||
|
return retry.NewStandard(func(o *retry.StandardOptions) {
|
||||||
|
o.MaxAttempts = 20
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+107
-9
@@ -22,8 +22,6 @@ import (
|
|||||||
|
|
||||||
const expectedEc2InstanceCount = 5
|
const expectedEc2InstanceCount = 5
|
||||||
|
|
||||||
var expectedTags = []string{"foo", "bar", "baz"}
|
|
||||||
|
|
||||||
func TestHostPlugin(t *testing.T) {
|
func TestHostPlugin(t *testing.T) {
|
||||||
region := os.Getenv("AWS_REGION")
|
region := os.Getenv("AWS_REGION")
|
||||||
if region == "" {
|
if region == "" {
|
||||||
@@ -80,6 +78,14 @@ func TestHostPlugin(t *testing.T) {
|
|||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
require.Len(ec2InstanceTags, expectedEc2InstanceCount)
|
require.Len(ec2InstanceTags, expectedEc2InstanceCount)
|
||||||
|
|
||||||
|
rawExpectedTags, err := tf.GetOutputSlice("instance_tag_keys")
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(rawExpectedTags, 3)
|
||||||
|
expectedTags := make([]string, 0, len(rawExpectedTags))
|
||||||
|
for _, rawExpectedTag := range rawExpectedTags {
|
||||||
|
expectedTags = append(expectedTags, rawExpectedTag.(string))
|
||||||
|
}
|
||||||
|
|
||||||
// Start the workflow now. Set up the host catalog. Note that this
|
// Start the workflow now. Set up the host catalog. Note that this
|
||||||
// will cause the state to go out of drift above in the sense that
|
// will cause the state to go out of drift above in the sense that
|
||||||
// the access key ID/secret access key will no longer be valid. We
|
// the access key ID/secret access key will no longer be valid. We
|
||||||
@@ -143,13 +149,13 @@ func TestHostPlugin(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cases := [][]string{
|
cases := [][]string{
|
||||||
{"foo"},
|
{expectedTags[0]},
|
||||||
{"bar"},
|
{expectedTags[1]},
|
||||||
{"baz"},
|
{expectedTags[2]},
|
||||||
{"foo", "bar"},
|
{expectedTags[0], expectedTags[1]},
|
||||||
{"foo", "baz"},
|
{expectedTags[0], expectedTags[2]},
|
||||||
{"bar", "baz"},
|
{expectedTags[1], expectedTags[2]},
|
||||||
{"foo", "bar", "baz"},
|
{expectedTags[0], expectedTags[1], expectedTags[2]},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
@@ -159,6 +165,9 @@ func TestHostPlugin(t *testing.T) {
|
|||||||
testPluginListHosts(ctx, t, p, region, keyid, secret, tc, expectedTagInstancesMap)
|
testPluginListHosts(ctx, t, p, region, keyid, secret, tc, expectedTagInstancesMap)
|
||||||
// TODO: add OnDeleteSet if it needs to be implemented
|
// TODO: add OnDeleteSet if it needs to be implemented
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finally lets test instance_addresses_only catalog attribute correctly syncs only instance addresses
|
||||||
|
testInstanceAddressesOnlyListHosts(ctx, t, p, tf, region, keyid, secret)
|
||||||
}
|
}
|
||||||
|
|
||||||
func testPluginOnCreateCatalog(ctx context.Context, t *testing.T, p *host.HostPlugin, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) {
|
func testPluginOnCreateCatalog(ctx context.Context, t *testing.T, p *host.HostPlugin, region, accessKeyId, secretAccessKey string, rotate bool) (string, string) {
|
||||||
@@ -444,3 +453,92 @@ func testPluginListHosts(ctx context.Context, t *testing.T, p *host.HostPlugin,
|
|||||||
// Success
|
// Success
|
||||||
t.Logf("testing ListHosts: success (region=%s, tags=%v, expected/actual=(len=%d, ids=%s))", region, tags, len(actualInstances), actualInstances)
|
t.Logf("testing ListHosts: success (region=%s, tags=%v, expected/actual=(len=%d, ids=%s))", region, tags, len(actualInstances), actualInstances)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testInstanceAddressesOnlyListHosts(ctx context.Context, t *testing.T, p *host.HostPlugin, tf *TestTerraformer, region, accessKeyId, secretAccessKey string) {
|
||||||
|
t.Helper()
|
||||||
|
t.Logf("testing testInstanceAddressesOnlyListHosts")
|
||||||
|
|
||||||
|
// Get the instance details from tf output
|
||||||
|
instanceID, err := tf.GetOutputString("multi_address_instance_id")
|
||||||
|
require.NoError(t, err)
|
||||||
|
primaryAddresses, err := tf.GetOutputMap("multi_address_primary_addresses")
|
||||||
|
require.NoError(t, err)
|
||||||
|
secondaryAddresses, err := tf.GetOutputMap("multi_address_secondary_addresses")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
var primary, allAddresses []string
|
||||||
|
for _, addr := range primaryAddresses {
|
||||||
|
primary = append(primary, addr.(string))
|
||||||
|
}
|
||||||
|
allAddresses = append(allAddresses, primary...)
|
||||||
|
for _, addr := range secondaryAddresses {
|
||||||
|
allAddresses = append(allAddresses, addr.(string))
|
||||||
|
}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
instanceAddressesOnly bool
|
||||||
|
expectedIPs []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "all addresses",
|
||||||
|
expectedIPs: allAddresses,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "instance addresses only",
|
||||||
|
instanceAddressesOnly: true,
|
||||||
|
expectedIPs: primary,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
require := require.New(t)
|
||||||
|
|
||||||
|
catalogAttrMap := map[string]any{
|
||||||
|
credential.ConstRegion: region,
|
||||||
|
credential.ConstDisableCredentialRotation: true,
|
||||||
|
}
|
||||||
|
if tc.instanceAddressesOnly {
|
||||||
|
catalogAttrMap[host.ConstInstanceAddressesOnly] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
catalogAttrs, err := structpb.NewStruct(catalogAttrMap)
|
||||||
|
require.NoError(err)
|
||||||
|
setAttrs, err := structpb.NewStruct(map[string]any{
|
||||||
|
host.ConstDescribeInstancesFilters: []any{fmt.Sprintf("instance-id=%s", instanceID)},
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
|
||||||
|
reqPersistedSecrets, err := structpb.NewStruct(map[string]any{
|
||||||
|
credential.ConstAccessKeyId: accessKeyId,
|
||||||
|
credential.ConstSecretAccessKey: secretAccessKey,
|
||||||
|
credential.ConstCredsLastRotatedTime: (time.Time{}).Format(time.RFC3339Nano),
|
||||||
|
})
|
||||||
|
require.NoError(err)
|
||||||
|
request := &pb.ListHostsRequest{
|
||||||
|
Catalog: &hostcatalogs.HostCatalog{
|
||||||
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
||||||
|
Attributes: catalogAttrs,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Sets: []*hostsets.HostSet{{
|
||||||
|
Id: "hostset-0",
|
||||||
|
Attrs: &hostsets.HostSet_Attributes{Attributes: setAttrs},
|
||||||
|
}},
|
||||||
|
Persisted: &pb.HostCatalogPersisted{
|
||||||
|
Secrets: reqPersistedSecrets,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := p.ListHosts(ctx, request)
|
||||||
|
require.NoError(err)
|
||||||
|
require.Len(response.GetHosts(), 1)
|
||||||
|
hostResp := response.GetHosts()[0]
|
||||||
|
|
||||||
|
require.Equal(instanceID, hostResp.ExternalId)
|
||||||
|
require.ElementsMatch(hostResp.IpAddresses, tc.expectedIPs)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ package testing
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
@@ -85,6 +86,12 @@ func TestStoragePlugin(t *testing.T) {
|
|||||||
iamSecretAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_secret_access_key_missing_delete_obj")
|
iamSecretAccessKeyMissingDeleteObject, err := tf.GetOutputString("iam_secret_access_key_missing_delete_obj")
|
||||||
require.NoError(err)
|
require.NoError(err)
|
||||||
|
|
||||||
|
s3PaginationTestObjectCount, err := tf.GetOutput("s3_pagination_test_object_count")
|
||||||
|
require.NoError(err)
|
||||||
|
objectCountFloat, ok := s3PaginationTestObjectCount.(float64)
|
||||||
|
require.True(ok)
|
||||||
|
objectCount := int(objectCountFloat)
|
||||||
|
|
||||||
// Start the workflow now. Set up the storage bucket. Note that this
|
// Start the workflow now. Set up the storage bucket. Note that this
|
||||||
// will cause the state to go out of drift above in the sense that
|
// will cause the state to go out of drift above in the sense that
|
||||||
// the access key ID/secret access key will no longer be valid. We
|
// the access key ID/secret access key will no longer be valid. We
|
||||||
@@ -138,6 +145,11 @@ func TestStoragePlugin(t *testing.T) {
|
|||||||
keyid, secret = iamAccessKeyIds[5].(string), iamSecretAccessKeys[5].(string)
|
keyid, secret = iamAccessKeyIds[5].(string), iamSecretAccessKeys[5].(string)
|
||||||
testPluginObjectMethods(ctx, t, p, bucketName, region, keyid, secret)
|
testPluginObjectMethods(ctx, t, p, bucketName, region, keyid, secret)
|
||||||
|
|
||||||
|
// ********************
|
||||||
|
// * ListObjects
|
||||||
|
// ********************
|
||||||
|
testListObjects(ctx, t, p, objectCount, bucketName, region, keyid, secret)
|
||||||
|
|
||||||
// ********************
|
// ********************
|
||||||
// * Validate Permissions
|
// * Validate Permissions
|
||||||
// ********************
|
// ********************
|
||||||
@@ -514,3 +526,198 @@ func testPluginObjectMethods(ctx context.Context, t *testing.T, p *storage.Stora
|
|||||||
require.NoError(conn.Close())
|
require.NoError(conn.Close())
|
||||||
grpcServer.Stop()
|
grpcServer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// testListObjects verifies top-level and recursive listing by matching the exact keys and types of returned objects.
|
||||||
|
func testListObjects(ctx context.Context, t *testing.T, p *storage.StoragePlugin, objectCount int, bucketName, region, accessKeyId, secretAccessKey string) {
|
||||||
|
t.Helper()
|
||||||
|
t.Logf("testing ListObjects (bucket=%s, region=%s, expectedObjects=%d, creds=%t)",
|
||||||
|
bucketName,
|
||||||
|
region,
|
||||||
|
objectCount,
|
||||||
|
accessKeyId != "" && secretAccessKey != "",
|
||||||
|
)
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
reqAttrs, err := structpb.NewStruct(map[string]any{
|
||||||
|
credential.ConstRegion: region,
|
||||||
|
credential.ConstDisableCredentialRotation: true,
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
reqSecrets, err := structpb.NewStruct(map[string]any{
|
||||||
|
credential.ConstAccessKeyId: accessKeyId,
|
||||||
|
credential.ConstSecretAccessKey: secretAccessKey,
|
||||||
|
})
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
bucket := &storagebuckets.StorageBucket{
|
||||||
|
BucketName: bucketName,
|
||||||
|
Attributes: reqAttrs,
|
||||||
|
Secrets: reqSecrets,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Scenario 1: Non-Paginated Listing
|
||||||
|
t.Run("NonPaginatedListing", func(t *testing.T) {
|
||||||
|
|
||||||
|
// Subtest for the non-recursive (top-level) option
|
||||||
|
t.Run("TopLevel", func(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
t.Logf("testing ListObjects for a small set with recursive=false")
|
||||||
|
req := &pb.ListObjectsRequest{
|
||||||
|
Bucket: bucket,
|
||||||
|
KeyPrefix: "list-objects/",
|
||||||
|
Recursive: false,
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, req)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(resp)
|
||||||
|
|
||||||
|
expectedObjects := []*pb.Object{
|
||||||
|
{Key: "list-objects/file1.txt", IsDir: false},
|
||||||
|
{Key: "list-objects/data.json", IsDir: false},
|
||||||
|
{Key: "list-objects/nested-dir/", IsDir: true},
|
||||||
|
{Key: "list-objects/empty-nested-dir/", IsDir: true},
|
||||||
|
}
|
||||||
|
r.ElementsMatch(resp.Objects, expectedObjects)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Subtest for the recursive option
|
||||||
|
t.Run("Recursive", func(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
t.Logf("testing ListObjects for a small set with recursive=true")
|
||||||
|
req := &pb.ListObjectsRequest{
|
||||||
|
Bucket: bucket,
|
||||||
|
KeyPrefix: "list-objects/",
|
||||||
|
Recursive: true,
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, req)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(resp)
|
||||||
|
|
||||||
|
expectedObjects := []*pb.Object{
|
||||||
|
{Key: "list-objects/file1.txt", IsDir: false},
|
||||||
|
{Key: "list-objects/data.json", IsDir: false},
|
||||||
|
{Key: "list-objects/nested-dir/file2.txt", IsDir: false},
|
||||||
|
{Key: "list-objects/nested-dir/", IsDir: true},
|
||||||
|
{Key: "list-objects/empty-nested-dir/", IsDir: true},
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ElementsMatch(resp.Objects, expectedObjects)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test Scenario 2: Paginated Top-Level Listing
|
||||||
|
t.Run("TopLevelListing", func(t *testing.T) {
|
||||||
|
// Subtest for the root prefix
|
||||||
|
t.Run("AtRoot", func(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
t.Logf("testing ListObjects with recursive=false at root prefix")
|
||||||
|
req := &pb.ListObjectsRequest{
|
||||||
|
Bucket: bucket,
|
||||||
|
KeyPrefix: "list-objects-paginated/",
|
||||||
|
Recursive: false,
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, req)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(resp)
|
||||||
|
|
||||||
|
var expectedObjects []*pb.Object
|
||||||
|
for i := 0; i < objectCount; i++ {
|
||||||
|
key := fmt.Sprintf("list-objects-paginated/root-file-%d.txt", i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: key, IsDir: false})
|
||||||
|
}
|
||||||
|
for i := 0; i < objectCount; i++ {
|
||||||
|
keyEmpty := fmt.Sprintf("list-objects-paginated/empty-dir-%d/", i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: keyEmpty, IsDir: true})
|
||||||
|
keyNested := fmt.Sprintf("list-objects-paginated/nested-dir-%d/", i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNested, IsDir: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ElementsMatch(resp.Objects, expectedObjects)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Subtest for a nested prefix
|
||||||
|
t.Run("AtNestedPrefix", func(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
t.Logf("testing ListObjects with recursive=false at a nested prefix")
|
||||||
|
req := &pb.ListObjectsRequest{
|
||||||
|
Bucket: bucket,
|
||||||
|
KeyPrefix: "list-objects-paginated/nested-dir-7/",
|
||||||
|
Recursive: false,
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, req)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(resp)
|
||||||
|
|
||||||
|
expectedObjects := []*pb.Object{
|
||||||
|
{Key: "list-objects-paginated/nested-dir-7/data-file-7.json", IsDir: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ElementsMatch(resp.Objects, expectedObjects)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test Scenario 3: Paginated Recursive Listing
|
||||||
|
t.Run("RecursiveListing", func(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
t.Logf("testing ListObjects with recursive=true")
|
||||||
|
req := &pb.ListObjectsRequest{
|
||||||
|
Bucket: bucket,
|
||||||
|
KeyPrefix: "list-objects-paginated/",
|
||||||
|
Recursive: true,
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, req)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(resp)
|
||||||
|
|
||||||
|
var expectedObjects []*pb.Object
|
||||||
|
for i := 0; i < objectCount; i++ {
|
||||||
|
keyFile := fmt.Sprintf("list-objects-paginated/root-file-%d.txt", i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: keyFile, IsDir: false})
|
||||||
|
keyNestedFile := fmt.Sprintf("list-objects-paginated/nested-dir-%d/data-file-%d.json", i, i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNestedFile, IsDir: false})
|
||||||
|
}
|
||||||
|
for i := 0; i < objectCount; i++ {
|
||||||
|
keyNestedDir := fmt.Sprintf("list-objects-paginated/nested-dir-%d/", i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNestedDir, IsDir: true})
|
||||||
|
keyEmptyDir := fmt.Sprintf("list-objects-paginated/empty-dir-%d/", i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: keyEmptyDir, IsDir: true})
|
||||||
|
keyNestedEmptyDir := fmt.Sprintf("list-objects-paginated/empty-dir-%d/dir-%d/", i, i)
|
||||||
|
expectedObjects = append(expectedObjects, &pb.Object{Key: keyNestedEmptyDir, IsDir: true})
|
||||||
|
}
|
||||||
|
|
||||||
|
r.ElementsMatch(resp.Objects, expectedObjects)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Test Scenario 4: Edge cases
|
||||||
|
t.Run("EdgeCases", func(t *testing.T) {
|
||||||
|
// Subtest for a prefix that doesn't exist
|
||||||
|
t.Run("NonExistentPrefix", func(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
t.Logf("testing ListObjects for a non-existent prefix")
|
||||||
|
req := &pb.ListObjectsRequest{
|
||||||
|
Bucket: bucket,
|
||||||
|
KeyPrefix: "this/prefix/does/not/exist/",
|
||||||
|
Recursive: false,
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, req)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(resp)
|
||||||
|
r.Empty(resp.Objects)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Subtest for listing inside a known empty directory
|
||||||
|
t.Run("InsideEmptyDirectory", func(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
t.Logf("testing ListObjects inside an empty directory")
|
||||||
|
req := &pb.ListObjectsRequest{
|
||||||
|
Bucket: bucket,
|
||||||
|
KeyPrefix: "list-objects-paginated/empty-dirs/dir-10/",
|
||||||
|
Recursive: false,
|
||||||
|
}
|
||||||
|
resp, err := p.ListObjects(ctx, req)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(resp)
|
||||||
|
r.Empty(resp.Objects)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
Vendored
+21
-14
@@ -1,24 +1,20 @@
|
|||||||
# Copyright IBM Corp. 2021, 2026
|
# Copyright IBM Corp. 2021, 2026
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
variable "iam_user_prefix" {
|
|
||||||
default = "boundary-plugin-host-aws-test-iam-user"
|
|
||||||
}
|
|
||||||
|
|
||||||
variable "iam_user_count" {
|
|
||||||
default = 6
|
|
||||||
}
|
|
||||||
|
|
||||||
resource "random_id" "aws_iam_user_name" {
|
resource "random_id" "aws_iam_user_name" {
|
||||||
count = var.iam_user_count
|
count = var.iam_user_count
|
||||||
prefix = "${var.iam_user_prefix}-${count.index}"
|
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
||||||
byte_length = 4
|
byte_length = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_user" "user" {
|
resource "aws_iam_user" "user" {
|
||||||
count = var.iam_user_count
|
count = var.iam_user_count
|
||||||
name = random_id.aws_iam_user_name[count.index].dec
|
name = random_id.aws_iam_user_name[count.index].dec
|
||||||
force_destroy = true
|
force_destroy = true
|
||||||
|
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
||||||
|
tags = {
|
||||||
|
"boundary-demo" = local.hashicorp_email
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_access_key" "user_initial_key" {
|
resource "aws_iam_access_key" "user_initial_key" {
|
||||||
@@ -26,8 +22,13 @@ resource "aws_iam_access_key" "user_initial_key" {
|
|||||||
user = aws_iam_user.user[count.index].name
|
user = aws_iam_user.user[count.index].name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "random_id" "aws_ec2_policy_name" {
|
||||||
|
prefix = "BoundaryPluginHost"
|
||||||
|
byte_length = 4
|
||||||
|
}
|
||||||
|
|
||||||
resource "aws_iam_policy" "ec2_describeinstances" {
|
resource "aws_iam_policy" "ec2_describeinstances" {
|
||||||
name = "BoundaryPluginHostAwsTestDescribeInstancesPolicy"
|
name = random_id.aws_ec2_policy_name.dec
|
||||||
|
|
||||||
policy = <<EOF
|
policy = <<EOF
|
||||||
{
|
{
|
||||||
@@ -51,9 +52,15 @@ resource "aws_iam_user_policy_attachment" "user_ec2_describeinstances_attachment
|
|||||||
policy_arn = aws_iam_policy.ec2_describeinstances.arn
|
policy_arn = aws_iam_policy.ec2_describeinstances.arn
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resource "random_id" "aws_iam_policy_name" {
|
||||||
|
count = var.iam_user_count
|
||||||
|
prefix = "BoundaryPluginCredentials"
|
||||||
|
byte_length = 4
|
||||||
|
}
|
||||||
|
|
||||||
resource "aws_iam_policy" "user_self_manage_policy" {
|
resource "aws_iam_policy" "user_self_manage_policy" {
|
||||||
count = var.iam_user_count
|
count = var.iam_user_count
|
||||||
name = "BoundaryPluginHostAwsTestUserSelfManagePolicy-${count.index}"
|
name = random_id.aws_iam_policy_name[count.index].dec
|
||||||
|
|
||||||
policy = <<EOF
|
policy = <<EOF
|
||||||
{
|
{
|
||||||
|
|||||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
aws = {
|
aws = {
|
||||||
source = "hashicorp/aws"
|
source = "hashicorp/aws"
|
||||||
version = "~> 3.0"
|
version = "~> 6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+49
@@ -0,0 +1,49 @@
|
|||||||
|
# Copyright IBM Corp. 2021, 2026
|
||||||
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
|
data "aws_caller_identity" "current" {}
|
||||||
|
|
||||||
|
variable "iam_user_count" {
|
||||||
|
default = 6
|
||||||
|
}
|
||||||
|
|
||||||
|
# We are using a shared account for testing so make sure the stale ec2
|
||||||
|
# machine tags don't match for this test run
|
||||||
|
resource "random_id" "foo_key" {
|
||||||
|
prefix = "foo"
|
||||||
|
byte_length = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_id" "bar_key" {
|
||||||
|
prefix = "bar"
|
||||||
|
byte_length = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "random_id" "baz_key" {
|
||||||
|
prefix = "baz"
|
||||||
|
byte_length = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
locals {
|
||||||
|
hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1]
|
||||||
|
|
||||||
|
instance_tags = [
|
||||||
|
{
|
||||||
|
"${random_id.foo_key.dec}" = "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"${random_id.foo_key.dec}" = "true"
|
||||||
|
"${random_id.bar_key.dec}" = "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"${random_id.bar_key.dec}" = "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"${random_id.bar_key.dec}" = "true"
|
||||||
|
"${random_id.baz_key.dec}" = "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"${random_id.baz_key.dec}" = "true"
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
Vendored
+132
-29
@@ -1,28 +1,6 @@
|
|||||||
# Copyright IBM Corp. 2021, 2026
|
# Copyright IBM Corp. 2021, 2026
|
||||||
# SPDX-License-Identifier: MPL-2.0
|
# SPDX-License-Identifier: MPL-2.0
|
||||||
|
|
||||||
variable "instance_tags" {
|
|
||||||
default = [
|
|
||||||
{
|
|
||||||
"foo" = "true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"foo" = "true",
|
|
||||||
"bar" = "true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bar" = "true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"bar" = "true",
|
|
||||||
"baz" = "true",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"baz" = "true",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
data "aws_availability_zones" "azs" {}
|
data "aws_availability_zones" "azs" {}
|
||||||
|
|
||||||
data "aws_ami" "ubuntu" {
|
data "aws_ami" "ubuntu" {
|
||||||
@@ -42,22 +20,117 @@ data "aws_ami" "ubuntu" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_vpc" "vpc" {
|
resource "aws_vpc" "vpc" {
|
||||||
cidr_block = "10.0.0.0/16"
|
cidr_block = "10.0.0.0/16"
|
||||||
|
assign_generated_ipv6_cidr_block = true
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_subnet" "subnet" {
|
resource "aws_subnet" "subnet" {
|
||||||
vpc_id = aws_vpc.vpc.id
|
vpc_id = aws_vpc.vpc.id
|
||||||
cidr_block = aws_vpc.vpc.cidr_block
|
cidr_block = aws_vpc.vpc.cidr_block
|
||||||
availability_zone = data.aws_availability_zones.azs.names[0]
|
ipv6_cidr_block = cidrsubnet(aws_vpc.vpc.ipv6_cidr_block, 8, 0)
|
||||||
|
availability_zone = data.aws_availability_zones.azs.names[0]
|
||||||
|
map_public_ip_on_launch = true
|
||||||
|
assign_ipv6_address_on_creation = true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Need to add a gateway for public IP addr assignments
|
||||||
|
resource "aws_internet_gateway" "igw" {
|
||||||
|
vpc_id = aws_vpc.vpc.id
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_route_table" "public" {
|
||||||
|
vpc_id = aws_vpc.vpc.id
|
||||||
|
|
||||||
|
route {
|
||||||
|
cidr_block = "0.0.0.0/0"
|
||||||
|
gateway_id = aws_internet_gateway.igw.id
|
||||||
|
}
|
||||||
|
|
||||||
|
route {
|
||||||
|
ipv6_cidr_block = "::/0"
|
||||||
|
gateway_id = aws_internet_gateway.igw.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_route_table_association" "public" {
|
||||||
|
subnet_id = aws_subnet.subnet.id
|
||||||
|
route_table_id = aws_route_table.public.id
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_instance" "instances" {
|
resource "aws_instance" "instances" {
|
||||||
count = length(var.instance_tags)
|
count = length(local.instance_tags)
|
||||||
ami = data.aws_ami.ubuntu.id
|
ami = data.aws_ami.ubuntu.id
|
||||||
instance_type = "t3.nano"
|
instance_type = "t3.nano"
|
||||||
subnet_id = aws_subnet.subnet.id
|
subnet_id = aws_subnet.subnet.id
|
||||||
|
|
||||||
tags = var.instance_tags[count.index]
|
tags = local.instance_tags[count.index]
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_instance" "multi_address_instance" {
|
||||||
|
ami = data.aws_ami.ubuntu.id
|
||||||
|
instance_type = "t3.nano"
|
||||||
|
subnet_id = aws_subnet.subnet.id
|
||||||
|
secondary_private_ips = ["10.0.10.10"]
|
||||||
|
ipv6_address_count = 1
|
||||||
|
associate_public_ip_address = true
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Name = "boundary-multi-address-instance"
|
||||||
|
"boundary-demo" = local.hashicorp_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_network_interface" "multi_address_secondary_eni" {
|
||||||
|
subnet_id = aws_subnet.subnet.id
|
||||||
|
private_ips = ["10.0.20.10"]
|
||||||
|
ipv6_addresses = [cidrhost(aws_subnet.subnet.ipv6_cidr_block, 32)]
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Name = "boundary-multi-address-secondary-eni"
|
||||||
|
"boundary-demo" = local.hashicorp_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_network_interface_attachment" "multi_address_secondary_eni" {
|
||||||
|
instance_id = aws_instance.multi_address_instance.id
|
||||||
|
network_interface_id = aws_network_interface.multi_address_secondary_eni.id
|
||||||
|
device_index = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_eip" "multi_address_primary" {
|
||||||
|
domain = "vpc"
|
||||||
|
network_interface = aws_instance.multi_address_instance.primary_network_interface_id
|
||||||
|
associate_with_private_ip = aws_instance.multi_address_instance.private_ip
|
||||||
|
depends_on = [aws_internet_gateway.igw, aws_route_table_association.public]
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Name = "boundary-multi-address-primary-eip"
|
||||||
|
"boundary-demo" = local.hashicorp_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_eip" "multi_address_primary_secondary_private" {
|
||||||
|
domain = "vpc"
|
||||||
|
network_interface = aws_instance.multi_address_instance.primary_network_interface_id
|
||||||
|
associate_with_private_ip = "10.0.10.10"
|
||||||
|
depends_on = [aws_internet_gateway.igw, aws_route_table_association.public, aws_instance.multi_address_instance]
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Name = "boundary-multi-address-primary-secondary-eip"
|
||||||
|
"boundary-demo" = local.hashicorp_email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
resource "aws_eip" "multi_address_secondary_eni" {
|
||||||
|
domain = "vpc"
|
||||||
|
network_interface = aws_network_interface.multi_address_secondary_eni.id
|
||||||
|
associate_with_private_ip = "10.0.20.10"
|
||||||
|
depends_on = [aws_internet_gateway.igw, aws_route_table_association.public, aws_network_interface_attachment.multi_address_secondary_eni]
|
||||||
|
|
||||||
|
tags = {
|
||||||
|
Name = "boundary-multi-address-secondary-eni-eip"
|
||||||
|
"boundary-demo" = local.hashicorp_email
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
output "instance_ids" {
|
output "instance_ids" {
|
||||||
@@ -74,4 +147,34 @@ output "instance_tags" {
|
|||||||
value = {
|
value = {
|
||||||
for _, r in aws_instance.instances : r.id => r.tags
|
for _, r in aws_instance.instances : r.id => r.tags
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "instance_tag_keys" {
|
||||||
|
value = [
|
||||||
|
random_id.foo_key.dec,
|
||||||
|
random_id.bar_key.dec,
|
||||||
|
random_id.baz_key.dec,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
output "multi_address_instance_id" {
|
||||||
|
value = aws_instance.multi_address_instance.id
|
||||||
|
}
|
||||||
|
|
||||||
|
output "multi_address_primary_addresses" {
|
||||||
|
value = {
|
||||||
|
private_ip = aws_instance.multi_address_instance.private_ip
|
||||||
|
public_ip = aws_eip.multi_address_primary.public_ip
|
||||||
|
ipv6 = tolist(aws_instance.multi_address_instance.ipv6_addresses)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
output "multi_address_secondary_addresses" {
|
||||||
|
value = {
|
||||||
|
primary_eni_private_ip = "10.0.10.10"
|
||||||
|
primary_eni_public_ip = aws_eip.multi_address_primary_secondary_private.public_ip
|
||||||
|
secondary_eni_private_ip = aws_network_interface.multi_address_secondary_eni.private_ip
|
||||||
|
secondary_eni_public_ip = aws_eip.multi_address_secondary_eni.public_ip
|
||||||
|
secondary_eni_ipv6 = tolist(aws_network_interface.multi_address_secondary_eni.ipv6_addresses)[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+22
-20
@@ -34,10 +34,18 @@ resource "aws_iam_policy" "valid" {
|
|||||||
"s3:GetObject",
|
"s3:GetObject",
|
||||||
"s3:DeleteObject",
|
"s3:DeleteObject",
|
||||||
"s3:GetObjectAttributes",
|
"s3:GetObjectAttributes",
|
||||||
"s3:ListBucket"
|
"s3:ListBucket",
|
||||||
|
"s3:DeleteObject"
|
||||||
],
|
],
|
||||||
"Effect": "Allow",
|
"Effect": "Allow",
|
||||||
"Resource": "${aws_s3_bucket.test.arn}*"
|
"Resource": "${aws_s3_bucket.test.arn}*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Action": [
|
||||||
|
"s3:ListBucket"
|
||||||
|
],
|
||||||
|
"Effect": "Allow",
|
||||||
|
"Resource": "${aws_s3_bucket.test.arn}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -196,7 +204,7 @@ resource "aws_iam_role_policy_attachment" "missing_delete_obj" {
|
|||||||
# Static Credential Testing
|
# Static Credential Testing
|
||||||
resource "random_id" "user_name" {
|
resource "random_id" "user_name" {
|
||||||
count = var.iam_user_count
|
count = var.iam_user_count
|
||||||
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
prefix = "demo-${local.hashicorp_email}" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
|
||||||
byte_length = 4
|
byte_length = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -204,10 +212,8 @@ resource "aws_iam_user" "test" {
|
|||||||
count = var.iam_user_count
|
count = var.iam_user_count
|
||||||
name = random_id.user_name[count.index].dec
|
name = random_id.user_name[count.index].dec
|
||||||
force_destroy = true
|
force_destroy = true
|
||||||
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
|
||||||
tags = {
|
tags = local.tags
|
||||||
"boundary-demo" = local.hashicorp_email
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_access_key" "test" {
|
resource "aws_iam_access_key" "test" {
|
||||||
@@ -277,17 +283,15 @@ resource "aws_iam_user_policy_attachment" "missing_delete_obj" {
|
|||||||
# Edge case for missing GetObject permission
|
# Edge case for missing GetObject permission
|
||||||
|
|
||||||
resource "random_id" "missing_get_obj" {
|
resource "random_id" "missing_get_obj" {
|
||||||
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
prefix = "demo-${local.hashicorp_email}" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
|
||||||
byte_length = 4
|
byte_length = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_user" "missing_get_obj" {
|
resource "aws_iam_user" "missing_get_obj" {
|
||||||
name = random_id.missing_get_obj.dec
|
name = random_id.missing_get_obj.dec
|
||||||
force_destroy = true
|
force_destroy = true
|
||||||
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
|
||||||
tags = {
|
tags = local.tags
|
||||||
"boundary-demo" = local.hashicorp_email
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_access_key" "missing_get_obj" {
|
resource "aws_iam_access_key" "missing_get_obj" {
|
||||||
@@ -302,17 +306,15 @@ resource "aws_iam_user_policy_attachment" "missing_get_obj" {
|
|||||||
# Edge case for missing PutObject permission
|
# Edge case for missing PutObject permission
|
||||||
|
|
||||||
resource "random_id" "missing_put_obj" {
|
resource "random_id" "missing_put_obj" {
|
||||||
prefix = "demo-${local.hashicorp_email}-boundary-iam-user"
|
prefix = "demo-${local.hashicorp_email}" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
|
||||||
byte_length = 4
|
byte_length = 4
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_user" "missing_put_obj" {
|
resource "aws_iam_user" "missing_put_obj" {
|
||||||
name = random_id.missing_put_obj.dec
|
name = random_id.missing_put_obj.dec
|
||||||
force_destroy = true
|
force_destroy = true
|
||||||
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser"
|
permissions_boundary = "arn:aws:iam::${data.aws_caller_identity.current.account_id}:policy/DemoUser" # Do Not Remove/Edit. This is a requirement to obtain access in creating a iam user in the dev account.
|
||||||
tags = {
|
tags = local.tags
|
||||||
"boundary-demo" = local.hashicorp_email
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
resource "aws_iam_access_key" "missing_put_obj" {
|
resource "aws_iam_access_key" "missing_put_obj" {
|
||||||
|
|||||||
Vendored
+1
-1
@@ -5,7 +5,7 @@ terraform {
|
|||||||
required_providers {
|
required_providers {
|
||||||
aws = {
|
aws = {
|
||||||
source = "hashicorp/aws"
|
source = "hashicorp/aws"
|
||||||
version = "~> 3.0"
|
version = "~> 6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vendored
+10
-5
@@ -6,19 +6,19 @@ output "bucket_name" {
|
|||||||
}
|
}
|
||||||
|
|
||||||
output "valid_policy_arn" {
|
output "valid_policy_arn" {
|
||||||
value = aws_iam_role.valid.arn
|
value = aws_iam_role.valid.arn
|
||||||
}
|
}
|
||||||
|
|
||||||
output "missing_put_obj_policy_arn" {
|
output "missing_put_obj_policy_arn" {
|
||||||
value = aws_iam_role.missing_put_obj.arn
|
value = aws_iam_role.missing_put_obj.arn
|
||||||
}
|
}
|
||||||
|
|
||||||
output "missing_get_obj_policy_arn" {
|
output "missing_get_obj_policy_arn" {
|
||||||
value = aws_iam_role.missing_get_obj.arn
|
value = aws_iam_role.missing_get_obj.arn
|
||||||
}
|
}
|
||||||
|
|
||||||
output "missing_delete_obj_policy_arn" {
|
output "missing_delete_obj_policy_arn" {
|
||||||
value = aws_iam_role.missing_delete_obj.arn
|
value = aws_iam_role.missing_delete_obj.arn
|
||||||
}
|
}
|
||||||
|
|
||||||
output "iam_user_names" {
|
output "iam_user_names" {
|
||||||
@@ -67,4 +67,9 @@ output "iam_access_key_missing_delete_obj" {
|
|||||||
output "iam_secret_access_key_missing_delete_obj" {
|
output "iam_secret_access_key_missing_delete_obj" {
|
||||||
value = aws_iam_access_key.missing_delete_obj.secret
|
value = aws_iam_access_key.missing_delete_obj.secret
|
||||||
sensitive = true
|
sensitive = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
output "s3_pagination_test_object_count" {
|
||||||
|
description = "The number of S3 objects created for each pagination test scenario."
|
||||||
|
value = local.s3_pagination_test_object_count
|
||||||
|
}
|
||||||
Vendored
+10
-2
@@ -9,9 +9,17 @@ resource "aws_s3_bucket" "test" {
|
|||||||
|
|
||||||
resource "aws_s3_bucket_public_access_block" "secure_access" {
|
resource "aws_s3_bucket_public_access_block" "secure_access" {
|
||||||
bucket = aws_s3_bucket.test.id
|
bucket = aws_s3_bucket.test.id
|
||||||
|
|
||||||
block_public_acls = true
|
block_public_acls = true
|
||||||
block_public_policy = true
|
block_public_policy = true
|
||||||
ignore_public_acls = true
|
ignore_public_acls = true
|
||||||
restrict_public_buckets = true
|
restrict_public_buckets = true
|
||||||
}
|
}
|
||||||
|
resource "aws_s3_object" "s3_files" {
|
||||||
|
for_each = local.s3_files
|
||||||
|
|
||||||
|
bucket = aws_s3_bucket.test.id
|
||||||
|
key = each.key
|
||||||
|
content = each.value
|
||||||
|
tags = local.tags
|
||||||
|
}
|
||||||
Vendored
+41
-1
@@ -16,7 +16,47 @@ locals {
|
|||||||
hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1]
|
hashicorp_email = split(":", data.aws_caller_identity.current.user_id)[1]
|
||||||
tags = {
|
tags = {
|
||||||
User = split(":", data.aws_caller_identity.current.user_id)[1]
|
User = split(":", data.aws_caller_identity.current.user_id)[1]
|
||||||
Repo = "boundary-plugin-aws"
|
Repo = "boundary-plugin-aws"
|
||||||
Environment = "Demo"
|
Environment = "Demo"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# total number of objects of each type to create
|
||||||
|
s3_pagination_test_object_count = 350
|
||||||
|
# Dynamically build a flat map of S3 objects:
|
||||||
|
# - root-level text files
|
||||||
|
# - nested JSON files
|
||||||
|
# - placeholder "empty directories"
|
||||||
|
# - some static files in list-objects directory
|
||||||
|
# This map is consumed directly by an aws_s3_object resource
|
||||||
|
s3_files = merge(
|
||||||
|
# Root-level text files
|
||||||
|
{
|
||||||
|
for i in range(local.s3_pagination_test_object_count) :
|
||||||
|
"list-objects-paginated/root-file-${i}.txt" => "This is a root-level file #${i}."
|
||||||
|
},
|
||||||
|
|
||||||
|
# Nested JSON files inside sub-directories
|
||||||
|
{
|
||||||
|
for i in range(local.s3_pagination_test_object_count) :
|
||||||
|
"list-objects-paginated/nested-dir-${i}/data-file-${i}.json" =>
|
||||||
|
jsonencode({
|
||||||
|
id = i,
|
||||||
|
message = "This is a nested JSON file #${i}."
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
|
# "Empty" directories (in S3 these are just placeholder objects ending with `/`)
|
||||||
|
{
|
||||||
|
for i in range(local.s3_pagination_test_object_count) :
|
||||||
|
"list-objects-paginated/empty-dir-${i}/dir-${i}/" => "" # zero-length object to mimic directory
|
||||||
|
},
|
||||||
|
|
||||||
|
# Static example files in list-objects directory
|
||||||
|
{
|
||||||
|
"list-objects/file1.txt" = "This is the content for file 1."
|
||||||
|
"list-objects/data.json" = jsonencode({ key = "value", items = [1, 2, 3] })
|
||||||
|
"list-objects/nested-dir/file2.txt" = "This is a nested file."
|
||||||
|
"list-objects/empty-nested-dir/" = ""
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user