diff --git a/code_generator/provider_code_spec.json b/code_generator/provider_code_spec.json index 0ee1026..07d2301 100644 --- a/code_generator/provider_code_spec.json +++ b/code_generator/provider_code_spec.json @@ -1,52 +1,210 @@ { - "provider": { - "name": "quay" - }, - "resources": [ - { - "name": "organization", - "schema": { - "attributes": [ - { - "name": "email", - "string": { - "computed_optional_required": "required", - "description": "Organization contact email" - } - }, - { - "name": "name", - "string": { - "computed_optional_required": "required", - "description": "Organization name" - } - } - ] - } - } - ], - "datasources": [ - { - "name": "organization", - "schema": { - "attributes": [ - { - "name": "email", - "string": { - "computed_optional_required": "computed", - "description": "Organization contact email" - } - }, - { - "name": "name", - "string": { - "computed_optional_required": "required", - "description": "Organization name" - } - } - ] - } - } - ], - "version": "0.1" + "provider": { + "name": "quay" + }, + "resources": [ + { + "name": "organization", + "schema": { + "attributes": [ + { + "name": "email", + "string": { + "computed_optional_required": "required", + "description": "Organization contact email" + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Organization name" + } + } + ] + } + }, + { + "name": "organization_robot", + "schema": { + "attributes": [ + { + "name": "description", + "string": { + "computed_optional_required": "computed_optional", + "default" : { + "static": "" + }, + "description": "Text description", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "fullname", + "string": { + "computed_optional_required": "computed", + "description": "Robot full name" + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Robot short name", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "orgname", + "string": { + "computed_optional_required": "required", + "description": "Organization name", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + } + ] + } + }, + { + "name": "organization_team", + "schema": { + "attributes": [ + { + "name": "description", + "string": { + "computed_optional_required": "computed_optional", + "default" : { + "static": "" + }, + "description": "Markdown description" + } + }, + { + "name": "members", + "list": { + "computed_optional_required": "optional", + "description": "List of team members", + "element_type": { + "string": {} + } + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Team name", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "orgname", + "string": { + "computed_optional_required": "required", + "description": "Organization name", + "plan_modifiers": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + } + ], + "schema_definition": "stringplanmodifier.RequiresReplace()" + } + } + ] + } + }, + { + "name": "role", + "string": { + "computed_optional_required": "required", + "description": "Team permission. Should be admin, creator, or member.", + "validators": [ + { + "custom": { + "imports": [ + { + "path": "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + } + ], + "schema_definition": "stringvalidator.OneOf([]string{\"admin\", \"creator\", \"member\"}...)" + } + } + ] + } + } + ] + } + } + ], + "datasources": [ + { + "name": "organization", + "schema": { + "attributes": [ + { + "name": "email", + "string": { + "computed_optional_required": "computed", + "description": "Organization contact email" + } + }, + { + "name": "name", + "string": { + "computed_optional_required": "required", + "description": "Organization name" + } + } + ] + } + } + ], + "version": "0.1" } \ No newline at end of file diff --git a/docs/resources/organization_robot.md b/docs/resources/organization_robot.md new file mode 100644 index 0000000..e379d31 --- /dev/null +++ b/docs/resources/organization_robot.md @@ -0,0 +1,50 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "quay_organization_robot Resource - quay" +subcategory: "" +description: |- + +--- + +# quay_organization_robot (Resource) + + + +## Example Usage + +```terraform +resource "quay_organization" "main" { + name = "main" + email = "quay+main@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.main.name +} +``` + + +## Schema + +### Required + +- `name` (String) Robot short name +- `orgname` (String) Organization name + +### Optional + +- `description` (String) Text description + +### Read-Only + +- `fullname` (String) Robot full name + +## Import + +Import is supported using the following syntax: + +```shell +# An organization robot can be imported using its short name. +terraform import quay_organization_robot.test test +``` diff --git a/docs/resources/organization_team.md b/docs/resources/organization_team.md new file mode 100644 index 0000000..896946e --- /dev/null +++ b/docs/resources/organization_team.md @@ -0,0 +1,57 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "quay_organization_team Resource - quay" +subcategory: "" +description: |- + +--- + +# quay_organization_team (Resource) + + + +## Example Usage + +```terraform +resource "quay_organization" "main" { + name = "main" + email = "quay+main@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.main.name +} + +resource "quay_organization_team" "admin" { + name = "admin" + orgname = quay_organization.main.name + role = "admin" + members = [ + quay_organization_robot.test.fullname + ] +} +``` + + +## Schema + +### Required + +- `name` (String) Team name +- `orgname` (String) Organization name +- `role` (String) Team permission. Should be admin, creator, or member. + +### Optional + +- `description` (String) Markdown description +- `members` (List of String) List of team members + +## Import + +Import is supported using the following syntax: + +```shell +# An organization team can be imported using its name. +terraform import quay_organization_team.admin admin +``` diff --git a/examples/main.tf b/examples/main.tf index 3ec6c40..b132032 100644 --- a/examples/main.tf +++ b/examples/main.tf @@ -14,3 +14,17 @@ resource "quay_organization" "main" { name = "main" email = "quay+main@example.com" } + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.main.name +} + +resource "quay_organization_team" "admin" { + name = "admin" + orgname = quay_organization.main.name + role = "admin" + members = [ + quay_organization_robot.test.fullname + ] +} diff --git a/examples/resources/quay_organization_robot/import.sh b/examples/resources/quay_organization_robot/import.sh new file mode 100644 index 0000000..dab0623 --- /dev/null +++ b/examples/resources/quay_organization_robot/import.sh @@ -0,0 +1,2 @@ +# An organization robot can be imported using its short name. +terraform import quay_organization_robot.test test diff --git a/examples/resources/quay_organization_robot/resource.tf b/examples/resources/quay_organization_robot/resource.tf new file mode 100644 index 0000000..507458c --- /dev/null +++ b/examples/resources/quay_organization_robot/resource.tf @@ -0,0 +1,9 @@ +resource "quay_organization" "main" { + name = "main" + email = "quay+main@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.main.name +} diff --git a/examples/resources/quay_organization_team/import.sh b/examples/resources/quay_organization_team/import.sh new file mode 100644 index 0000000..4511157 --- /dev/null +++ b/examples/resources/quay_organization_team/import.sh @@ -0,0 +1,2 @@ +# An organization team can be imported using its name. +terraform import quay_organization_team.admin admin diff --git a/examples/resources/quay_organization_team/resource.tf b/examples/resources/quay_organization_team/resource.tf new file mode 100644 index 0000000..18120eb --- /dev/null +++ b/examples/resources/quay_organization_team/resource.tf @@ -0,0 +1,18 @@ +resource "quay_organization" "main" { + name = "main" + email = "quay+main@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.main.name +} + +resource "quay_organization_team" "admin" { + name = "admin" + orgname = quay_organization.main.name + role = "admin" + members = [ + quay_organization_robot.test.fullname + ] +} diff --git a/go.mod b/go.mod index db17f3b..e774705 100644 --- a/go.mod +++ b/go.mod @@ -7,19 +7,30 @@ replace github.com/enthought/terraform-provider-quay/quay_api => ./quay_api require ( github.com/enthought/terraform-provider-quay/quay_api v0.0.0-00010101000000-000000000000 github.com/hashicorp/terraform-plugin-framework v1.11.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.10.0 ) require ( + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect github.com/ProtonMail/go-crypto v1.1.0-alpha.2 // indirect github.com/agext/levenshtein v1.2.2 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect github.com/fatih/color v1.16.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/cli v1.1.6 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-checkpoint v0.5.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect @@ -35,25 +46,36 @@ require ( github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.21.0 // indirect github.com/hashicorp/terraform-json v0.22.1 // indirect + github.com/hashicorp/terraform-plugin-docs v0.19.4 // indirect github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.15 // indirect github.com/kr/pretty v0.3.0 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-testing-interface v1.14.1 // indirect github.com/mitchellh/go-wordwrap v1.0.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.0.0 // indirect + github.com/posener/complete v1.2.3 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // indirect github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/yuin/goldmark v1.7.1 // indirect + github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/zclconf/go-cty v1.15.0 // indirect + go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect golang.org/x/crypto v0.26.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.19.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.8.0 // indirect @@ -64,4 +86,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/grpc v1.63.2 // indirect google.golang.org/protobuf v1.34.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index ab8d665..eb03bce 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,15 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= +github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= +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/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg= @@ -9,6 +19,12 @@ github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= @@ -43,6 +59,11 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8= +github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4= 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= @@ -55,6 +76,7 @@ github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 h1:1/D3zfFHttUK github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320/go.mod h1:EiZBMaudVLy8fmjf9Npq1dq9RalhveqZG5w/yz3mHWs= github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= @@ -76,8 +98,12 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/terraform-plugin-docs v0.19.4 h1:G3Bgo7J22OMtegIgn8Cd/CaSeyEljqjH3G39w28JK4c= +github.com/hashicorp/terraform-plugin-docs v0.19.4/go.mod h1:4pLASsatTmRynVzsjEhbXZ6s7xBlUw/2Kt0zfrq8HxA= github.com/hashicorp/terraform-plugin-framework v1.11.0 h1:M7+9zBArexHFXDx/pKTxjE6n/2UCXY6b8FIq9ZYhwfE= github.com/hashicorp/terraform-plugin-framework v1.11.0/go.mod h1:qBXLDn69kM97NNVi/MQ9qgd1uWWsVftGSnygYG1tImM= +github.com/hashicorp/terraform-plugin-framework-validators v0.13.0 h1:bxZfGo9DIUoLLtHMElsu+zwqI4IsMZQBRRy4iLzZJ8E= +github.com/hashicorp/terraform-plugin-framework-validators v0.13.0/go.mod h1:wGeI02gEhj9nPANU62F2jCaHjXulejm/X+af4PdZaNo= github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= @@ -92,6 +118,11 @@ github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= 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.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= @@ -114,6 +145,9 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +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-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= @@ -122,6 +156,7 @@ github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9 github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= @@ -130,14 +165,25 @@ github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= 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.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +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/skeema/knownhosts v1.2.2 h1:Iug2P4fLmDw9f41PB6thxUkNUkJzB5i+1/exaj40L3A= github.com/skeema/knownhosts v1.2.2/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +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.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= @@ -151,14 +197,23 @@ github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= github.com/zclconf/go-cty v1.15.0 h1:tTCRWxsexYUmtt/wVxgDClUe+uQusuI443uL6e+5sXQ= github.com/zclconf/go-cty v1.15.0/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8= golang.org/x/mod v0.19.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -166,6 +221,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -184,16 +240,19 @@ 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.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.23.0 h1:YfKFowiIMvtgl1UERQoTPPToxltDeZfbj4H7dVUCwmM= golang.org/x/sys v0.23.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 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.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -220,5 +279,8 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/provider/helpers.go b/internal/provider/helpers.go index eacec48..cb1c87f 100644 --- a/internal/provider/helpers.go +++ b/internal/provider/helpers.go @@ -1,8 +1,41 @@ package provider -import "net/url" +import ( + "errors" + "net/url" + + "github.com/enthought/terraform-provider-quay/quay_api" +) + +func handleQuayAPIError(err error) string { + var apiErr *quay_api.GenericOpenAPIError + errDetail := "" + if errors.As(err, &apiErr) { + errDetail = string(apiErr.Body()) + } else { + errDetail = err.Error() + } + return errDetail +} func isValidURL(u string) bool { _, err := url.ParseRequestURI(u) return err == nil } + +// Subtract two slices, c = a - b. +func subtractStringSlice(a []string, b []string) []string { + bMap := make(map[string]struct{}) + for _, v := range b { + bMap[v] = struct{}{} + } + + var c []string + for _, v := range a { + if _, found := bMap[v]; !found { + c = append(c, v) + } + } + + return c +} diff --git a/internal/provider/organization_data_source.go b/internal/provider/organization_data_source.go index 8aa4f93..6a39cb9 100644 --- a/internal/provider/organization_data_source.go +++ b/internal/provider/organization_data_source.go @@ -37,7 +37,7 @@ func (d *organizationDataSource) Schema(ctx context.Context, _ datasource.Schema func (d *organizationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { var data datasource_organization.OrganizationModel - var resData OrganizationModelJSON + var resData organizationModelJSON var apiErr *quay_api.GenericOpenAPIError // Read Terraform configuration data into the model diff --git a/internal/provider/organization_data_source_test.go b/internal/provider/organization_data_source_test.go index b2334a2..b5d0b0d 100644 --- a/internal/provider/organization_data_source_test.go +++ b/internal/provider/organization_data_source_test.go @@ -7,28 +7,28 @@ import ( ) func TestAccOrganizationDataSource(t *testing.T) { - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ // Create and read resource { Config: providerConfig + ` -resource "quay_organization" "test" { - name = "test" - email = "quay+test@example.com" +resource "quay_organization" "org_data" { + name = "org_data" + email = "quay+org_data@example.com" } -data "quay_organization" "test" { - name = "test" +data "quay_organization" "org_data" { + name = "org_data" depends_on = [ - quay_organization.test + quay_organization.org_data ] } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("data.quay_organization.test", "name", "test"), - resource.TestCheckResourceAttr("data.quay_organization.test", "email", "quay+test@example.com"), + resource.TestCheckResourceAttr("data.quay_organization.org_data", "name", "org_data"), + resource.TestCheckResourceAttr("data.quay_organization.org_data", "email", "quay+org_data@example.com"), ), }, }, diff --git a/internal/provider/organization_resource.go b/internal/provider/organization_resource.go index 2c60fe2..f107854 100644 --- a/internal/provider/organization_resource.go +++ b/internal/provider/organization_resource.go @@ -3,7 +3,6 @@ package provider import ( "context" "encoding/json" - "errors" "fmt" "io" @@ -29,9 +28,20 @@ type organizationResource struct { client *quay_api.APIClient } -type OrganizationModelJSON struct { - Email string `json:"email"` - Name string `json:"name"` +type organizationModelJSON struct { + Email string `json:"email"` + Name string `json:"name"` + Teams map[string]organizationTeamModelJSON `json:"teams"` +} + +type organizationTeamModelJSON struct { + Name string `json:"name"` + Role string `json:"role"` + Description string `json:"description"` + CanView bool `json:"can_view"` + RepoCount int `json:"repo_count"` + MemberCount int `json:"member_count"` + IsSynced bool `json:"is_synced"` } func (r *organizationResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { @@ -44,7 +54,6 @@ func (r *organizationResource) Schema(ctx context.Context, _ resource.SchemaRequ func (r *organizationResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data resource_organization.OrganizationModel - var apiErr *quay_api.GenericOpenAPIError // Read Terraform plan data into the model resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) @@ -57,15 +66,8 @@ func (r *organizationResource) Create(ctx context.Context, req resource.CreateRe newOrg := quay_api.NewNewOrg(data.Name.ValueString(), data.Email.ValueString()) _, err := r.client.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() if err != nil { - errDetail := "" - if errors.As(err, &apiErr) { - errDetail = string(apiErr.Body()) - } else { - errDetail = err.Error() - } - resp.Diagnostics.AddError( - "Error creating Quay org", - "Could not create Quay org, unexpected error: "+errDetail) + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error creating Quay org", "Could not create Quay org, unexpected error: "+errDetail) return } @@ -75,8 +77,7 @@ func (r *organizationResource) Create(ctx context.Context, req resource.CreateRe func (r *organizationResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data resource_organization.OrganizationModel - var resData OrganizationModelJSON - var apiErr *quay_api.GenericOpenAPIError + var resData organizationModelJSON // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) @@ -87,15 +88,8 @@ func (r *organizationResource) Read(ctx context.Context, req resource.ReadReques // Get data from API httpRes, err := r.client.OrganizationAPI.GetOrganization(context.Background(), data.Name.ValueString()).Execute() if err != nil { - errDetail := "" - if errors.As(err, &apiErr) { - errDetail = string(apiErr.Body()) - } else { - errDetail = err.Error() - } - resp.Diagnostics.AddError( - "Error reading Quay org", - "Could not read Quay org, unexpected error: "+errDetail) + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error read Quay org", "Could not read Quay org, unexpected error: "+errDetail) return } @@ -125,7 +119,6 @@ func (r *organizationResource) Read(ctx context.Context, req resource.ReadReques func (r *organizationResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var dataState resource_organization.OrganizationModel var dataPlan resource_organization.OrganizationModel - var apiErr *quay_api.GenericOpenAPIError // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &dataState)...) @@ -146,15 +139,8 @@ func (r *organizationResource) Update(ctx context.Context, req resource.UpdateRe } _, err := r.client.OrganizationAPI.ChangeOrganizationDetails(context.Background(), dataState.Name.ValueString()).Body(updateOrg).Execute() if err != nil { - errDetail := "" - if errors.As(err, &apiErr) { - errDetail = string(apiErr.Body()) - } else { - errDetail = err.Error() - } - resp.Diagnostics.AddError( - "Error updating Quay org", - "Could not update Quay org, unexpected error: "+errDetail) + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error updating Quay org", "Could not update Quay org, unexpected error: "+errDetail) return } } @@ -166,15 +152,8 @@ func (r *organizationResource) Update(ctx context.Context, req resource.UpdateRe } _, err := r.client.SuperuserAPI.ChangeOrganization(context.Background(), dataState.Name.ValueString()).Body(updateOrg).Execute() if err != nil { - errDetail := "" - if errors.As(err, &apiErr) { - errDetail = string(apiErr.Body()) - } else { - errDetail = err.Error() - } - resp.Diagnostics.AddError( - "Error updating Quay org", - "Could not update Quay org, unexpected error: "+errDetail) + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error updating Quay org", "Could not update Quay org, unexpected error: "+errDetail) return } } @@ -185,7 +164,6 @@ func (r *organizationResource) Update(ctx context.Context, req resource.UpdateRe func (r *organizationResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data resource_organization.OrganizationModel - var apiErr *quay_api.GenericOpenAPIError // Read Terraform prior state data into the model resp.Diagnostics.Append(req.State.Get(ctx, &data)...) @@ -197,15 +175,8 @@ func (r *organizationResource) Delete(ctx context.Context, req resource.DeleteRe // Delete API call logic _, err := r.client.OrganizationAPI.DeleteAdminedOrganization(context.Background(), data.Name.ValueString()).Execute() if err != nil { - errDetail := "" - if errors.As(err, &apiErr) { - errDetail = string(apiErr.Body()) - } else { - errDetail = err.Error() - } - resp.Diagnostics.AddError( - "Error deleting Quay org", - "Could not deleting Quay org, unexpected error: "+errDetail) + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error deleting Quay org", "Could not delete Quay org, unexpected error: "+errDetail) return } } diff --git a/internal/provider/organization_resource_test.go b/internal/provider/organization_resource_test.go index 44f8471..f290152 100644 --- a/internal/provider/organization_resource_test.go +++ b/internal/provider/organization_resource_test.go @@ -7,67 +7,67 @@ import ( ) func TestAccOrganizationResource(t *testing.T) { - resource.Test(t, resource.TestCase{ + resource.ParallelTest(t, resource.TestCase{ ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, Steps: []resource.TestStep{ // Create and Read { Config: providerConfig + ` -resource "quay_organization" "test" { - name = "test" - email = "quay+test@example.com" +resource "quay_organization" "org" { + name = "org" + email = "quay+org@example.com" } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("quay_organization.test", "name", "test"), - resource.TestCheckResourceAttr("quay_organization.test", "email", "quay+test@example.com"), + resource.TestCheckResourceAttr("quay_organization.org", "name", "org"), + resource.TestCheckResourceAttr("quay_organization.org", "email", "quay+org@example.com"), ), }, // Import { - ResourceName: "quay_organization.test", + ResourceName: "quay_organization.org", ImportState: true, - ImportStateId: "test", + ImportStateId: "org", ImportStateVerify: true, ImportStateVerifyIdentifierAttribute: "name", }, // Update name { Config: providerConfig + ` -resource "quay_organization" "test" { - name = "test2" - email = "quay+test@example.com" +resource "quay_organization" "org" { + name = "org2" + email = "quay+org@example.com" } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("quay_organization.test", "name", "test2"), - resource.TestCheckResourceAttr("quay_organization.test", "email", "quay+test@example.com"), + resource.TestCheckResourceAttr("quay_organization.org", "name", "org2"), + resource.TestCheckResourceAttr("quay_organization.org", "email", "quay+org@example.com"), ), }, // Update email { Config: providerConfig + ` -resource "quay_organization" "test" { - name = "test2" - email = "quay+test2@example.com" +resource "quay_organization" "org" { + name = "org2" + email = "quay+org2@example.com" } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("quay_organization.test", "name", "test2"), - resource.TestCheckResourceAttr("quay_organization.test", "email", "quay+test2@example.com"), + resource.TestCheckResourceAttr("quay_organization.org", "name", "org2"), + resource.TestCheckResourceAttr("quay_organization.org", "email", "quay+org2@example.com"), ), }, // Update name and email { Config: providerConfig + ` -resource "quay_organization" "test" { - name = "test3" - email = "quay+test3@example.com" +resource "quay_organization" "org" { + name = "org3" + email = "quay+org3@example.com" } `, Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("quay_organization.test", "name", "test3"), - resource.TestCheckResourceAttr("quay_organization.test", "email", "quay+test3@example.com"), + resource.TestCheckResourceAttr("quay_organization.org", "name", "org3"), + resource.TestCheckResourceAttr("quay_organization.org", "email", "quay+org3@example.com"), ), }, }, diff --git a/internal/provider/organization_robot_resource.go b/internal/provider/organization_robot_resource.go new file mode 100644 index 0000000..f4e0450 --- /dev/null +++ b/internal/provider/organization_robot_resource.go @@ -0,0 +1,200 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Modifications copyright (c) Enthought, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package provider + +import ( + "context" + "encoding/json" + "fmt" + "io" + "strings" + + "github.com/enthought/terraform-provider-quay/quay_api" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + + "terraform-provider-quay/internal/resource_organization_robot" +) + +var ( + _ resource.Resource = (*organizationRobotResource)(nil) + _ resource.ResourceWithConfigure = (*organizationRobotResource)(nil) + _ resource.ResourceWithImportState = (*organizationRobotResource)(nil) +) + +func NewOrganizationRobotResource() resource.Resource { + return &organizationRobotResource{} +} + +type organizationRobotResource struct { + client *quay_api.APIClient +} + +type organizationRobotModelJSON struct { + Created string `json:"created"` + Description string `json:"description"` + LastAccessed string `json:"last_accessed"` + Name string `json:"name"` + Token string `json:"token"` +} + +func (r *organizationRobotResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization_robot" +} + +func (r *organizationRobotResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resource_organization_robot.OrganizationRobotResourceSchema(ctx) +} + +func (r *organizationRobotResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resource_organization_robot.OrganizationRobotModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + orgName := data.Orgname.ValueString() + robotName := data.Name.ValueString() + robotDescription := data.Description.ValueStringPointer() + + // Create robot + newRobot := quay_api.CreateRobot{ + Description: robotDescription, + UnstructuredMetadata: nil, + } + _, err := r.client.RobotAPI.CreateOrgRobot(context.Background(), orgName, robotName).Body(newRobot).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error creating Quay org robot", "Could not create Quay org robot, unexpected error: "+errDetail) + return + } + + // Set robot full name + data.Fullname = types.StringValue(orgName + "+" + robotName) + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *organizationRobotResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resource_organization_robot.OrganizationRobotModel + var resRobotData organizationRobotModelJSON + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + orgName := data.Orgname.ValueString() + robotName := data.Name.ValueString() + + // Get robot + httpRes, err := r.client.RobotAPI.GetOrgRobot(context.Background(), orgName, robotName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error reading Quay org robot", "Could not read Quay org robot, unexpected error: "+errDetail) + return + } + body, err := io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resRobotData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + + // Set Description + data.Description = types.StringValue(resRobotData.Description) + + // Set robot full name + data.Fullname = types.StringValue(orgName + "+" + robotName) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *organizationRobotResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data resource_organization_robot.OrganizationRobotModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *organizationRobotResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resource_organization_robot.OrganizationRobotModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + orgName := data.Orgname.ValueString() + robotName := data.Name.ValueString() + + // Delete robot + _, err := r.client.RobotAPI.DeleteOrgRobot(context.Background(), orgName, robotName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error deleting Quay org robot", "Could not delete Quay org robot, unexpected error: "+errDetail) + return + } +} + +func (r *organizationRobotResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*quay_api.APIClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *quay_api.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + + r.client = client +} + +func (r *organizationRobotResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idSplit := strings.Split(req.ID, "+") + if len(idSplit) != 2 || idSplit[0] == "" || idSplit[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format orgname+robotname. Got: %q", req.ID), + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("orgname"), idSplit[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idSplit[1])...) +} diff --git a/internal/provider/organization_robot_resource_test.go b/internal/provider/organization_robot_resource_test.go new file mode 100644 index 0000000..3eb9366 --- /dev/null +++ b/internal/provider/organization_robot_resource_test.go @@ -0,0 +1,65 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccOrganizationRobotResource(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: providerConfig + ` +resource "quay_organization" "org_robot" { + name = "org_robot" + email = "quay+org_robot@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_robot.name + description = "test" +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_robot.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "orgname", "org_robot"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "description", "test"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "fullname", "org_robot+test"), + ), + }, + // Import + { + ResourceName: "quay_organization_robot.test", + ImportState: true, + ImportStateId: "org_robot+test", + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "name", + }, + // Replace resource + { + Config: providerConfig + ` +resource "quay_organization" "org_robot" { + name = "org_robot" + email = "quay+org_robot@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test2" + orgname = quay_organization.org_robot.name + description = "test2" +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_robot.test", "name", "test2"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "orgname", "org_robot"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "description", "test2"), + resource.TestCheckResourceAttr("quay_organization_robot.test", "fullname", "org_robot+test2"), + ), + }, + }, + }) +} diff --git a/internal/provider/organization_team_resource.go b/internal/provider/organization_team_resource.go new file mode 100644 index 0000000..89813a5 --- /dev/null +++ b/internal/provider/organization_team_resource.go @@ -0,0 +1,334 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Modifications copyright (c) Enthought, Inc. +// SPDX-License-Identifier: BSD-3-Clause + +package provider + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "io" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/enthought/terraform-provider-quay/quay_api" + "terraform-provider-quay/internal/resource_organization_team" +) + +var ( + _ resource.Resource = (*organizationTeamResource)(nil) + _ resource.ResourceWithConfigure = (*organizationTeamResource)(nil) + _ resource.ResourceWithImportState = (*organizationTeamResource)(nil) +) + +func NewOrganizationTeamResource() resource.Resource { + return &organizationTeamResource{} +} + +type organizationTeamResource struct { + client *quay_api.APIClient +} + +type teamModelJSON struct { + Name string `json:"name"` + Members []teamMemberModelJSON `json:"members"` + CanEdit bool `json:"can_edit"` +} + +type teamMemberModelJSON struct { + Name string `json:"name"` + Kind string `json:"kind"` + IsRobot bool `json:"is_robot"` + Invited bool `json:"invited"` +} + +func (r *organizationTeamResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_organization_team" +} + +func (r *organizationTeamResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = resource_organization_team.OrganizationTeamResourceSchema(ctx) +} + +func (r *organizationTeamResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data resource_organization_team.OrganizationTeamModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + teamName := data.Name.ValueString() + orgName := data.Orgname.ValueString() + role := data.Role.ValueString() + description := data.Description.ValueString() + + // Create team + newTeam := quay_api.TeamDescription{ + Role: role, + Description: &description, + } + _, err := r.client.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(newTeam).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error creating Quay team", "Could not create Quay team, unexpected error: "+errDetail) + return + } + + // Add team members + if !data.Members.IsNull() { + elements := make([]types.String, 0, len(data.Members.Elements())) + diags := data.Members.ElementsAs(ctx, &elements, false) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + for _, member := range elements { + _, err = r.client.TeamAPI.UpdateOrganizationTeamMember(context.Background(), orgName, member.ValueString(), teamName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error creating Quay team", "Could not create Quay team, unexpected error: "+errDetail) + return + } + } + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *organizationTeamResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data resource_organization_team.OrganizationTeamModel + var resTeamData teamModelJSON + var resOrgData organizationModelJSON + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + // Create variables + teamName := data.Name.ValueString() + orgName := data.Orgname.ValueString() + + // Get members + httpRes, err := r.client.TeamAPI.GetOrganizationTeamMembers(context.Background(), orgName, teamName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error reading Quay team", "Could not read Quay team, unexpected error: "+errDetail) + return + } + + body, err := io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resTeamData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + + // Set members + var memList []string + for _, member := range resTeamData.Members { + memList = append(memList, member.Name) + } + members, diags := types.ListValueFrom(ctx, types.StringType, memList) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // handle case where `members` is set to [] + if len(data.Members.Elements()) > 0 || len(members.Elements()) > 0 { + data.Members = members + } + + // Get org data + httpRes, err = r.client.OrganizationAPI.GetOrganization(context.Background(), orgName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error reading Quay team", "Could not read Quay team, unexpected error: "+errDetail) + return + } + + body, err = io.ReadAll(httpRes.Body) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + err = json.Unmarshal(body, &resOrgData) + if err != nil { + resp.Diagnostics.AddError( + "Error reading Quay team", + "Could not read Quay team, unexpected error: "+err.Error()) + return + } + + // Set Role + data.Role = types.StringValue(resOrgData.Teams[teamName].Role) + + // Set Description + data.Description = types.StringValue(resOrgData.Teams[teamName].Description) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *organizationTeamResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var dataState resource_organization_team.OrganizationTeamModel + var dataPlan resource_organization_team.OrganizationTeamModel + var apiErr *quay_api.GenericOpenAPIError + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &dataState)...) + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &dataPlan)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + teamName := dataPlan.Name.ValueString() + orgName := dataPlan.Orgname.ValueString() + + // Update members + if !dataPlan.Members.Equal(dataState.Members) { + elementsPlan := make([]string, 0, len(dataPlan.Members.Elements())) + diags := dataPlan.Members.ElementsAs(ctx, &elementsPlan, false) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + elementsState := make([]string, 0, len(dataState.Members.Elements())) + diags = dataState.Members.ElementsAs(ctx, &elementsState, false) + + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // add team members + for _, member := range subtractStringSlice(elementsPlan, elementsState) { + _, err := r.client.TeamAPI.UpdateOrganizationTeamMember(context.Background(), orgName, member, teamName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error updating Quay team", "Could not update Quay team, unexpected error: "+errDetail) + return + } + } + + // remove team members + for _, member := range subtractStringSlice(elementsState, elementsPlan) { + _, err := r.client.TeamAPI.DeleteOrganizationTeamMember(context.Background(), orgName, member, teamName).Execute() + if err != nil { + // handle case where team member no longer exists + if errors.As(err, &apiErr) { + if model, ok := apiErr.Model().(quay_api.ApiError); ok { + if model.Status == 404 { + continue + } + } + } + + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error updating Quay team", "Could not update Quay team, unexpected error: "+errDetail) + return + } + } + } + + // Update role and description + updateTeam := quay_api.TeamDescription{ + Role: dataPlan.Role.ValueString(), + Description: dataPlan.Description.ValueStringPointer(), + } + _, err := r.client.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(updateTeam).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error creating Quay team", "Could not create Quay team, unexpected error: "+errDetail) + return + } + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &dataPlan)...) +} + +func (r *organizationTeamResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data resource_organization_team.OrganizationTeamModel + + // Read Terraform prior state data into the model + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + // Create variables + teamName := data.Name.ValueString() + orgName := data.Orgname.ValueString() + + // Delete API call logic + _, err := r.client.TeamAPI.DeleteOrganizationTeam(context.Background(), orgName, teamName).Execute() + if err != nil { + errDetail := handleQuayAPIError(err) + resp.Diagnostics.AddError("Error deleting Quay team", "Could not delete Quay team, unexpected error: "+errDetail) + return + } +} + +func (r *organizationTeamResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*quay_api.APIClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *quay_api.APIClient, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + + r.client = client +} + +func (r *organizationTeamResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + idSplit := strings.Split(req.ID, "+") + if len(idSplit) != 2 || idSplit[0] == "" || idSplit[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format orgname+teamname. Got: %q", req.ID), + ) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("orgname"), idSplit[0])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), idSplit[1])...) +} diff --git a/internal/provider/organization_team_resource_test.go b/internal/provider/organization_team_resource_test.go new file mode 100644 index 0000000..57a40c7 --- /dev/null +++ b/internal/provider/organization_team_resource_test.go @@ -0,0 +1,282 @@ +package provider + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccOrganizationTeamResource(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create and Read + { + Config: providerConfig + ` +resource "quay_organization" "org_team" { + name = "org_team" + email = "quay+org_team@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_team.name +} + +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team.name + role = "member" + description = "test" + members = [ + quay_organization_robot.test.fullname + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "member"), + resource.TestCheckResourceAttr("quay_organization_team.test", "description", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "members.0", "org_team+test"), + ), + }, + // Import + { + ResourceName: "quay_organization_team.test", + ImportState: true, + ImportStateId: "org_team+test", + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "name", + }, + // Update + { + Config: providerConfig + ` +resource "quay_organization" "org_team" { + name = "org_team" + email = "quay+org_team@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_team.name +} + +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team.name + role = "admin" + description = "test2" + members = [ + quay_organization_robot.test.fullname + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "admin"), + resource.TestCheckResourceAttr("quay_organization_team.test", "description", "test2"), + resource.TestCheckResourceAttr("quay_organization_team.test", "members.0", "org_team+test"), + ), + }, + // Replace resource + { + Config: providerConfig + ` +resource "quay_organization" "org_team" { + name = "org_team" + email = "quay+org_team@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_team.name +} + +resource "quay_organization_team" "test" { + name = "test2" + orgname = quay_organization.org_team.name + role = "admin" + description = "test2" + members = [ + quay_organization_robot.test.fullname + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test2"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "admin"), + resource.TestCheckResourceAttr("quay_organization_team.test", "description", "test2"), + resource.TestCheckResourceAttr("quay_organization_team.test", "members.0", "org_team+test"), + ), + }, + }, + }) +} + +func TestAccOrganizationTeamResourceMembers(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + ProtoV6ProviderFactories: testAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Create team with no members + { + Config: providerConfig + ` +resource "quay_organization" "org_team_members" { + name = "org_team_members" + email = "quay+org_team_members@example.com" +} +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_members.name + role = "member" +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team_members"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "member"), + ), + }, + // Set members to empty list + { + Config: providerConfig + ` +resource "quay_organization" "org_team_members" { + name = "org_team_members" + email = "quay+org_team_members@example.com" +} +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_members.name + role = "member" + members = [] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team_members"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "member"), + ), + }, + // Add team member + { + Config: providerConfig + ` +resource "quay_organization" "org_team_members" { + name = "org_team_members" + email = "quay+org_team_members@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_team_members.name +} + +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_members.name + role = "member" + members = [ + quay_organization_robot.test.fullname + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team_members"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "member"), + resource.TestCheckResourceAttr("quay_organization_team.test", "members.0", "org_team_members+test"), + ), + }, + // Add team member + { + Config: providerConfig + ` +resource "quay_organization" "org_team_members" { + name = "org_team_members" + email = "quay+org_team_members@example.com" +} + +resource "quay_organization_robot" "test" { + name = "test" + orgname = quay_organization.org_team_members.name +} + +resource "quay_organization_robot" "test2" { + name = "test2" + orgname = quay_organization.org_team_members.name +} + +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_members.name + role = "member" + members = [ + quay_organization_robot.test.fullname, + quay_organization_robot.test2.fullname + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team_members"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "member"), + resource.TestCheckResourceAttr("quay_organization_team.test", "members.0", "org_team_members+test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "members.1", "org_team_members+test2"), + ), + }, + // Remove team member by removing robot resource first (Quay will automatically remove the robot from the team) + { + Config: providerConfig + ` +resource "quay_organization" "org_team_members" { + name = "org_team_members" + email = "quay+org_team_members@example.com" +} + +resource "quay_organization_robot" "test2" { + name = "test2" + orgname = quay_organization.org_team_members.name +} + +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_members.name + role = "member" + members = [ + quay_organization_robot.test2.fullname + ] +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team_members"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "member"), + resource.TestCheckResourceAttr("quay_organization_team.test", "members.0", "org_team_members+test2"), + ), + }, + // Remove team member without removing robot resource + { + Config: providerConfig + ` +resource "quay_organization" "org_team_members" { + name = "org_team_members" + email = "quay+org_team_members@example.com" +} + +resource "quay_organization_robot" "test2" { + name = "test2" + orgname = quay_organization.org_team_members.name +} + +resource "quay_organization_team" "test" { + name = "test" + orgname = quay_organization.org_team_members.name + role = "member" +} +`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("quay_organization_team.test", "name", "test"), + resource.TestCheckResourceAttr("quay_organization_team.test", "orgname", "org_team_members"), + resource.TestCheckResourceAttr("quay_organization_team.test", "role", "member"), + ), + }, + }, + }) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 7d276fe..05f2c88 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -157,5 +157,7 @@ func (p *quayProvider) DataSources(_ context.Context) []func() datasource.DataSo func (p *quayProvider) Resources(_ context.Context) []func() resource.Resource { return []func() resource.Resource{ NewOrganizationResource, + NewOrganizationTeamResource, + NewOrganizationRobotResource, } } diff --git a/internal/resource_organization_robot/organization_robot_resource_gen.go b/internal/resource_organization_robot/organization_robot_resource_gen.go new file mode 100644 index 0000000..777440e --- /dev/null +++ b/internal/resource_organization_robot/organization_robot_resource_gen.go @@ -0,0 +1,58 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package resource_organization_robot + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func OrganizationRobotResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Text description", + MarkdownDescription: "Text description", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Default: stringdefault.StaticString(""), + }, + "fullname": schema.StringAttribute{ + Computed: true, + Description: "Robot full name", + MarkdownDescription: "Robot full name", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Robot short name", + MarkdownDescription: "Robot short name", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "orgname": schema.StringAttribute{ + Required: true, + Description: "Organization name", + MarkdownDescription: "Organization name", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + } +} + +type OrganizationRobotModel struct { + Description types.String `tfsdk:"description"` + Fullname types.String `tfsdk:"fullname"` + Name types.String `tfsdk:"name"` + Orgname types.String `tfsdk:"orgname"` +} diff --git a/internal/resource_organization_team/organization_team_resource_gen.go b/internal/resource_organization_team/organization_team_resource_gen.go new file mode 100644 index 0000000..3cefe9e --- /dev/null +++ b/internal/resource_organization_team/organization_team_resource_gen.go @@ -0,0 +1,67 @@ +// Code generated by terraform-plugin-framework-generator DO NOT EDIT. + +package resource_organization_team + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +func OrganizationTeamResourceSchema(ctx context.Context) schema.Schema { + return schema.Schema{ + Attributes: map[string]schema.Attribute{ + "description": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "Markdown description", + MarkdownDescription: "Markdown description", + Default: stringdefault.StaticString(""), + }, + "members": schema.ListAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "List of team members", + MarkdownDescription: "List of team members", + }, + "name": schema.StringAttribute{ + Required: true, + Description: "Team name", + MarkdownDescription: "Team name", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "orgname": schema.StringAttribute{ + Required: true, + Description: "Organization name", + MarkdownDescription: "Organization name", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "role": schema.StringAttribute{ + Required: true, + Description: "Team permission. Should be admin, creator, or member.", + MarkdownDescription: "Team permission. Should be admin, creator, or member.", + Validators: []validator.String{ + stringvalidator.OneOf([]string{"admin", "creator", "member"}...), + }, + }, + }, + } +} + +type OrganizationTeamModel struct { + Description types.String `tfsdk:"description"` + Members types.List `tfsdk:"members"` + Name types.String `tfsdk:"name"` + Orgname types.String `tfsdk:"orgname"` + Role types.String `tfsdk:"role"` +} diff --git a/quay_api/test/api_robot_test.go b/quay_api/test/api_robot_test.go index 631d72e..f0e734f 100644 --- a/quay_api/test/api_robot_test.go +++ b/quay_api/test/api_robot_test.go @@ -1,39 +1,44 @@ -/* -Quay Frontend - -Testing RobotAPIService - -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); - package quay_api import ( "context" - openapiclient "github.com/enthought/terraform-provider-quay/quay_api" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" + + "github.com/enthought/terraform-provider-quay/quay_api" ) func Test_quay_api_RobotAPIService(t *testing.T) { - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) + configuration := newConfiguration() + apiClient := quay_api.NewAPIClient(configuration) t.Run("Test RobotAPIService CreateOrgRobot", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var orgname string - var robotShortname string - - httpRes, err := apiClient.RobotAPI.CreateOrgRobot(context.Background(), orgname, robotShortname).Execute() - - require.Nil(t, err) - assert.Equal(t, 200, httpRes.StatusCode) - + orgName := "create-org-robot" + robotName := "create" + robotDescription := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create robot + newRobot := quay_api.CreateRobot{ + Description: &robotDescription, + UnstructuredMetadata: nil, + } + httpRes, err = apiClient.RobotAPI.CreateOrgRobot(context.Background(), orgName, robotName).Body(newRobot).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) }) t.Run("Test RobotAPIService CreateUserRobot", func(t *testing.T) { @@ -50,17 +55,35 @@ func Test_quay_api_RobotAPIService(t *testing.T) { }) t.Run("Test RobotAPIService DeleteOrgRobot", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var orgname string - var robotShortname string - - httpRes, err := apiClient.RobotAPI.DeleteOrgRobot(context.Background(), orgname, robotShortname).Execute() - - require.Nil(t, err) - assert.Equal(t, 200, httpRes.StatusCode) - + orgName := "delete-org-robot" + robotName := "delete" + robotDescription := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create robot + newRobot := quay_api.CreateRobot{ + Description: &robotDescription, + UnstructuredMetadata: nil, + } + httpRes, err = apiClient.RobotAPI.CreateOrgRobot(context.Background(), orgName, robotName).Body(newRobot).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Delete robot + httpRes, err = apiClient.RobotAPI.DeleteOrgRobot(context.Background(), orgName, robotName).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 204, httpRes.StatusCode) }) t.Run("Test RobotAPIService DeleteUserRobot", func(t *testing.T) { @@ -77,17 +100,35 @@ func Test_quay_api_RobotAPIService(t *testing.T) { }) t.Run("Test RobotAPIService GetOrgRobot", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var orgname string - var robotShortname string - - httpRes, err := apiClient.RobotAPI.GetOrgRobot(context.Background(), orgname, robotShortname).Execute() - - require.Nil(t, err) + orgName := "get-org-robot" + robotName := "get" + robotDescription := "test" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create robot + newRobot := quay_api.CreateRobot{ + Description: &robotDescription, + UnstructuredMetadata: nil, + } + httpRes, err = apiClient.RobotAPI.CreateOrgRobot(context.Background(), orgName, robotName).Body(newRobot).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Get robot + httpRes, err = apiClient.RobotAPI.GetOrgRobot(context.Background(), orgName, robotName).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) - }) t.Run("Test RobotAPIService GetOrgRobotPermissions", func(t *testing.T) { diff --git a/quay_api/test/api_team_test.go b/quay_api/test/api_team_test.go index 1b56a87..0f197b1 100644 --- a/quay_api/test/api_team_test.go +++ b/quay_api/test/api_team_test.go @@ -1,54 +1,92 @@ -/* -Quay Frontend - -Testing TeamAPIService - -*/ - -// Code generated by OpenAPI Generator (https://openapi-generator.tech); - package quay_api import ( "context" - openapiclient "github.com/enthought/terraform-provider-quay/quay_api" + "testing" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "testing" + + "github.com/enthought/terraform-provider-quay/quay_api" ) func Test_quay_api_TeamAPIService(t *testing.T) { - - configuration := openapiclient.NewConfiguration() - apiClient := openapiclient.NewAPIClient(configuration) + configuration := newConfiguration() + apiClient := quay_api.NewAPIClient(configuration) t.Run("Test TeamAPIService DeleteOrganizationTeam", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var orgname string - var teamname string - - httpRes, err := apiClient.TeamAPI.DeleteOrganizationTeam(context.Background(), orgname, teamname).Execute() - - require.Nil(t, err) + orgName := "delete-org-team" + teamName := "delete" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create team + newTeam := quay_api.TeamDescription{ + Role: "admin", + Description: nil, + } + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(newTeam).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) + // Delete team + httpRes, err = apiClient.TeamAPI.DeleteOrganizationTeam(context.Background(), orgName, teamName).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 204, httpRes.StatusCode) }) t.Run("Test TeamAPIService DeleteOrganizationTeamMember", func(t *testing.T) { + orgName := "delete-org-team-member" + teamName := "deleteorgteammember" + robotShortname := "robot" + memberName := orgName + "+" + robotShortname + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create team + newTeam := quay_api.TeamDescription{ + Role: "admin", + Description: nil, + } + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(newTeam).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 200, httpRes.StatusCode) - t.Skip("skip test") // remove to run test - - var orgname string - var membername string - var teamname string - - httpRes, err := apiClient.TeamAPI.DeleteOrganizationTeamMember(context.Background(), orgname, membername, teamname).Execute() + // Create org robot + newCreateRobot := quay_api.NewCreateRobot() + httpRes, err = apiClient.RobotAPI.CreateOrgRobot(context.Background(), orgName, robotShortname).Body(*newCreateRobot).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) - require.Nil(t, err) + // Add org team member + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeamMember(context.Background(), orgName, memberName, teamName).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) + // Delete org team member + httpRes, err = apiClient.TeamAPI.DeleteOrganizationTeamMember(context.Background(), orgName, memberName, teamName).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 204, httpRes.StatusCode) }) t.Run("Test TeamAPIService DeleteTeamMemberEmailInvite", func(t *testing.T) { @@ -95,31 +133,83 @@ func Test_quay_api_TeamAPIService(t *testing.T) { }) t.Run("Test TeamAPIService GetOrganizationTeamMembers", func(t *testing.T) { + orgName := "get-org-team-members" + teamName := "getorgteammembers" + robotShortname := "robot" + memberName := orgName + "+" + robotShortname + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create team + newTeam := quay_api.TeamDescription{ + Role: "admin", + Description: nil, + } + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(newTeam).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 200, httpRes.StatusCode) - t.Skip("skip test") // remove to run test - - var orgname string - var teamname string + // Create org robot + newCreateRobot := quay_api.NewCreateRobot() + httpRes, err = apiClient.RobotAPI.CreateOrgRobot(context.Background(), orgName, robotShortname).Body(*newCreateRobot).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) - httpRes, err := apiClient.TeamAPI.GetOrganizationTeamMembers(context.Background(), orgname, teamname).Execute() + // Add org team member + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeamMember(context.Background(), orgName, memberName, teamName).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 200, httpRes.StatusCode) - require.Nil(t, err) + // Get org team members + httpRes, err = apiClient.TeamAPI.GetOrganizationTeamMembers(context.Background(), orgName, teamName).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) + // Verify response + data, err := unmarshallArbitraryJSON(httpRes) + require.Nil(t, err) + assert.Equal(t, memberName, data["members"].([]interface{})[0].(map[string]interface{})["name"]) }) t.Run("Test TeamAPIService GetOrganizationTeamPermissions", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var orgname string - var teamname string - - httpRes, err := apiClient.TeamAPI.GetOrganizationTeamPermissions(context.Background(), orgname, teamname).Execute() - - require.Nil(t, err) + orgName := "get-org-team-perm" + teamName := "getorgteamperm" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create team + newTeam := quay_api.TeamDescription{ + Role: "admin", + Description: nil, + } + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(newTeam).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) + // Get team permissions + httpRes, err = apiClient.TeamAPI.GetOrganizationTeamPermissions(context.Background(), orgName, teamName).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 200, httpRes.StatusCode) }) t.Run("Test TeamAPIService InviteTeamMemberEmail", func(t *testing.T) { @@ -138,32 +228,68 @@ func Test_quay_api_TeamAPIService(t *testing.T) { }) t.Run("Test TeamAPIService UpdateOrganizationTeam", func(t *testing.T) { - - t.Skip("skip test") // remove to run test - - var orgname string - var teamname string - - httpRes, err := apiClient.TeamAPI.UpdateOrganizationTeam(context.Background(), orgname, teamname).Execute() - - require.Nil(t, err) + orgName := "update-org-team" + teamName := "update" + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create team + newTeam := quay_api.TeamDescription{ + Role: "admin", + Description: nil, + } + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(newTeam).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) - }) t.Run("Test TeamAPIService UpdateOrganizationTeamMember", func(t *testing.T) { + orgName := "add-org-team-member" + teamName := "addorgteammember" + robotShortname := "robot" + memberName := orgName + "+" + robotShortname + + // Ensure org is destroyed + defer func(organization quay_api.ApiDeleteAdminedOrganizationRequest) { + _, err := organization.Execute() + handleQuayAPIError(t, err) + }(apiClient.OrganizationAPI.DeleteAdminedOrganization(context.Background(), orgName)) + + // Create org + newOrg := quay_api.NewNewOrg(orgName, orgName+"@example.com") + httpRes, err := apiClient.OrganizationAPI.CreateOrganization(context.Background()).Body(*newOrg).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) + + // Create team + newTeam := quay_api.TeamDescription{ + Role: "admin", + Description: nil, + } + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeam(context.Background(), orgName, teamName).Body(newTeam).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 200, httpRes.StatusCode) - t.Skip("skip test") // remove to run test - - var orgname string - var membername string - var teamname string - - httpRes, err := apiClient.TeamAPI.UpdateOrganizationTeamMember(context.Background(), orgname, membername, teamname).Execute() + // Create org robot + newCreateRobot := quay_api.NewCreateRobot() + httpRes, err = apiClient.RobotAPI.CreateOrgRobot(context.Background(), orgName, robotShortname).Body(*newCreateRobot).Execute() + handleQuayAPIError(t, err) + assert.Equal(t, 201, httpRes.StatusCode) - require.Nil(t, err) + // Add org team member + httpRes, err = apiClient.TeamAPI.UpdateOrganizationTeamMember(context.Background(), orgName, memberName, teamName).Execute() + handleQuayAPIError(t, err) assert.Equal(t, 200, httpRes.StatusCode) - }) }