diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 922ee27..e69de29 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +0,0 @@ -* @hashicorp/terraform-devex diff --git a/README.md b/README.md index 30a4027..39088ff 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,11 @@ - [ ] Task # Resources -- [ ] User Group - - [ ] Create - - [ ] Read - - [ ] Update - - [ ] Delete +- [x] User Group + - [x] Create + - [x] Read + - [x] Update + - [x] Delete - [ ] User - [ ] Create - [ ] Read @@ -56,3 +56,18 @@ - [ ] Read - [ ] Update - [ ] Delete + +# Development Notes + +## Local Go CLI + +When working on this provider, you can add this to the top of `go.mod` to use your local dev version of the CLI to test any changes: +``` +replace github.com/raksul/go-clickup => ../go-clickup +``` + +## Log levels + +```sh +TF_LOG=DEBUG terraform apply +``` diff --git a/docs/index.md b/docs/index.md index f1b3948..5100b2e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,8 +13,16 @@ description: |- ## Example Usage ```terraform -provider "clickcup" { - # example configuration here +terraform { + required_providers { + clickup = { + source = "catdevman/clickup" + } + } +} + +provider "clickup" { + api_token = "API_KEY" } ``` diff --git a/docs/resources/usergroup.md b/docs/resources/usergroup.md new file mode 100644 index 0000000..376fe81 --- /dev/null +++ b/docs/resources/usergroup.md @@ -0,0 +1,30 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "clickup_usergroup Resource - terraform-provider-clickup" +subcategory: "" +description: |- + User Group resource +--- + +# clickup_usergroup (Resource) + +User Group resource + + + + +## Schema + +### Required + +- `members` (List of Number) User Group Member Ids +- `name` (String) User Group name +- `team_id` (String) Team ID (Workspace) + +### Optional + +- `handle` (String) User Group handle + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/examples/README.md b/examples/README.md index 026c42c..21e229c 100644 --- a/examples/README.md +++ b/examples/README.md @@ -7,3 +7,9 @@ The document generation tool looks for files in the following locations by defau * **provider/provider.tf** example file for the provider index page * **data-sources/`full data source name`/data-source.tf** example file for the named data source page * **resources/`full resource name`/resource.tf** example file for the named data source page + +To generate the docs run: + +```sh +go generate ./... +``` diff --git a/examples/data-sources/scaffolding_example/data-source.tf b/examples/data-sources/scaffolding_example/data-source.tf deleted file mode 100644 index a852489..0000000 --- a/examples/data-sources/scaffolding_example/data-source.tf +++ /dev/null @@ -1,3 +0,0 @@ -data "scaffolding_example" "example" { - configurable_attribute = "some-value" -} diff --git a/examples/data-sources/usergroups/data-source.tf b/examples/data-sources/usergroups/data-source.tf new file mode 100644 index 0000000..534ed3d --- /dev/null +++ b/examples/data-sources/usergroups/data-source.tf @@ -0,0 +1,8 @@ +# List all User Groups. +data "clickup_usergroups" "all" { + team_id = 123 +} + +output "usergroups" { + value = data.clickup_usergroups.all.groups +} diff --git a/examples/provider-install-verification/main.tf b/examples/provider-install-verification/main.tf index 927632d..64ab47d 100644 --- a/examples/provider-install-verification/main.tf +++ b/examples/provider-install-verification/main.tf @@ -1,14 +1,14 @@ terraform { required_providers { clickup = { - version = "~> 0.0.1" - source = "hashicorp.io/catdevman/clickup" + version = "~> 0.0.1" + source = "hashicorp.io/catdevman/clickup" } } } variable "CLICKUP_API_KEY" { - type = string + type = string } provider "clickup" { @@ -17,60 +17,60 @@ provider "clickup" { data "clickup_teams" "teams" {} -data "clickup_usergroups" "groups"{} +data "clickup_usergroups" "groups" {} data "clickup_spaces" "spaces" { - team_id = "9014024487" + team_id = "9014024487" } data "clickup_space" "space" { - space_id = "90140051562" + space_id = "90140051562" } data "clickup_folders" "folders" { - space_id = "90140051562" + space_id = "90140051562" } data "clickup_folder" "folder" { - folder_id = "90140096619" + folder_id = "90140096619" } data "clickup_lists" "lists" { - folder_id = "90140103670" + folder_id = "90140103670" } -data "clickup_folderless_lists" "folderless_lists"{ - space_id = data.clickup_space.space.space.id +data "clickup_folderless_lists" "folderless_lists" { + space_id = data.clickup_space.space.space.id } output "outtie" { - value = data.clickup_teams.teams + value = data.clickup_teams.teams } output "outtie2" { - value = data.clickup_usergroups.groups + value = data.clickup_usergroups.groups } output "outtie3" { - value = data.clickup_spaces.spaces + value = data.clickup_spaces.spaces } output "outtie4" { - value = data.clickup_space.space + value = data.clickup_space.space } output "outtie5" { - value = data.clickup_folders.folders + value = data.clickup_folders.folders } output "outtie6" { - value = data.clickup_folder.folder + value = data.clickup_folder.folder } output "outtie7" { - value = data.clickup_lists.lists + value = data.clickup_lists.lists } output "outtie8" { - value = data.clickup_folderless_lists.folderless_lists + value = data.clickup_folderless_lists.folderless_lists } diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf index 84b6bca..03a2c4b 100644 --- a/examples/provider/provider.tf +++ b/examples/provider/provider.tf @@ -1,3 +1,11 @@ -provider "clickcup" { - # example configuration here +terraform { + required_providers { + clickup = { + source = "catdevman/clickup" + } + } +} + +provider "clickup" { + api_token = "API_KEY" } diff --git a/examples/resources/scaffolding_example/resource.tf b/examples/resources/scaffolding_example/resource.tf deleted file mode 100644 index 9ae3f57..0000000 --- a/examples/resources/scaffolding_example/resource.tf +++ /dev/null @@ -1,3 +0,0 @@ -resource "scaffolding_example" "example" { - configurable_attribute = "some-value" -} diff --git a/examples/resources/usergroups/resource.tf b/examples/resources/usergroups/resource.tf new file mode 100644 index 0000000..32fc779 --- /dev/null +++ b/examples/resources/usergroups/resource.tf @@ -0,0 +1,5 @@ +resource "clickup_usergroup" "test_group" { + name = "some-test-name" + team_id = "123" + members = [456] +} diff --git a/go.mod b/go.mod index 72ccbed..9cf7989 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,8 @@ module github.com/catdevman/terraform-provider-clickup -go 1.19 +go 1.21.1 + +toolchain go1.21.6 require ( github.com/hashicorp/terraform-plugin-docs v0.16.0 @@ -8,7 +10,7 @@ require ( github.com/hashicorp/terraform-plugin-go v0.18.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-testing v1.5.1 - github.com/raksul/go-clickup v0.0.0-20230724022611-03151315cb7f + github.com/raksul/go-clickup v0.0.0-20240205020557-253e1c35fda7 ) require ( diff --git a/go.sum b/go.sum index 7e1b0f6..6abddb5 100644 --- a/go.sum +++ b/go.sum @@ -6,9 +6,11 @@ github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFP github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8 h1:wPbRQzjjwFc0ih8puEVAOFGELsn1zoIIYdxvML7mDxA= github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0gYDMZ6Z5GRU7l58bNFSkPTFN6Yl12dsUlAZ8xy98g= github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= @@ -27,14 +29,20 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= github.com/go-git/go-billy/v5 v5.4.1 h1:Uwp5tDRkPr+l/TnbHOQzp+tmJfLceOlbVucgpTz8ix4= +github.com/go-git/go-billy/v5 v5.4.1/go.mod h1:vjbugF6Fz7JIflbVpl1hJsGjSHNltrSw45YK/ukIvQg= github.com/go-git/go-git/v5 v5.6.1 h1:q4ZRqQl4pR/ZJHc1L5CFjGA1a10u76aV1iC+nh+bHsk= +github.com/go-git/go-git/v5 v5.6.1/go.mod h1:mvyoL6Unz0PiTQrGQfSfiLFhBH1c1e84ylC2MDs4ee8= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -108,14 +116,20 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= 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.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/jhump/protoreflect v1.6.0/go.mod h1:eaTn3RZAmMBcV0fifFvlm6VHNz3wSkYyXYWUh7ymB74= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -143,21 +157,25 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= 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.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 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/raksul/go-clickup v0.0.0-20230724022611-03151315cb7f h1:MqKgg0fBGhiQM9hJN+Efi0ByYTfy+XkSdT56ra4LEBU= -github.com/raksul/go-clickup v0.0.0-20230724022611-03151315cb7f/go.mod h1:86YbdUWKNb7garCSTWW8tZD0g+p12na/p1pLONOB7+k= +github.com/raksul/go-clickup v0.0.0-20240205020557-253e1c35fda7 h1:XHWHrBkA3ZPdtuYKm7s9t4sDHE163f7c1oYalT9EPdU= +github.com/raksul/go-clickup v0.0.0-20240205020557-253e1c35fda7/go.mod h1:yV7laN3+dGErOC+KcJlmyqkLpvKT4Z8YkAcHkgGg1cc= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= 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.1.0 h1:Wvr9V0MxhjRbl3f9nMnKnFfiWTJmtECJ9Njkea3ysW0= +github.com/skeema/knownhosts v1.1.0/go.mod h1:sKFq3RD6/TKZkSWn8boUbDC7Qkgcv+8XXijpFO6roag= 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= @@ -168,6 +186,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -176,6 +195,7 @@ github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= 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/zclconf/go-cty v1.13.3 h1:m+b9q3YDbg6Bec5rr+KGy1MzEVzY/jC2X+YX4yqKtHI= github.com/zclconf/go-cty v1.13.3/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= @@ -230,6 +250,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/provider/provider.go b/internal/provider/provider.go index c902031..a51fc48 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -25,10 +25,8 @@ import ( "github.com/raksul/go-clickup/clickup" ) -// Ensure ScaffoldingProvider satisfies various provider interfaces. var _ provider.Provider = &ClickUpProvider{} -// ScaffoldingProvider defines the provider implementation. type ClickUpProvider struct { // version is set to the provider version on release, "dev" when the // provider is built and ran locally, and "test" when running acceptance @@ -81,7 +79,9 @@ func (p *ClickUpProvider) Configure(ctx context.Context, req provider.ConfigureR } func (p *ClickUpProvider) Resources(ctx context.Context) []func() resource.Resource { - return []func() resource.Resource{} + return []func() resource.Resource{ + usergroups.NewResource, + } } func (p *ClickUpProvider) DataSources(ctx context.Context) []func() datasource.DataSource { diff --git a/internal/provider/provider_test.go b/internal/provider/provider_test.go index b5201d5..f849bc8 100644 --- a/internal/provider/provider_test.go +++ b/internal/provider/provider_test.go @@ -10,6 +10,16 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) +const ( + // providerConfig is a shared configuration to combine with the actual + // test configuration so the client is properly configured. + providerConfig = ` +provider "clickup" { + api_token = "123" +} + ` +) + // testAccProtoV6ProviderFactories are used to instantiate a provider during // acceptance testing. The factory function will be invoked for every Terraform // CLI command executed to create a provider server to which the CLI can diff --git a/internal/service/usergroups/data_source.go b/internal/service/usergroups/data_source.go index 814ba23..d54877c 100644 --- a/internal/service/usergroups/data_source.go +++ b/internal/service/usergroups/data_source.go @@ -3,9 +3,11 @@ package usergroups import ( "context" "fmt" + "strings" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/raksul/go-clickup/clickup" ) @@ -42,20 +44,23 @@ func (c *ClickUpUserGroupsDataSource) Configure(ctx context.Context, req datasou } func (c *ClickUpUserGroupsDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "Starting Data Source Read") + var data ClickUpUserGroupsDataSourceModel var opts clickup.GetUserGroupsOptions resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } + if !data.TeamId.IsNull() { - opts.TeamID = data.TeamId.String() + opts.TeamID = strings.Trim(data.TeamId.String(), "\"") } groups, _, err := c.client.UserGroups.GetUserGroups(ctx, &opts) if err != nil { resp.Diagnostics.AddError( - "failed to make call to ClickUp API", + "during call to ClickUp API", fmt.Sprintf("err: %s", err), ) } @@ -64,7 +69,7 @@ func (c *ClickUpUserGroupsDataSource) Read(ctx context.Context, req datasource.R for _, g := range groups { group = ClickUpUserGroupDataSourceModel{ - Id: types.StringValue(g.ID), + Id: types.StringValue(fmt.Sprint(g.ID)), UserId: types.StringValue(fmt.Sprint(g.UserID)), Name: types.StringValue(g.Name), Handle: types.StringValue(g.Handle), @@ -79,25 +84,30 @@ func (c *ClickUpUserGroupsDataSource) Read(ctx context.Context, req datasource.R resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } -func getMembers(ctx context.Context, members []clickup.GroupMember) []ClickUpUserGroupMemberSourceModel { - mems := []ClickUpUserGroupMemberSourceModel{} +func getMembers(_ context.Context, members []clickup.GroupMember) []ClickUpUserGroupMemberSourceModel { + group_members := []ClickUpUserGroupMemberSourceModel{} for _, m := range members { mem := ClickUpUserGroupMemberSourceModel{ - ID: types.StringValue(fmt.Sprint(m.ID)), - Username: types.StringValue(m.Username), - Email: types.StringValue(m.Email), - Color: types.StringValue(m.Color), - Intials: types.StringValue(m.Initials), - ProfilePicture: types.StringValue(m.ProfilePicture), + ID: m.ID, + Username: m.Username, + Email: m.Email, + Color: m.Color, + Initials: m.Initials, + ProfilePicture: m.ProfilePicture, } - mems = append(mems, mem) + group_members = append(group_members, mem) } - return mems + return group_members } -// TODO: Figure out why avatar comes back as any -func getAvatar(ctx context.Context, avatar any) ClickUpUserGroupAvatarSourceModel { - return ClickUpUserGroupAvatarSourceModel{} +// TODO: Figure out why avatar comes back as null. +func getAvatar(_ context.Context, avatar clickup.UserGroupAvatar) ClickUpUserGroupAvatarSourceModel { + return ClickUpUserGroupAvatarSourceModel{ + AttachmentId: avatar.AttachmentId, + Color: avatar.Color, + Source: avatar.Source, + Icon: avatar.Icon, + } } diff --git a/internal/service/usergroups/model.go b/internal/service/usergroups/model.go index f31e7b2..aa6f719 100644 --- a/internal/service/usergroups/model.go +++ b/internal/service/usergroups/model.go @@ -1,6 +1,8 @@ package usergroups -import "github.com/hashicorp/terraform-plugin-framework/types" +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) type ClickUpUserGroupsDataSourceModel struct { TeamId types.String `tfsdk:"team_id"` @@ -19,17 +21,37 @@ type ClickUpUserGroupDataSourceModel struct { } type ClickUpUserGroupMemberSourceModel struct { - ID types.String `tfsdk:"id"` - Username types.String `tfsdk:"username"` - Email types.String `tfsdk:"email"` - Color types.String `tfsdk:"color"` - Intials types.String `tfsdk:"initials"` - ProfilePicture types.String `tfsdk:"profilePicture"` + ID int `tfsdk:"id"` + Username string `tfsdk:"username"` + Email string `tfsdk:"email"` + Color string `tfsdk:"color"` + Initials string `tfsdk:"initials"` + ProfilePicture string `tfsdk:"profile_picture"` } type ClickUpUserGroupAvatarSourceModel struct { - AttachmentId types.String `tfsdk:"attachment_id"` - Color types.String `tfsdk:"color"` - Source types.String `tfsdk:"source"` - Icon types.String `tfsdk:"icon"` + AttachmentId *string `tfsdk:"attachment_id"` + Color *string `tfsdk:"color"` + Source *string `tfsdk:"source"` + Icon *string `tfsdk:"icon"` +} + +type ClickUpUserGroupResourceModel struct { + Id types.String `tfsdk:"id"` + TeamId types.String `tfsdk:"team_id"` + UserId types.String `tfsdk:"userid"` + Name types.String `tfsdk:"name"` + Handle types.String `tfsdk:"handle"` + DateCreated types.String `tfsdk:"date_created"` + Initials types.String `tfsdk:"initials"` + Members []ClickUpUserGroupMemberSourceModel `tfsdk:"members"` + Avatar ClickUpUserGroupAvatarSourceModel `tfsdk:"avatar"` +} + +type ClickUpUserGroupCreateResourceModel struct { + ID types.String `tfsdk:"id"` + TeamID types.String `tfsdk:"team_id"` + Name types.String `tfsdk:"name"` + Handle types.String `tfsdk:"handle"` + Members []int `tfsdk:"members"` } diff --git a/internal/service/usergroups/resource.go b/internal/service/usergroups/resource.go new file mode 100644 index 0000000..5a69400 --- /dev/null +++ b/internal/service/usergroups/resource.go @@ -0,0 +1,398 @@ +package usergroups + +import ( + "context" + "fmt" + "io" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/raksul/go-clickup/clickup" +) + +var ( + _ resource.Resource = &ClickUpUserGroupsResource{} + _ resource.ResourceWithConfigure = &ClickUpUserGroupsResource{} + _ resource.ResourceWithImportState = &ClickUpUserGroupsResource{} +) + +func NewResource() resource.Resource { + return &ClickUpUserGroupsResource{} +} + +type ClickUpUserGroupsResource struct { + client *clickup.Client +} + +func (r *ClickUpUserGroupsResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_usergroup" +} + +// Configure adds the provider configured client to the resource. +func (r *ClickUpUserGroupsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*clickup.Client) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *clickup.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +// Schema defines the schema for the resource. +func (c *ClickUpUserGroupsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "User Group resource", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + MarkdownDescription: "User Group name", + Required: true, + }, + "handle": schema.StringAttribute{ + MarkdownDescription: "User Group handle", + Optional: true, + }, + "team_id": schema.StringAttribute{ + MarkdownDescription: "Team ID (Workspace)", + Required: true, + }, + // "user_id": schema.StringAttribute{ + // MarkdownDescription: "User ID - Owner of this group", + // Required: true, + // }, + "members": schema.ListAttribute{ + MarkdownDescription: "User Group Member Ids", + ElementType: types.Int64Type, + Required: true, + }, + }, + } +} + +// Create creates the resource and sets the initial Terraform state. +func (c *ClickUpUserGroupsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "Starting Resource Create") + + // Retrieve values from plan + var userGroup ClickUpUserGroupCreateResourceModel + diags := req.Plan.Get(ctx, &userGroup) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + var userGroupRequest clickup.CreateUserGroupRequest + + tflog.Debug(ctx, "Preparing to make members list") + for _, member := range userGroup.Members { + memberId := member + userGroupRequest.Members = append(userGroupRequest.Members, memberId) + } + userGroupRequest.Name = userGroup.Name.String() + + tflog.Debug(ctx, "Preparing to send API request") + newUserGroup, createResponse, err := c.client.UserGroups.CreateUserGroup(ctx, trimQuotes(userGroup.TeamID.String()), &userGroupRequest) + + if err != nil { + resp.Diagnostics.AddError( + "Could not create user group: "+err.Error(), + getResponseBody(ctx, createResponse), + ) + return + } + + tflog.Debug(ctx, "Processing response") + userGroup.ID = types.StringValue(newUserGroup.ID) + userGroup.Name = types.StringValue(trimQuotes(newUserGroup.Name)) + for userGroupIndex, userGroupMember := range newUserGroup.Members { + userGroup.Members[userGroupIndex] = userGroupMember.ID + } + + tflog.Debug(ctx, "Setting final state") + diags = resp.State.Set(ctx, userGroup) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Read refreshes the Terraform state with the latest data. +func (r *ClickUpUserGroupsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + tflog.Debug(ctx, "Starting Resource Read") + + // Get current state. + var state ClickUpUserGroupCreateResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + opts := &clickup.GetUserGroupsOptions{ + TeamID: trimQuotes(state.TeamID.String()), + GroupIDs: []string{trimQuotes(state.ID.String())}, + } + + // Get refreshed user group value from the API. + groups, _, err := r.client.UserGroups.GetUserGroups(ctx, opts) + if err != nil { + resp.Diagnostics.AddError( + "Error Reading ClickUp User Groups", + err.Error(), + ) + return + } + if len(groups) != 1 { + resp.Diagnostics.AddError( + "Wrong number of ClickUp User Groups returned from API", + "Expected 1, got: "+fmt.Sprint(len(groups))+" groups.", + ) + return + } + + // our API only returns a list, so we need to get out the first item. + var group = groups[0] + + state.ID = types.StringValue(group.ID) + state.Name = types.StringValue(group.Name) + if group.Handle != "" { + state.Handle = types.StringValue(group.Handle) + } + + var memberIDs []int + for _, member := range group.Members { + memberIDs = append(memberIDs, member.ID) + } + if len(memberIDs) > 0 { + state.Members = memberIDs + } + + // Set refreshed state. + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *ClickUpUserGroupsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + tflog.Debug(ctx, "Starting Resource Update") + + var plan ClickUpUserGroupCreateResourceModel + diags := req.Plan.Get(ctx, &plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // get the old state, so we can figure out the changes required. + var oldState ClickUpUserGroupCreateResourceModel + req.State.Get(ctx, &oldState) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + var membersToAdd []int + var membersToRemove []int + tflog.Debug(ctx, fmt.Sprintf("Total state members: %d", len(oldState.Members))) + tflog.Debug(ctx, fmt.Sprintf("Total plan members: %d", len(plan.Members))) + + // find the members in the plan that are not in the state. + // eg: the ones we want to add. + for _, member := range plan.Members { + found := false + + for _, stateMember := range oldState.Members { + if member == stateMember { + found = true + break + } + } + if !found { + tflog.Debug(ctx, fmt.Sprintf("Adding new member: %d", member)) + membersToAdd = append(membersToAdd, member) + } + } + // find the members in the STATE that are not in the plan. + // eg: the ones we want to remove. + for _, member := range oldState.Members { + found := false + + for _, stateMember := range plan.Members { + if member == stateMember { + found = true + break + } + } + + if !found { + tflog.Debug(ctx, fmt.Sprintf("Removing member: %d", member)) + membersToRemove = append(membersToRemove, member) + } + } + + tflog.Debug(ctx, fmt.Sprintf("Total members to add: %d", len(membersToAdd))) + tflog.Debug(ctx, fmt.Sprintf("Total members to remove: %d", len(membersToRemove))) + + opts := &clickup.UpdateUserGroupRequest{ + Name: trimQuotes(plan.Name.String()), + } + + if plan.Handle.String() != "" { + opts.Handle = plan.Handle.String() + } + if len(membersToAdd) > 0 { + opts.Members.Add = membersToAdd + } + if len(membersToRemove) > 0 { + opts.Members.Remove = membersToRemove + } + + updatedGroup, updateResponse, err := r.client.UserGroups.UpdateUserGroup(ctx, trimQuotes(oldState.ID.String()), opts) + if err != nil { + resp.Diagnostics.AddError( + "Could not update User Group, unexpected error: "+err.Error(), + getResponseBody(ctx, updateResponse), + ) + return + } + + memberIDs := make([]int, len(updatedGroup.Members)) + for i, member := range updatedGroup.Members { + memberIDs[i] = member.ID + } + tflog.Debug(ctx, fmt.Sprintf("Adding memebers to final state: %d", len(memberIDs))) + // plan.Members = append(plan.Members, memberIDs...) + plan.Members = memberIDs + + plan.Name = types.StringValue(updatedGroup.Name) + // plan.Handle = types.StringValue(trimQuotes(updatedGroup.Handle)) + // if updatedGroup.Handle != "" || updatedGroup.Handle != "" { + // plan.Handle = types.StringValue(trimQuotes(updatedGroup.Handle)) + // } + // if plan.Handle == types.StringValue("") { + // plan.Handle = types.StringValue("") + // } + + plan.ID = types.StringValue(updatedGroup.ID) + + diags = resp.State.Set(ctx, plan) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *ClickUpUserGroupsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "Starting Resource Delete") + + var state ClickUpUserGroupCreateResourceModel + diags := req.State.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + deleteResponse, err := r.client.UserGroups.DeleteUserGroup(ctx, trimQuotes(state.ID.String())) + if err != nil { + resp.Diagnostics.AddError( + "Could not delete User Group, unexpected error: "+err.Error(), + getResponseBody(ctx, deleteResponse), + ) + return + } +} + +func (r *ClickUpUserGroupsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + tflog.Debug(ctx, "Starting Resource Import") + teamID, groupId, err := splitId(req.ID) + if err != nil { + resp.Diagnostics.AddError( + "Error extracting values from import ID: "+err.Error(), + "", + ) + } + + // call read with id and teamID + opts := &clickup.GetUserGroupsOptions{ + TeamID: teamID, + GroupIDs: []string{groupId}, + } + + groups, readResponse, err := r.client.UserGroups.GetUserGroups(ctx, opts) + + if err != nil { + resp.Diagnostics.AddError( + "Error reading user group: "+err.Error(), + getResponseBody(ctx, readResponse), + ) + } + + if len(groups) == 0 { + resp.Diagnostics.AddError( + "No user group found with id: "+groupId, + getResponseBody(ctx, readResponse), + ) + } + + // Set the state + var userGroup ClickUpUserGroupCreateResourceModel + var group = groups[0] + userGroup.ID = types.StringValue(group.ID) + userGroup.Name = types.StringValue(group.Name) + userGroup.Members = make([]int, len(group.Members)) + for i, member := range group.Members { + userGroup.Members[i] = member.ID + } + userGroup.TeamID = types.StringValue(group.TeamID) + diags := resp.State.Set(ctx, &userGroup) + if diags.HasError() { + resp.Diagnostics.AddError( + fmt.Errorf("Error setting state: %v", diags).Error(), + "", + ) + } + +} + +// 'id' looks like: 'team_id/id'. +func splitId(id string) (string, string, error) { + splitLine := strings.Split(id, "/") + if len(splitLine) != 2 { + return "", "", fmt.Errorf("invalid ID. Use the format: team_id/group_id") + } + var team_id = splitLine[0] + var group_id = splitLine[1] + return team_id, group_id, nil +} + +func trimQuotes(s string) string { + return strings.Trim(s, "\"") +} + +func getResponseBody(_ context.Context, res *clickup.Response) string { + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return "There was an error getting the response body" + } + + return string(body) +} diff --git a/main.go b/main.go index d99c5b6..b54c59b 100644 --- a/main.go +++ b/main.go @@ -38,7 +38,6 @@ func main() { flag.Parse() opts := providerserver.ServeOpts{ - // TODO: Update this string with the published name of your provider. Address: "registry.terraform.io/catdevman/clickup", Debug: debug, }