From dd42a5715a76d38b4b8d74b9cbccee7028b1d97b Mon Sep 17 00:00:00 2001 From: Mickael Stanislas Date: Tue, 17 Oct 2023 16:44:13 +0200 Subject: [PATCH] feat: add `cloudavenue_s3_bucket` resource/datasource --- .changelog/576.txt | 11 + docs/data-sources/s3_bucket.md | 35 +++ docs/resources/s3_bucket.md | 41 ++++ .../cloudavenue_s3_bucket/data-source.tf | 3 + .../resources/cloudavenue_s3_bucket/import.sh | 1 + .../cloudavenue_s3_bucket/resource.tf | 3 + go.mod | 5 +- go.sum | 21 +- internal/helpers/testsacc/template.go | 2 +- internal/provider/provider_datasources.go | 4 + internal/provider/provider_resources.go | 4 + internal/provider/s3/base.go | 82 +++++++ internal/provider/s3/bucket_datasource.go | 99 ++++++++ internal/provider/s3/bucket_resource.go | 223 ++++++++++++++++++ internal/provider/s3/bucket_schema.go | 64 +++++ internal/provider/s3/bucket_schema_test.go | 58 +++++ internal/provider/s3/bucket_types.go | 20 ++ internal/testsacc/acctest_resources_test.go | 3 + .../testsacc/s3_bucket_datasource_test.go | 59 +++++ internal/testsacc/s3_bucket_resource_test.go | 110 +++++++++ templates/data-sources/s3_bucket.md.tmpl | 25 ++ templates/resources/s3_bucket.md.tmpl | 25 ++ 22 files changed, 894 insertions(+), 4 deletions(-) create mode 100644 .changelog/576.txt create mode 100644 docs/data-sources/s3_bucket.md create mode 100644 docs/resources/s3_bucket.md create mode 100644 examples/data-sources/cloudavenue_s3_bucket/data-source.tf create mode 100644 examples/resources/cloudavenue_s3_bucket/import.sh create mode 100644 examples/resources/cloudavenue_s3_bucket/resource.tf create mode 100644 internal/provider/s3/base.go create mode 100644 internal/provider/s3/bucket_datasource.go create mode 100644 internal/provider/s3/bucket_resource.go create mode 100644 internal/provider/s3/bucket_schema.go create mode 100644 internal/provider/s3/bucket_schema_test.go create mode 100644 internal/provider/s3/bucket_types.go create mode 100644 internal/testsacc/s3_bucket_datasource_test.go create mode 100644 internal/testsacc/s3_bucket_resource_test.go create mode 100644 templates/data-sources/s3_bucket.md.tmpl create mode 100644 templates/resources/s3_bucket.md.tmpl diff --git a/.changelog/576.txt b/.changelog/576.txt new file mode 100644 index 000000000..c0f9ddc5c --- /dev/null +++ b/.changelog/576.txt @@ -0,0 +1,11 @@ +```release-note:new-resource +`resource/cloudavenue_s3_bucket` - Create and manage S3 buckets. +``` + +```release-note:new-data-source +`datasource/cloudavenue_s3_bucket` - Retrieve information about S3 buckets. +``` + +```release-note:dependency +deps: bumps github.com/orange-cloudavenue/cloudavenue-sdk-go from 0.2.0 to 0.3.0 +``` \ No newline at end of file diff --git a/docs/data-sources/s3_bucket.md b/docs/data-sources/s3_bucket.md new file mode 100644 index 000000000..782e83291 --- /dev/null +++ b/docs/data-sources/s3_bucket.md @@ -0,0 +1,35 @@ +--- +page_title: "cloudavenue_s3_bucket Data Source - cloudavenue" +subcategory: "S3 (Object Storage)" +description: |- + The cloudavenue_s3_bucket data source allows you to retrieve information about an existing S3 bucket +--- + +# cloudavenue_s3_bucket (Data Source) + +The `cloudavenue_s3_bucket` data source allows you to retrieve information about an existing S3 bucket + +## Example Usage + +```terraform +data "cloudavenue_s3_bucket" "example" { + name = "example" +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the bucket. + +### Optional + +- `object_lock` (Boolean) Indicates whether this bucket has an Object Lock configuration enabled. + +### Read-Only + +- `endpoint` (String) The endpoint URL of the bucket. +- `id` (String) The ID of the S3 bucket. + diff --git a/docs/resources/s3_bucket.md b/docs/resources/s3_bucket.md new file mode 100644 index 000000000..795f44a0c --- /dev/null +++ b/docs/resources/s3_bucket.md @@ -0,0 +1,41 @@ +--- +page_title: "cloudavenue_s3_bucket Resource - cloudavenue" +subcategory: "S3 (Object Storage)" +description: |- + The cloudavenue_s3_bucket resource allows you to manage S3 buckets +--- + +# cloudavenue_s3_bucket (Resource) + +The `cloudavenue_s3_bucket` resource allows you to manage S3 buckets + +## Example Usage + +```terraform +resource "cloudavenue_s3_bucket" "example" { + name = "example" +} +``` + + +## Schema + +### Required + +- `name` (String) (ForceNew) The name of the bucket. A full list of bucket naming rules (may be found here)[https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html]. + +### Optional + +- `object_lock` (Boolean) (ForceNew) Indicates whether this bucket has an Object Lock configuration enabled. Value defaults to `false`. + +### Read-Only + +- `endpoint` (String) The endpoint URL of the bucket. +- `id` (String) The ID of the S3 bucket. + +## Import + +Import is supported using the following syntax: +```shell +terraform import cloudavenue_s3_bucket.example bucketName +``` \ No newline at end of file diff --git a/examples/data-sources/cloudavenue_s3_bucket/data-source.tf b/examples/data-sources/cloudavenue_s3_bucket/data-source.tf new file mode 100644 index 000000000..80e52bcc1 --- /dev/null +++ b/examples/data-sources/cloudavenue_s3_bucket/data-source.tf @@ -0,0 +1,3 @@ +data "cloudavenue_s3_bucket" "example" { + name = "example" +} diff --git a/examples/resources/cloudavenue_s3_bucket/import.sh b/examples/resources/cloudavenue_s3_bucket/import.sh new file mode 100644 index 000000000..bb9e18e26 --- /dev/null +++ b/examples/resources/cloudavenue_s3_bucket/import.sh @@ -0,0 +1 @@ +terraform import cloudavenue_s3_bucket.example bucketName diff --git a/examples/resources/cloudavenue_s3_bucket/resource.tf b/examples/resources/cloudavenue_s3_bucket/resource.tf new file mode 100644 index 000000000..f552c2f22 --- /dev/null +++ b/examples/resources/cloudavenue_s3_bucket/resource.tf @@ -0,0 +1,3 @@ +resource "cloudavenue_s3_bucket" "example" { + name = "example" +} diff --git a/go.mod b/go.mod index a5ec9bb63..d95f8fcda 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/FrangipaneTeam/terraform-plugin-framework-supertypes v0.1.0 github.com/FrangipaneTeam/terraform-plugin-framework-validators v1.8.1 github.com/antihax/optional v1.0.0 + github.com/aws/aws-sdk-go v1.45.26 github.com/drhodes/golorem v0.0.0-20220328165741-da82e5b29246 github.com/google/uuid v1.3.1 + github.com/hashicorp/aws-sdk-go-base v1.1.0 github.com/hashicorp/terraform-plugin-docs v0.16.0 github.com/hashicorp/terraform-plugin-framework v1.4.1 github.com/hashicorp/terraform-plugin-framework-timeouts v0.4.1 @@ -19,7 +21,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.29.0 github.com/iancoleman/strcase v0.3.0 - github.com/orange-cloudavenue/cloudavenue-sdk-go v0.2.0 + github.com/orange-cloudavenue/cloudavenue-sdk-go v0.3.0 github.com/orange-cloudavenue/infrapi-sdk-go v0.1.4-0.20231005074857-89878ea119fb github.com/rs/zerolog v1.31.0 github.com/thanhpk/randstr v1.0.6 @@ -72,6 +74,7 @@ require ( github.com/imdario/mergo v0.3.15 // indirect github.com/influxdata/influxdb-client-go/v2 v2.12.3 // indirect github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect diff --git a/go.sum b/go.sum index bd3e15246..3cbcc949a 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,9 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoU github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 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.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= +github.com/aws/aws-sdk-go v1.45.26 h1:PJ2NJNY5N/yeobLYe1Y+xLdavBi67ZI8gvph6ftwVCg= +github.com/aws/aws-sdk-go v1.45.26/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI= github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= @@ -80,6 +83,7 @@ github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo= github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -98,6 +102,8 @@ github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ 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/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/hashicorp/aws-sdk-go-base v1.1.0 h1:27urM3JAp6v+Oj/Ea5ULZwuFPK9cO1RUdEpV+rNdSAc= +github.com/hashicorp/aws-sdk-go-base v1.1.0/go.mod h1:2fRjWDv3jJBeN6mVWFHV6hFTNeFBx2gpDLQaZNxUVAY= github.com/hashicorp/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= @@ -166,6 +172,11 @@ github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839 h1:W9WBk7 github.com/influxdata/line-protocol v0.0.0-20200327222509-2487e7298839/go.mod h1:xaLFMmpvUxqXtVkUJfg9QmT88cDaCJ3ZKgdZ78oO8Qo= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -202,6 +213,7 @@ github.com/mitchellh/cli v1.1.5/go.mod h1:v8+iFts2sPIKUV1ltktPXMCC8fumSKFItNcD2c github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= 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-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 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/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= @@ -216,8 +228,8 @@ 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/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= -github.com/orange-cloudavenue/cloudavenue-sdk-go v0.2.0 h1:pNCOlxzCX7Q2/8zNApgWP8VeZFwNXABNVELWElqsxQE= -github.com/orange-cloudavenue/cloudavenue-sdk-go v0.2.0/go.mod h1:8HtVSQVDVoW1pk/pFUTTOOQ2TL1SErMxV7fmke2+acg= +github.com/orange-cloudavenue/cloudavenue-sdk-go v0.3.0 h1:S5ZOzQ7Iyk6/iAebdVH+H/gNs5EokUG/27l2z4TMb4w= +github.com/orange-cloudavenue/cloudavenue-sdk-go v0.3.0/go.mod h1:XyfayrWLUBIxh49bIZUnJUUo0B2E7Vloxvo+6Zn0K2w= github.com/orange-cloudavenue/infrapi-sdk-go v0.1.4-0.20231005074857-89878ea119fb h1:1/Wc21Tp9RnDOUTjKBm9x3wi+UgUkDc2bv0fHJc5f2o= github.com/orange-cloudavenue/infrapi-sdk-go v0.1.4-0.20231005074857-89878ea119fb/go.mod h1:pGa9mB6s+weCi5QtNe5nicp7yL0C/e+i+3wHRh4cjBE= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= @@ -298,10 +310,12 @@ golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= @@ -332,6 +346,7 @@ golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -343,6 +358,7 @@ golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= @@ -388,6 +404,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/helpers/testsacc/template.go b/internal/helpers/testsacc/template.go index 3bfdcb438..3e62a51f2 100644 --- a/internal/helpers/testsacc/template.go +++ b/internal/helpers/testsacc/template.go @@ -87,7 +87,7 @@ func generateRandomString(format string) string { case "longString": return lorem.Sentence(1, 5) default: - return randstr.String(16) + return randstr.String(16, "abcdefghijklmnopqrstuvwxyz") } } diff --git a/internal/provider/provider_datasources.go b/internal/provider/provider_datasources.go index 8c8f6f835..17f23712e 100644 --- a/internal/provider/provider_datasources.go +++ b/internal/provider/provider_datasources.go @@ -12,6 +12,7 @@ import ( "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/iam" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/network" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/publicip" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/s3" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/storage" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vapp" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vdc" @@ -83,5 +84,8 @@ func (p *cloudavenueProvider) DataSources(_ context.Context) []func() datasource // * BACKUP backup.NewBackupDataSource, + + // * S3 + s3.NewBucketDataSource, } } diff --git a/internal/provider/provider_resources.go b/internal/provider/provider_resources.go index a5c04a144..adba9b7a8 100644 --- a/internal/provider/provider_resources.go +++ b/internal/provider/provider_resources.go @@ -12,6 +12,7 @@ import ( "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/iam" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/network" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/publicip" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/s3" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vapp" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vcda" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vdc" @@ -76,5 +77,8 @@ func (p *cloudavenueProvider) Resources(_ context.Context) []func() resource.Res // * BACKUP backup.NewBackupResource, + + // * S3 + s3.NewBucketResource, } } diff --git a/internal/provider/s3/base.go b/internal/provider/s3/base.go new file mode 100644 index 000000000..6ea43bd1a --- /dev/null +++ b/internal/provider/s3/base.go @@ -0,0 +1,82 @@ +package s3 + +import ( + "context" + "errors" + "time" + + "github.com/hashicorp/aws-sdk-go-base/tfawserr" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" +) + +const ( + categoryName = "s3" + + // ErrCodeObjectLockConfigurationNotFoundError object lock configuration not found. + ErrCodeObjectLockConfigurationNotFoundError = "ObjectLockConfigurationNotFoundError" +) + +// DefaultWaitRetryInterval is used to set the retry interval to 0 during acceptance tests. +var DefaultWaitRetryInterval *time.Duration + +// NotFound returns true if the error represents a "resource not found" condition. +// Specifically, NotFound returns true if the error or a wrapped error is of type +// retry.NotFoundError. +func NotFound(err error) bool { + var e *retry.NotFoundError // nosemgrep:ci.is-not-found-error + return errors.As(err, &e) +} + +type RetryWhenConfig[T any] struct { + Timeout time.Duration + Interval time.Duration + Function func() (T, error) +} + +var ErrRetryWhenTimeout = errors.New("timeout reached") + +// retryWhen executes the function passed in the configuration object until the timeout is reached or the context is cancelled. +// It will retry if the shouldRetry function returns true. It will stop if the shouldRetry function returns false. +func retryWhen[T any](ctx context.Context, config *RetryWhenConfig[T], shouldRetry func(error) bool) (T, error) { //nolint: ireturn,unused + retryInterval := config.Interval + if DefaultWaitRetryInterval != nil { + retryInterval = *DefaultWaitRetryInterval + } + + timer := time.NewTimer(config.Timeout) + + for { + result, err := config.Function() + if shouldRetry(err) { + select { + case <-timer.C: + return result, ErrRetryWhenTimeout + case <-ctx.Done(): + return result, ctx.Err() + default: + time.Sleep(retryInterval) // lintignore:R018 + continue + } + } + + return result, err + } +} + +// retryWhenAWSErrCodeEquals retries a function when it returns a specific AWS error. +func retryWhenAWSErrCodeEquals[T any](ctx context.Context, codes []string, config *RetryWhenConfig[T]) (T, error) { //nolint: ireturn,unused + return retryWhen(ctx, config, func(err error) bool { + return tfawserr.ErrCodeEquals(err, codes...) + }) +} + +// retryWhenAWSErrCodeNotEquals retries a function until it returns a specific AWS error. +func retryWhenAWSErrCodeNotEquals[T any](ctx context.Context, codes []string, config *RetryWhenConfig[T]) (T, error) { //nolint: ireturn,unused + return retryWhen(ctx, config, func(err error) bool { + if err == nil { + return true + } + + return !tfawserr.ErrCodeEquals(err, codes...) + }) +} diff --git a/internal/provider/s3/bucket_datasource.go b/internal/provider/s3/bucket_datasource.go new file mode 100644 index 000000000..70ef6b0f4 --- /dev/null +++ b/internal/provider/s3/bucket_datasource.go @@ -0,0 +1,99 @@ +// Package s3 provides a Terraform datasource. +package s3 + +import ( + "context" + "fmt" + + "github.com/aws/aws-sdk-go/service/s3" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" +) + +var ( + _ datasource.DataSource = &BucketDataSource{} + _ datasource.DataSourceWithConfigure = &BucketDataSource{} +) + +func NewBucketDataSource() datasource.DataSource { + return &BucketDataSource{} +} + +type BucketDataSource struct { + client *client.CloudAvenue + s3Client *s3.S3 +} + +// Init Initializes the data source. +func (d *BucketDataSource) Init(ctx context.Context, dm *BucketModel) (diags diag.Diagnostics) { + d.s3Client = d.client.CAVSDK.V1.S3() + return +} + +func (d *BucketDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_bucket" +} + +func (d *BucketDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = s3BucketSchema(ctx).GetDataSource(ctx) +} + +func (d *BucketDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + d.client = client +} + +func (d *BucketDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_s3_bucket", d.client.GetOrgName(), metrics.Read)() + + config := &BucketModel{} + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(d.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the data source read logic here. + */ + + // If read function is identical to the resource, you can use the following code: + s := &BucketResource{ + client: d.client, + s3Client: d.s3Client, + } + + // Read data from the API + data, _, diags := s.read(ctx, config) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/s3/bucket_resource.go b/internal/provider/s3/bucket_resource.go new file mode 100644 index 000000000..7fd8871af --- /dev/null +++ b/internal/provider/s3/bucket_resource.go @@ -0,0 +1,223 @@ +package s3 + +import ( + "context" + "errors" + "fmt" + + "github.com/aws/aws-sdk-go/service/s3" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &BucketResource{} + _ resource.ResourceWithConfigure = &BucketResource{} + _ resource.ResourceWithImportState = &BucketResource{} +) + +// NewBucketResource is a helper function to simplify the provider implementation. +func NewBucketResource() resource.Resource { + return &BucketResource{} +} + +// BucketResource is the resource implementation. +type BucketResource struct { + client *client.CloudAvenue + s3Client *s3.S3 +} + +// Init Initializes the resource. +func (r *BucketResource) Init(ctx context.Context, rm *BucketModel) (diags diag.Diagnostics) { + r.s3Client = r.client.CAVSDK.V1.S3() + return +} + +// Metadata returns the resource type name. +func (r *BucketResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_bucket" +} + +// Schema defines the schema for the resource. +func (r *BucketResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = s3BucketSchema(ctx).GetResource(ctx) +} + +func (r *BucketResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.client = client +} + +// Create creates the resource and sets the initial Terraform state. +func (r *BucketResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer metrics.New("cloudavenue_s3_bucket", r.client.GetOrgName(), metrics.Create)() + + plan := &BucketModel{} + + // Retrieve values from plan + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource creation logic here. + */ + + if _, err := r.s3Client.CreateBucket(&s3.CreateBucketInput{ + Bucket: plan.Name.GetPtr(), + ObjectLockEnabledForBucket: utils.TakeBoolPointer(plan.ObjectLock.Get()), + }); err != nil { + resp.Diagnostics.AddError("Error creating bucket", fmt.Sprintf("Error creating bucket (name=%s): %s", plan.Name.Get(), err.Error())) + return + } + + // Use generic read function to refresh the state + state, _, d := r.read(ctx, plan) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *BucketResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer metrics.New("cloudavenue_s3_bucket", r.client.GetOrgName(), metrics.Read)() + + state := &BucketModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Refresh the state + stateRefreshed, found, d := r.read(ctx, state) + if !found { + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *BucketResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer metrics.New("cloudavenue_s3_bucket", r.client.GetOrgName(), metrics.Update)() + // All attributes are immutable +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *BucketResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer metrics.New("cloudavenue_s3_bucket", r.client.GetOrgName(), metrics.Delete)() + + state := &BucketModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource deletion here + */ + + _, err := r.s3Client.DeleteBucket(&s3.DeleteBucketInput{ + Bucket: state.Name.GetPtr(), + }) + if err != nil { + resp.Diagnostics.AddError("Error deleting bucket", fmt.Sprintf("Error deleting bucket: %s", err.Error())) + return + } +} + +func (r *BucketResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + defer metrics.New("cloudavenue_s3_bucket", r.client.GetOrgName(), metrics.Import)() + resource.ImportStatePassthroughID(ctx, path.Root("name"), req, resp) +} + +// * CustomFuncs + +// read is a generic read function that can be used by the resource Create, Read and Update functions. +func (r *BucketResource) read(ctx context.Context, planOrState *BucketModel) (stateRefreshed *BucketModel, found bool, diags diag.Diagnostics) { + stateRefreshed = planOrState.Copy() + + /* + Implement the resource read here + */ + + // Get object_lock_enabled + objectLockConfiguration, err := r.s3Client.GetObjectLockConfigurationWithContext(ctx, &s3.GetObjectLockConfigurationInput{ + Bucket: planOrState.Name.GetPtr(), + }) + if err != nil { + switch { + case errors.Is(err, errors.New(ErrCodeObjectLockConfigurationNotFoundError)): + stateRefreshed.ObjectLock.Set(false) + case errors.Is(err, errors.New(s3.ErrCodeNoSuchBucket)): + // Not Found + return nil, false, nil + default: + diags.AddError("Error retrieving bucket", fmt.Sprintf("Error retrieving bucket: %s", err.Error())) + return nil, true, diags + } + } else if objectLockConfiguration.ObjectLockConfiguration != nil { + stateRefreshed.ObjectLock.Set(true) + } + + stateRefreshed.Endpoint.Set(fmt.Sprintf("https://%s.s3-region01.cloudavenue.orange-business.com", planOrState.Name.Get())) + + if !stateRefreshed.ID.IsKnown() { + stateRefreshed.ID.Set(stateRefreshed.Name.Get()) + } + + return stateRefreshed, true, nil +} diff --git a/internal/provider/s3/bucket_schema.go b/internal/provider/s3/bucket_schema.go new file mode 100644 index 000000000..e78f17bd3 --- /dev/null +++ b/internal/provider/s3/bucket_schema.go @@ -0,0 +1,64 @@ +package s3 + +import ( + "context" + + schemaR "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + + superschema "github.com/FrangipaneTeam/terraform-plugin-framework-superschema" +) + +func s3BucketSchema(_ context.Context) superschema.Schema { + return superschema.Schema{ + Resource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_s3_bucket` resource allows you to manage S3 buckets", + }, + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_s3_bucket` data source allows you to retrieve information about an existing S3 bucket", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + Computed: true, + MarkdownDescription: "The ID of the S3 bucket.", + }, + }, + "name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the bucket.", + Required: true, + }, + Resource: &schemaR.StringAttribute{ + MarkdownDescription: "A full list of bucket naming rules (may be found here)[https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucketnamingrules.html].", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + "object_lock": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "Indicates whether this bucket has an Object Lock configuration enabled.", + Optional: true, + Computed: true, + }, + Resource: &schemaR.BoolAttribute{ + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.RequiresReplace(), + }, + }, + }, + "endpoint": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The endpoint URL of the bucket.", + Computed: true, + }, + }, + }, + } +} diff --git a/internal/provider/s3/bucket_schema_test.go b/internal/provider/s3/bucket_schema_test.go new file mode 100644 index 000000000..4f0b3d633 --- /dev/null +++ b/internal/provider/s3/bucket_schema_test.go @@ -0,0 +1,58 @@ +package s3_test + +import ( + "context" + "testing" + + // The fwresource import alias is so there is no collistion + // with the more typical acceptance testing import: + // "github.com/hashicorp/terraform-plugin-testing/helper/resource". + fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/s3" +) + +// Unit test for the schema of the resource cloudavenue_s3_Bucket. +func Test3BucketResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + s3.NewBucketResource().Schema(ctx, fwresource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} + +// Unit test for the schema of the datasource cloudavenue_s3_Bucket. +func Test3BucketDataSourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + s3.NewBucketDataSource().Schema(ctx, fwdatasource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} diff --git a/internal/provider/s3/bucket_types.go b/internal/provider/s3/bucket_types.go new file mode 100644 index 000000000..fe1bebd3d --- /dev/null +++ b/internal/provider/s3/bucket_types.go @@ -0,0 +1,20 @@ +package s3 + +import ( + supertypes "github.com/FrangipaneTeam/terraform-plugin-framework-supertypes" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +type BucketModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + ObjectLock supertypes.BoolValue `tfsdk:"object_lock"` + Endpoint supertypes.StringValue `tfsdk:"endpoint"` +} + +func (rm *BucketModel) Copy() *BucketModel { + x := &BucketModel{} + utils.ModelCopy(rm, x) + return x +} diff --git a/internal/testsacc/acctest_resources_test.go b/internal/testsacc/acctest_resources_test.go index 0a0d03b10..ce84339df 100644 --- a/internal/testsacc/acctest_resources_test.go +++ b/internal/testsacc/acctest_resources_test.go @@ -24,5 +24,8 @@ func GetResourceConfig() map[testsacc.ResourceName]func() resourceConfig { // * Backup BackupResourceName: NewResourceConfig(NewBackupResourceTest()), + + // * S3 + S3BucketResourceName: NewResourceConfig(NewS3BucketResourceTest()), } } diff --git a/internal/testsacc/s3_bucket_datasource_test.go b/internal/testsacc/s3_bucket_datasource_test.go new file mode 100644 index 000000000..c99c34e0c --- /dev/null +++ b/internal/testsacc/s3_bucket_datasource_test.go @@ -0,0 +1,59 @@ +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &S3BucketDatasourceGoDataSource{} + +const ( + S3BucketDatasourceGoDataSourceName = testsacc.ResourceName("data.cloudavenue_s3_bucket") +) + +type S3BucketDatasourceGoDataSource struct{} + +func NewS3BucketDatasourceGoDataSourceTest() testsacc.TestACC { + return &S3BucketDatasourceGoDataSource{} +} + +// GetResourceName returns the name of the resource. +func (r *S3BucketDatasourceGoDataSource) GetResourceName() string { + return S3BucketDatasourceGoDataSourceName.String() +} + +func (r *S3BucketDatasourceGoDataSource) DependenciesConfig() (configs testsacc.TFData) { + configs.Append(GetResourceConfig()[S3BucketResourceName]().GetDefaultConfig()) + return +} + +func (r *S3BucketDatasourceGoDataSource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // * Test One (example) + "example": func(_ context.Context, _ string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_s3_bucket" "example" { + name = cloudavenue_s3_bucket.example.name + }`, + // Here use resource config test to test the data source + Checks: GetResourceConfig()[S3BucketResourceName]().GetDefaultChecks(), + }, + } + }, + } +} + +func TestAccS3BucketDatasourceGoDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&S3BucketDatasourceGoDataSource{}), + }) +} diff --git a/internal/testsacc/s3_bucket_resource_test.go b/internal/testsacc/s3_bucket_resource_test.go new file mode 100644 index 000000000..6652c5737 --- /dev/null +++ b/internal/testsacc/s3_bucket_resource_test.go @@ -0,0 +1,110 @@ +package testsacc + +import ( + "context" + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &S3BucketResource{} + +const ( + S3BucketResourceName = testsacc.ResourceName("cloudavenue_s3_bucket") +) + +type S3BucketResource struct{} + +func NewS3BucketResourceTest() testsacc.TestACC { + return &S3BucketResource{} +} + +// GetResourceName returns the name of the resource. +func (r *S3BucketResource) GetResourceName() string { + return S3BucketResourceName.String() +} + +func (r *S3BucketResource) DependenciesConfig() (configs testsacc.TFData) { + return +} + +func (r *S3BucketResource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + // TODO : Complete tests + // * First test named "example" + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "id"), + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_s3_bucket" "example" { + name = {{ generate . "name" }} + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "object_lock", "false"), + resource.TestCheckResourceAttr(resourceName, "endpoint", fmt.Sprintf("https://%s.s3-region01.cloudavenue.orange-business.com", testsacc.GetValueFromTemplate(resourceName, "name"))), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // No updates + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + "examplewithobjectlock": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrSet(resourceName, "id"), + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_s3_bucket" "examplewithobjectlock" { + name = {{ generate . "name" }} + object_lock = true + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "object_lock", "true"), + resource.TestCheckResourceAttr(resourceName, "endpoint", fmt.Sprintf("https://%s.s3-region01.cloudavenue.orange-business.com", testsacc.GetValueFromTemplate(resourceName, "name"))), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // No updates + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + } +} + +func TestAccS3BucketResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&S3BucketResource{}), + }) +} diff --git a/templates/data-sources/s3_bucket.md.tmpl b/templates/data-sources/s3_bucket.md.tmpl new file mode 100644 index 000000000..62ff04eeb --- /dev/null +++ b/templates/data-sources/s3_bucket.md.tmpl @@ -0,0 +1,25 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "S3 (Object Storage)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: +{{ codefile "shell" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/s3_bucket.md.tmpl b/templates/resources/s3_bucket.md.tmpl new file mode 100644 index 000000000..62ff04eeb --- /dev/null +++ b/templates/resources/s3_bucket.md.tmpl @@ -0,0 +1,25 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "S3 (Object Storage)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: +{{ codefile "shell" .ImportFile }} +{{- end }} \ No newline at end of file