From 050357f367bfdaa70d515d5fb82c39c8f0a6597f Mon Sep 17 00:00:00 2001 From: dani <29378233+kheina@users.noreply.github.com> Date: Fri, 15 Dec 2023 19:12:35 +0100 Subject: [PATCH] add DeleteObjects command to plugin (#38) --- go.mod | 22 +- go.sum | 95 +++-- plugin/service/storage/plugin.go | 142 +++++++- plugin/service/storage/plugin_test.go | 500 ++++++++++++++++++++++++++ plugin/service/storage/state.go | 2 + plugin/service/storage/testing.go | 119 +++++- 6 files changed, 830 insertions(+), 50 deletions(-) diff --git a/go.mod b/go.mod index f5c074f..8311ce6 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/hashicorp/boundary-plugin-aws -go 1.20 +go 1.21 require ( github.com/aws/aws-sdk-go v1.44.80 @@ -11,14 +11,14 @@ require ( github.com/aws/aws-sdk-go-v2/service/sts v1.21.2 github.com/aws/smithy-go v1.14.1 github.com/google/go-cmp v0.5.9 - github.com/google/uuid v1.3.0 - github.com/hashicorp/boundary/sdk v0.0.33 + github.com/google/uuid v1.3.1 + github.com/hashicorp/boundary/sdk v0.0.41-0.20231117205527-08045ddb050e github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0 github.com/hashicorp/terraform-json v0.14.0 github.com/mitchellh/mapstructure v1.5.0 github.com/stretchr/testify v1.8.4 - google.golang.org/grpc v1.56.3 - google.golang.org/protobuf v1.30.0 + google.golang.org/grpc v1.59.0 + google.golang.org/protobuf v1.31.0 ) require ( @@ -37,20 +37,20 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.13.2 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.15.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.14.1 // indirect + github.com/fatih/color v1.15.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect - github.com/hashicorp/eventlogger v0.1.2-0.20230428153751-cca445805f24 // indirect - github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20230428153751-cca445805f24 // indirect + github.com/hashicorp/eventlogger v0.2.6-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-hclog v1.5.0 // indirect - github.com/hashicorp/go-kms-wrapping/v2 v2.0.9 // indirect + github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.5.0 // indirect github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/pointerstructure v1.2.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -60,6 +60,6 @@ require ( golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.13.0 // indirect golang.org/x/text v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 76bbe45..82240fa 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,13 @@ github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= 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/aws/aws-sdk-go v1.44.80 h1:jEXGecSgPdvM5KnyDsSgFhZSm7WwaTp4h544Im4SfhI= github.com/aws/aws-sdk-go v1.44.80/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= github.com/aws/aws-sdk-go-v2 v1.18.0/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= @@ -52,13 +56,14 @@ github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J github.com/aws/smithy-go v1.14.1 h1:EFKMUmH/iHMqLiwoEDx2rRjRQpI1YCn5jTysoaDujFs= github.com/aws/smithy-go v1.14.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= +github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= +github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= github.com/go-test/deep v1.0.4 h1:u2CU3YKy9I2pmu9pX0eq50wCgjfGIt539SqR7FbHiho= github.com/go-test/deep v1.0.4/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -73,59 +78,76 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/hashicorp/boundary/sdk v0.0.33 h1:kHaVZ3b9j1QvYlG+vhAgrbXh8/+Rq2qlUIs8fKMQH7k= -github.com/hashicorp/boundary/sdk v0.0.33/go.mod h1:jn9j5mM8v2pOk8aLeJNIszdm7WBf4gRSP68iy8Iu5Q0= +github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= +github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/boundary/sdk v0.0.41-0.20231117205527-08045ddb050e h1:PzZklmCjnaqQq9JdZRI9SVPSd9xo0iXLlmOHoQX68aM= +github.com/hashicorp/boundary/sdk v0.0.41-0.20231117205527-08045ddb050e/go.mod h1:+XTDYf9YNeKIbGOPJwy7hlO2Le4zgzCtHCG/u+z4THI= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/eventlogger v0.1.2-0.20230428153751-cca445805f24 h1:vtCJgcxwDMFrpUgVMfjtwXY1YqvHbj8pNA7GsXYEoLg= -github.com/hashicorp/eventlogger v0.1.2-0.20230428153751-cca445805f24/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM= -github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20230428153751-cca445805f24 h1:mopKPTxSaVr24uSysyRwywTDs85L5b05qqZvEkWa0qc= -github.com/hashicorp/eventlogger/filters/encrypt v0.1.8-0.20230428153751-cca445805f24/go.mod h1:EQPLoX6CONA9BSYUovTQBHfPGE91g7wOxv03sO29FzY= +github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0 h1:f9oX8/3zxiQrfrWnBeyjDm4S02GAU02OBtCRoZOUwlo= +github.com/hashicorp/eventlogger v0.2.6-0.20231025104552-802587e608f0/go.mod h1://CHt6/j+Q2lc0NlUB5af4aS2M0c0aVBg9/JfcpAyhM= +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/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-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.4 h1:8z+Zi0uLYXONkxwWb/D7O5t1IUucB6Ft3uvSOa950bM= -github.com/hashicorp/go-kms-wrapping/v2 v2.0.9 h1:JpCvi97NMA+saNqO8ovQcGoRbBq6P5ZZlJqvOsW5ick= -github.com/hashicorp/go-kms-wrapping/v2 v2.0.9/go.mod h1:NtMaPhqSlfQ72XWDD2g80o8HI8RKkowIB8/WZHMyPY4= +github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5 h1:jrnDfQm2hCQ0/hEselgqzV4fK16gpZoY0OWGZpVPNHM= +github.com/hashicorp/go-kms-wrapping/plugin/v2 v2.0.5/go.mod h1:psh1qKep5ukvuNobFY/hCybuudlkkACpmazOsCgX5Rg= +github.com/hashicorp/go-kms-wrapping/v2 v2.0.14 h1:1ZuhfnZgRnLK8S0KovJkoTCRIQId5pv3sDR7pG5VQBw= +github.com/hashicorp/go-kms-wrapping/v2 v2.0.14/go.mod h1:0dWtzl2ilqKpavgM3id/kFK9L3tjo6fS4OhbVPSYpnQ= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.9 h1:ESiK220/qE0aGxWdzKIvRH69iLiuN/PjoLTm69RoWtU= +github.com/hashicorp/go-plugin v1.5.2 h1:aWv8eimFqWlsEiMrYZdPYl+FdHaBJSN4AWwGWfT1G2Y= +github.com/hashicorp/go-plugin v1.5.2/go.mod h1:w1sAEES3g3PuV/RzUrgow20W2uErMly84hhD3um1WL4= github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0 h1:ca5TSI4AgaOncPpyzLDtCGjVEtKukONpeM95vFxXCOQ= github.com/hashicorp/go-secure-stdlib/awsutil/v2 v2.0.0/go.mod h1:7CUvZtfTp2U0CYQCLzMtS2ngckjAZePSfwrE2aeDP1M= 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/configutil/v2 v2.0.9 h1:YZ01/aiARPneDUryzjiPGvr3pmjCSC+1WBZKI1P7I7k= -github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.6 h1:rOmnqOSUBn5opme1Kh9OXO8qWd0KztO+f02vvCwCc/w= -github.com/hashicorp/go-secure-stdlib/parseutil v0.1.7 h1:UpiO20jno/eV1eVZcxqWnUohyKRe1g8FPV/xH1s/2qs= -github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.4 h1:IU2iGcvthrJ53rPbQU6B4+iVs/cLPO89ti5dd2XEj3k= +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.11/go.mod h1:uis9dCmOzXuOaRyXq+1Foh31kcvXKoWogjNnhfjHfW8= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9 h1:0S0ctJ7Ra8O7ap+/3fZUnzJ3VzJyirWS/WnNCuOYtZY= +github.com/hashicorp/go-secure-stdlib/listenerutil v0.1.9/go.mod h1:TNNdgtjLgVDbrgFcyCKrlAicIl3dZF94swJltyGUX2M= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 h1:iBt4Ew4XEGLfh6/bPk4rSYmuZJGizr6/x/AEizP0CQc= +github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8/go.mod h1:aiJI+PIApBRQG7FZTEBx5GiiX+HbOHilUdNxUZi4eV0= +github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6 h1:ZYv2XA+tEfFXIToR2jmBgVqQU9gERt0APbWqmUoNGnY= +github.com/hashicorp/go-secure-stdlib/pluginutil/v2 v2.0.6/go.mod h1:ggFN8dlaLWS2R1gymBbCrvXM/bkZP7hEAa4seqDwhyg= 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/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/tlsutil v0.1.2 h1:phcbL8urUzF/kxA/Oj6awENaRwfWsjP59GW7u2qlDyY= -github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +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/go.mod h1:LWq2Sy8UoKKuK4lFuCNWSjJj57MhNNf2zzBWMtkAIX4= +github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU= +github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI= 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/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.5.0 h1:O293SZ2Eg+AAYijkVK3jR786Am1bhDEh2GHT0tIVE5E= github.com/hashicorp/go-version v1.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= 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/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= 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/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +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/go.mod h1:3J2qVK16Lq8V+wfiL2lPeDZ7UWMxk5LemerHa1p6N00= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 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= @@ -138,12 +160,14 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/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/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= @@ -152,17 +176,21 @@ github.com/mitchellh/pointerstructure v1.2.1/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8oh github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/rogpeppe/go-internal v1.6.2 h1:aIihoIOHCiLZHxyoNQ+ABL4NKhFTgKLBdMLyEAh98m0= github.com/rogpeppe/go-internal v1.6.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/ryanuber/go-glob v1.0.0 h1:iQh3xXAumdQ+4Ufa5b25cRpC5TYKlno6hsv6Cb3pkBk= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= @@ -178,8 +206,9 @@ github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q github.com/zclconf/go-cty v1.10.0 h1:mp9ZXQeIcN8kAwuqorjH+Q+njbJKjLrvB2yIh4q7U+0= github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= -go.uber.org/goleak v1.0.0 h1:qsup4IcBdlmsnGfqyLl4Ntn3C2XCCuKAE7DwHpScyUo= go.uber.org/goleak v1.0.0/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA= +go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -187,7 +216,6 @@ golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 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/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= 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= @@ -215,6 +243,7 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -231,25 +260,25 @@ golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtn 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/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= 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= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b h1:ZlWIi1wSK56/8hn4QcBp/j9M7Gt3U/3hZw3mC7vDICo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b/go.mod h1:swOH3j0KzcDDgGUWr+SNpyTen5YrXjS3eyPzFYKc6lc= +google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk= +google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 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.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= diff --git a/plugin/service/storage/plugin.go b/plugin/service/storage/plugin.go index 8e6a8c8..c258348 100644 --- a/plugin/service/storage/plugin.go +++ b/plugin/service/storage/plugin.go @@ -12,6 +12,7 @@ import ( "io" "os" "path" + "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/aws/retry" @@ -622,6 +623,129 @@ func (p *StoragePlugin) PutObject(ctx context.Context, req *pb.PutObjectRequest) }, nil } +// DeleteObjects is used to delete one or many objects from an s3 bucket. +func (p *StoragePlugin) DeleteObjects(ctx context.Context, req *pb.DeleteObjectsRequest) (*pb.DeleteObjectsResponse, error) { + if req.GetKeyPrefix() == "" { + return nil, status.Error(codes.InvalidArgument, "key prefix is required") + } + + bucket := req.GetBucket() + if bucket == nil { + return nil, status.Error(codes.InvalidArgument, "bucket is required") + } + + if bucket.GetBucketName() == "" { + return nil, status.Error(codes.InvalidArgument, "bucketName is required") + } + + attrs := bucket.GetAttributes() + if attrs == nil { + return nil, status.Error(codes.InvalidArgument, "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, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err) + } + + storageState, err := newAwsStoragePersistedState( + append([]awsStoragePersistedStateOption{ + withCredentials(credState), + }, p.testStorageStateOpts...)..., + ) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "error setting up persisted state: %s", err) + } + + opts := []s3Option{} + if storageAttributes.EndpointUrl != "" { + opts = append(opts, WithEndpoint(storageAttributes.EndpointUrl)) + } + client, err := storageState.S3Client(ctx, opts...) + if err != nil { + return nil, status.Errorf(codes.InvalidArgument, "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 += "/" + } + + if !req.Recursive { + _, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{ + Bucket: aws.String(bucket.GetBucketName()), + Key: aws.String(prefix), + }) + if err != nil { + return nil, parseAWSErrCode("error deleting S3 object", err) + } + return &pb.DeleteObjectsResponse{ + ObjectsDeleted: uint32(1), + }, nil + } + + const maxkeys = 1000 + + objects := []types.ObjectIdentifier{} + var conToken *string + truncated := true + for truncated { + res, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: aws.String(bucket.GetBucketName()), + Prefix: aws.String(prefix), + MaxKeys: maxkeys, + ContinuationToken: conToken, + }) + if err != nil { + return nil, parseAWSErrCode("error iterating S3 bucket contents", err) + } + truncated = res.IsTruncated + conToken = res.NextContinuationToken + for _, o := range res.Contents { + objects = append(objects, types.ObjectIdentifier{ + Key: o.Key, + }) + } + } + + deleted := 0 + + for i := 0; i < len(objects); i += maxkeys { + toDelete := objects[i:min(len(objects), i+maxkeys)] // min is required to avoid an out of bounds panic + res, err := client.DeleteObjects(ctx, &s3.DeleteObjectsInput{ + Bucket: aws.String(bucket.GetBucketName()), + Delete: &types.Delete{ + Objects: toDelete, + }, + }) + if err != nil { + return nil, parseAWSErrCode("error deleting S3 object(s)", err) + } + deleted += len(res.Deleted) + } + + return &pb.DeleteObjectsResponse{ + ObjectsDeleted: uint32(deleted), + }, nil +} + // 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 // as expected. This function will create an object in the s3 bucket to validate it has @@ -672,13 +796,21 @@ func dryRunValidation(ctx context.Context, state *awsStoragePersistedState, attr return parseAWSErrCode("error failed to get head object", err) } - // attempt to delete the test object created for the dry run validation, - // this step is allowed to fail because DeleteObject is not a required - // operation for the plugin. - client.DeleteObject(ctx, &s3.DeleteObjectInput{ + if res, err := client.ListObjectsV2(ctx, &s3.ListObjectsV2Input{ + Bucket: aws.String(bucket.GetBucketName()), + Prefix: aws.String(objectKey), + }); err != nil { + return parseAWSErrCode("error failed to list objects", err) + } else if res == nil || len(res.Contents) != 1 || *res.Contents[0].Key != objectKey { + return status.Errorf(codes.InvalidArgument, "list response did not contain the expected key: %+v", res) + } + + if _, err := client.DeleteObject(ctx, &s3.DeleteObjectInput{ Bucket: aws.String(bucket.GetBucketName()), Key: aws.String(objectKey), - }) + }); err != nil { + return parseAWSErrCode("error failed to delete object", err) + } return nil } diff --git a/plugin/service/storage/plugin_test.go b/plugin/service/storage/plugin_test.go index 7d47fa2..483920d 100644 --- a/plugin/service/storage/plugin_test.go +++ b/plugin/service/storage/plugin_test.go @@ -20,6 +20,7 @@ import ( "github.com/aws/aws-sdk-go-v2/service/iam" iamTypes "github.com/aws/aws-sdk-go-v2/service/iam/types" "github.com/aws/aws-sdk-go-v2/service/s3" + s3types "github.com/aws/aws-sdk-go-v2/service/s3/types" "github.com/aws/aws-sdk-go-v2/service/sts" "github.com/aws/aws-sdk-go-v2/service/sts/types" "github.com/hashicorp/boundary-plugin-aws/internal/credential" @@ -263,6 +264,15 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -320,6 +330,15 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -361,6 +380,15 @@ func TestStoragePlugin_OnCreateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -670,6 +698,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -879,6 +916,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -899,6 +945,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -977,6 +1032,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -1064,6 +1128,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -1107,6 +1180,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -1167,6 +1249,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -1211,6 +1302,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectError(fmt.Errorf("put object fail oops")), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -1256,6 +1356,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -1302,6 +1411,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -1396,6 +1514,15 @@ func TestStoragePlugin_OnUpdateStorageBucket(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -2039,6 +2166,15 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -2065,6 +2201,15 @@ func TestStoragePlugin_ValidatePermissions(t *testing.T) { testMockS3WithPutObjectOutput(&s3.PutObjectOutput{}), testMockS3WithGetObjectOutput(&s3.GetObjectOutput{}), testMockS3WithHeadObjectOutput(&s3.HeadObjectOutput{}), + testMockS3WithListObjectsV2OutputFunc(func(i *s3.ListObjectsV2Input) *s3.ListObjectsV2Output { + return &s3.ListObjectsV2Output{ + Contents: []s3types.Object{ + { + Key: i.Prefix, + }, + }, + } + }), ), ), }, @@ -2735,3 +2880,358 @@ func TestStoragePlugin_PutObject(t *testing.T) { }) } } + +func TestStoragePlugin_DeleteObjects(t *testing.T) { + validRequest := func() *pb.DeleteObjectsRequest { + return &pb.DeleteObjectsRequest{ + Bucket: &storagebuckets.StorageBucket{ + BucketName: "external-obj-store", + Secrets: credential.MockStaticCredentialSecrets(), + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + credential.ConstRegion: structpb.NewStringValue("us-west-2"), + }, + }, + }, + KeyPrefix: "abc/", + } + } + + validRecursiveRequest := func() *pb.DeleteObjectsRequest { + return &pb.DeleteObjectsRequest{ + Bucket: &storagebuckets.StorageBucket{ + BucketName: "external-obj-store", + Secrets: credential.MockStaticCredentialSecrets(), + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + credential.ConstRegion: structpb.NewStringValue("us-west-2"), + }, + }, + }, + KeyPrefix: "abc/", + Recursive: true, + } + } + + cases := []struct { + name string + req *pb.DeleteObjectsRequest + credOpts []credential.AwsCredentialPersistedStateOption + storageOpts []awsStoragePersistedStateOption + expectedErrContains string + expectedErrCode codes.Code + expected uint32 + }{ + { + name: "empty key prefix", + req: &pb.DeleteObjectsRequest{}, + expectedErrContains: "key prefix is required", + expectedErrCode: codes.InvalidArgument, + }, + { + name: "nil bucket", + req: &pb.DeleteObjectsRequest{ + KeyPrefix: "/foo/bar/key", + }, + expectedErrContains: "bucket is required", + expectedErrCode: codes.InvalidArgument, + }, + { + name: "missing bucket name", + req: &pb.DeleteObjectsRequest{ + KeyPrefix: "/foo/bar/key", + Bucket: &storagebuckets.StorageBucket{}, + }, + expectedErrContains: "bucketName is required", + expectedErrCode: codes.InvalidArgument, + }, + { + name: "nil attributes", + req: &pb.DeleteObjectsRequest{ + KeyPrefix: "/foo/bar/key", + Bucket: &storagebuckets.StorageBucket{ + BucketName: "foo", + Secrets: new(structpb.Struct), + }, + }, + expectedErrContains: "attributes is required", + expectedErrCode: codes.InvalidArgument, + }, + { + name: "error reading attributes", + req: &pb.DeleteObjectsRequest{ + KeyPrefix: "/foo/bar/key", + Bucket: &storagebuckets.StorageBucket{ + BucketName: "foo", + Secrets: new(structpb.Struct), + Attributes: new(structpb.Struct), + }, + }, + expectedErrContains: "missing required value \"region\"", + expectedErrCode: codes.InvalidArgument, + }, + { + name: "invalid region", + req: &pb.DeleteObjectsRequest{ + KeyPrefix: "/foo/bar/key", + Bucket: &storagebuckets.StorageBucket{ + BucketName: "foo", + Secrets: new(structpb.Struct), + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + credential.ConstRegion: structpb.NewStringValue("foobar"), + }, + }, + }, + }, + expectedErrContains: "not a valid region: foobar", + 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: "DeleteObject error", + req: validRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithDeleteObjectError(errors.New(testDeleteObjectErr)), + ), + ), + }, + expectedErrContains: "error deleting S3 object: test error for DeleteObject", + expectedErrCode: codes.Internal, + }, + { + name: "ListObjectV2 error", + req: validRecursiveRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithListObjectsV2Error(errors.New(testListObjectV2Err)), + ), + ), + }, + expectedErrContains: "error iterating S3 bucket contents: test error for ListObjectV2", + expectedErrCode: codes.Internal, + }, + { + name: "DeleteObjects error", + req: validRecursiveRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ + IsTruncated: false, + Contents: []s3types.Object{ + s3types.Object{ + Key: aws.String("abc/abc"), + }, + }, + }), + testMockS3WithDeleteObjectsError(errors.New(testDeleteObjectsErr)), + ), + ), + }, + expectedErrContains: "error deleting S3 object(s): test error for DeleteObjects", + expectedErrCode: codes.Internal, + }, + { + name: "DeleteObject throttle error", + req: validRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithDeleteObjectError(new(throttleErr)), + ), + ), + }, + expectedErrContains: "error deleting S3 object: ThrottlingException", + expectedErrCode: codes.Unavailable, + }, + { + name: "ListObjectsV2 throttle error", + req: validRecursiveRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithListObjectsV2Error(new(throttleErr)), + ), + ), + }, + expectedErrContains: "error iterating S3 bucket contents: ThrottlingException", + expectedErrCode: codes.Unavailable, + }, + { + name: "DeleteObjects throttle error", + req: validRecursiveRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ + IsTruncated: false, + Contents: []s3types.Object{ + s3types.Object{ + Key: aws.String("abc/abc"), + }, + }, + }), + testMockS3WithDeleteObjectsError(new(throttleErr)), + ), + ), + }, + expectedErrContains: "error deleting S3 object(s): ThrottlingException", + expectedErrCode: codes.Unavailable, + }, + { + name: "success", + req: validRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithDeleteObjectOutput(&s3.DeleteObjectOutput{}), + ), + ), + }, + expected: 1, + }, + { + name: "recursive success", + req: validRecursiveRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ + IsTruncated: false, + Contents: []s3types.Object{ + s3types.Object{ + Key: aws.String("abc/abc1"), + }, + s3types.Object{ + Key: aws.String("abc/abc2"), + }, + s3types.Object{ + Key: aws.String("abc/abc3"), + }, + }, + }), + testMockS3WithDeleteObjectsOutput(&s3.DeleteObjectsOutput{ + Deleted: []s3types.DeletedObject{ + s3types.DeletedObject{}, + s3types.DeletedObject{}, + s3types.DeletedObject{}, + }, + }), + ), + ), + }, + expected: 3, + }, + { + name: "recursive success empty", + req: validRecursiveRequest(), + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithListObjectsV2Output(&s3.ListObjectsV2Output{ + IsTruncated: false, + Contents: []s3types.Object{}, + }), + // no deleted response since it shouldn't be called + ), + ), + }, + expected: 0, + }, + { + name: "success with dynamic credentials", + req: &pb.DeleteObjectsRequest{ + KeyPrefix: "/foo/bar/key", + Bucket: &storagebuckets.StorageBucket{ + BucketName: "foo", + Attributes: &structpb.Struct{ + Fields: map[string]*structpb.Value{ + credential.ConstRegion: structpb.NewStringValue("us-west-2"), + credential.ConstRoleArn: structpb.NewStringValue("arn:aws:iam::123456789012:role/S3Access"), + credential.ConstDisableCredentialRotation: structpb.NewBoolValue(true), + }, + }, + }, + }, + credOpts: validSTSMock(), + storageOpts: []awsStoragePersistedStateOption{ + withTestS3APIFunc( + newTestMockS3( + nil, + testMockS3WithDeleteObjectOutput(&s3.DeleteObjectOutput{}), + ), + ), + }, + expected: 1, + }, + // NOTE: there is no automated test for checking continuation tokens or multiple + // delete calls due to not having a stack for output responses. this was tested + // manually + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + require := require.New(t) + p := &StoragePlugin{ + testCredStateOpts: tc.credOpts, + testStorageStateOpts: tc.storageOpts, + } + res, err := p.DeleteObjects(context.Background(), tc.req) + if tc.expectedErrContains != "" { + require.Error(err) + require.Contains(err.Error(), tc.expectedErrContains) + require.Equal(status.Code(err).String(), tc.expectedErrCode.String()) + return + } + require.NoError(err) + + require.NoError(err) + require.NotNil(res) + require.Equal(tc.expected, res.ObjectsDeleted) + }) + } +} diff --git a/plugin/service/storage/state.go b/plugin/service/storage/state.go index 1a0032f..d130f25 100644 --- a/plugin/service/storage/state.go +++ b/plugin/service/storage/state.go @@ -25,6 +25,8 @@ type S3API interface { GetObject(ctx context.Context, params *s3.GetObjectInput, optFns ...func(*s3.Options)) (*s3.GetObjectOutput, error) HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) PutObject(ctx context.Context, params *s3.PutObjectInput, optFns ...func(*s3.Options)) (*s3.PutObjectOutput, error) + DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) + ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) } var customClient = &http.Client{ diff --git a/plugin/service/storage/testing.go b/plugin/service/storage/testing.go index 33a3453..d1ef852 100644 --- a/plugin/service/storage/testing.go +++ b/plugin/service/storage/testing.go @@ -31,6 +31,9 @@ const ( testGetObjectErr = "test error for GetObject" testPutObjectErr = "test error for PutObject" testHeadObjectErr = "test error for HeadObject" + testListObjectV2Err = "test error for ListObjectV2" + testDeleteObjectErr = "test error for DeleteObject" + testDeleteObjectsErr = "test error for DeleteObjects" ) // throttleErr is a mocked error used for testing the aws s3 client @@ -54,6 +57,14 @@ type testMockS3State struct { HeadObjectCalled bool HeadObjectInputParams *s3.HeadObjectInput + + ListObjectsV2Called bool + ListObjectsV2InputParams *s3.ListObjectsV2Input + + DeleteObjectCalled bool + DeleteObjectInputParams *s3.DeleteObjectInput + DeleteObjectsCalled bool + DeleteObjectsInputParams *s3.DeleteObjectsInput } func (s *testMockS3State) Reset() { @@ -64,6 +75,12 @@ func (s *testMockS3State) Reset() { s.PutObjectBody = nil s.HeadObjectCalled = false s.HeadObjectInputParams = nil + s.ListObjectsV2Called = false + s.ListObjectsV2InputParams = nil + s.DeleteObjectCalled = false + s.DeleteObjectInputParams = nil + s.DeleteObjectsCalled = false + s.DeleteObjectsInputParams = nil } type testMockS3 struct { @@ -83,6 +100,18 @@ type testMockS3 struct { // mocked responses for headObject HeadObjectOutput *s3.HeadObjectOutput HeadObjectErr error + + // mocked responses for ListObjectsV2 (needed for delete) + ListObjectsV2Output *s3.ListObjectsV2Output + ListObjectsV2Err error + // sometimes we need to create a wildcard output + ListObjectsV2OutputFunc func(*s3.ListObjectsV2Input) *s3.ListObjectsV2Output + + // mocked responses for DeleteObject(s) + DeleteObjectOutput *s3.DeleteObjectOutput + DeleteObjectErr error + DeleteObjectsOutput *s3.DeleteObjectsOutput + DeleteObjectsErr error } type testMockS3Option func(m *testMockS3) error @@ -129,6 +158,55 @@ func testMockS3WithHeadObjectError(e error) testMockS3Option { } } +func testMockS3WithListObjectsV2Output(o *s3.ListObjectsV2Output) testMockS3Option { + return func(m *testMockS3) error { + m.ListObjectsV2Output = o + return nil + } +} + +func testMockS3WithListObjectsV2Error(e error) testMockS3Option { + return func(m *testMockS3) error { + m.ListObjectsV2Err = e + return nil + } +} + +func testMockS3WithListObjectsV2OutputFunc(o func(*s3.ListObjectsV2Input) *s3.ListObjectsV2Output) testMockS3Option { + return func(m *testMockS3) error { + m.ListObjectsV2OutputFunc = o + return nil + } +} + +func testMockS3WithDeleteObjectOutput(o *s3.DeleteObjectOutput) testMockS3Option { + return func(m *testMockS3) error { + m.DeleteObjectOutput = o + return nil + } +} + +func testMockS3WithDeleteObjectError(e error) testMockS3Option { + return func(m *testMockS3) error { + m.DeleteObjectErr = e + return nil + } +} + +func testMockS3WithDeleteObjectsOutput(o *s3.DeleteObjectsOutput) testMockS3Option { + return func(m *testMockS3) error { + m.DeleteObjectsOutput = o + return nil + } +} + +func testMockS3WithDeleteObjectsError(e error) testMockS3Option { + return func(m *testMockS3) error { + m.DeleteObjectsErr = e + return nil + } +} + func newTestMockS3(state *testMockS3State, opts ...testMockS3Option) s3APIFunc { return func(cfgs ...aws.Config) (S3API, error) { m := &testMockS3{ @@ -193,8 +271,47 @@ func (m *testMockS3) PutObject(ctx context.Context, params *s3.PutObjectInput, o return m.PutObjectOutput, nil } +func (m *testMockS3) ListObjectsV2(ctx context.Context, params *s3.ListObjectsV2Input, optFns ...func(*s3.Options)) (*s3.ListObjectsV2Output, error) { + if m.State != nil { + m.State.ListObjectsV2Called = true + m.State.ListObjectsV2InputParams = params + } + + if m.ListObjectsV2Err != nil { + return nil, m.ListObjectsV2Err + } + + if m.ListObjectsV2OutputFunc != nil { + return m.ListObjectsV2OutputFunc(params), nil + } + + return m.ListObjectsV2Output, nil +} + func (m *testMockS3) DeleteObject(ctx context.Context, params *s3.DeleteObjectInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectOutput, error) { - return &s3.DeleteObjectOutput{}, nil + if m.State != nil { + m.State.DeleteObjectCalled = true + m.State.DeleteObjectInputParams = params + } + + if m.DeleteObjectErr != nil { + return nil, m.DeleteObjectErr + } + + return m.DeleteObjectOutput, nil +} + +func (m *testMockS3) DeleteObjects(ctx context.Context, params *s3.DeleteObjectsInput, optFns ...func(*s3.Options)) (*s3.DeleteObjectsOutput, error) { + if m.State != nil { + m.State.DeleteObjectsCalled = true + m.State.DeleteObjectsInputParams = params + } + + if m.DeleteObjectsErr != nil { + return nil, m.DeleteObjectsErr + } + + return m.DeleteObjectsOutput, nil } func (m *testMockS3) HeadObject(ctx context.Context, params *s3.HeadObjectInput, optFns ...func(*s3.Options)) (*s3.HeadObjectOutput, error) {