diff --git a/docs/data-sources/cloudaccount_aws.md b/docs/data-sources/cloudaccount_aws.md
index 4dd46c1d..7148feba 100644
--- a/docs/data-sources/cloudaccount_aws.md
+++ b/docs/data-sources/cloudaccount_aws.md
@@ -33,6 +33,7 @@ data "spectrocloud_cloudaccount_aws" "aws_account" {
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `depends` (String)
- `id` (String) ID of the AWS cloud account registered in Palette.
- `name` (String) Name of the AWS cloud account registered in Palette.
\ No newline at end of file
diff --git a/docs/data-sources/cloudaccount_azure.md b/docs/data-sources/cloudaccount_azure.md
index e89d529b..b7615825 100644
--- a/docs/data-sources/cloudaccount_azure.md
+++ b/docs/data-sources/cloudaccount_azure.md
@@ -33,6 +33,7 @@ data "spectrocloud_cloudaccount_azure" "azure_account" {
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `id` (String) ID of the Azure cloud account registered in Palette.
- `name` (String) Name of the Azure cloud account registered in Palette.
diff --git a/docs/data-sources/cloudaccount_custom.md b/docs/data-sources/cloudaccount_custom.md
index 6bcc4cda..ba2fee55 100644
--- a/docs/data-sources/cloudaccount_custom.md
+++ b/docs/data-sources/cloudaccount_custom.md
@@ -21,5 +21,6 @@ description: |-
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `id` (String) The unique identifier of the cloud account. Either `id` or `name` must be provided.
- `name` (String) The name of the cloud account. Either `id` or `name` must be provided.
diff --git a/docs/data-sources/cloudaccount_gcp.md b/docs/data-sources/cloudaccount_gcp.md
index 7275521f..4f08a644 100644
--- a/docs/data-sources/cloudaccount_gcp.md
+++ b/docs/data-sources/cloudaccount_gcp.md
@@ -33,5 +33,6 @@ data "spectrocloud_cloudaccount_gcp" "gcp_account" {
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `id` (String) ID of the GCP cloud account registered in Palette.
- `name` (String) Name of the GCP cloud account registered in Palette.
\ No newline at end of file
diff --git a/docs/data-sources/cloudaccount_maas.md b/docs/data-sources/cloudaccount_maas.md
index 48bf1b26..cba43d0a 100644
--- a/docs/data-sources/cloudaccount_maas.md
+++ b/docs/data-sources/cloudaccount_maas.md
@@ -17,6 +17,7 @@ description: |-
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `id` (String) The unique ID of the cloud account. Either `id` or `name` must be provided, but not both.
- `name` (String) The name of the cloud account. This can be used instead of `id` to retrieve the account details. Only one of `id` or `name` can be specified.
diff --git a/docs/data-sources/cloudaccount_openstack.md b/docs/data-sources/cloudaccount_openstack.md
index c6c9350c..b8d6fd7a 100644
--- a/docs/data-sources/cloudaccount_openstack.md
+++ b/docs/data-sources/cloudaccount_openstack.md
@@ -17,5 +17,6 @@ description: |-
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `id` (String) The unique ID of the OpenStack cloud account. Either `id` or `name` must be provided, but not both.
- `name` (String) The name of the OpenStack cloud account. Either `id` or `name` must be provided, but not both.
diff --git a/docs/data-sources/cloudaccount_tencent.md b/docs/data-sources/cloudaccount_tencent.md
index 4d7137d0..6a00916f 100644
--- a/docs/data-sources/cloudaccount_tencent.md
+++ b/docs/data-sources/cloudaccount_tencent.md
@@ -17,6 +17,7 @@ description: |-
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `id` (String) The unique ID of the Tencent cloud account. Either `id` or `name` must be provided, but not both.
- `name` (String) The name of the Tencent cloud account. Either `id` or `name` must be provided, but not both.
diff --git a/docs/data-sources/cloudaccount_vsphere.md b/docs/data-sources/cloudaccount_vsphere.md
index 13ab9834..86c7d718 100644
--- a/docs/data-sources/cloudaccount_vsphere.md
+++ b/docs/data-sources/cloudaccount_vsphere.md
@@ -17,5 +17,6 @@ description: |-
### Optional
+- `context` (String) The context of the cluster. Allowed values are `project` or `tenant` or ``.
- `id` (String) The unique ID of the vSphere cloud account. Either `id` or `name` must be provided, but not both.
- `name` (String) The name of the vSphere cloud account. Either `id` or `name` must be provided, but not both.
diff --git a/docs/data-sources/role.md b/docs/data-sources/role.md
index 54a2b780..eb810997 100644
--- a/docs/data-sources/role.md
+++ b/docs/data-sources/role.md
@@ -13,11 +13,19 @@ description: |-
## Example Usage
```terraform
-data "spectrocloud_role" "role1" {
- name = "Project Editor"
+data "spectrocloud_role" "role" {
+ name = "Resource Cluster Admin"
# (alternatively)
- # id = "5fd0ca727c411c71b55a359c"
+ # id = "66fbea622947f81fb62294ac"
+}
+
+output "role_id" {
+ value = data.spectrocloud_role.role.id
+}
+
+output "role_permissions" {
+ value = data.spectrocloud_role.role.permissions
}
```
@@ -31,3 +39,4 @@ data "spectrocloud_role" "role1" {
### Read-Only
- `id` (String) The ID of this resource.
+- `permissions` (Set of String) List of permissions associated with the role.
diff --git a/docs/data-sources/team.md b/docs/data-sources/team.md
new file mode 100644
index 00000000..bdeb3e36
--- /dev/null
+++ b/docs/data-sources/team.md
@@ -0,0 +1,42 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "spectrocloud_team Data Source - terraform-provider-spectrocloud"
+subcategory: ""
+description: |-
+
+---
+
+# spectrocloud_team (Data Source)
+
+
+
+## Example Usage
+
+```terraform
+data "spectrocloud_team" "team1" {
+ name = "team2"
+
+ # (alternatively)
+ # id = "5fd0ca727c411c71b55a359c"
+}
+
+output "team-id" {
+ value = data.spectrocloud_team.team1.id
+}
+
+output "team-role-ids" {
+ value = data.spectrocloud_team.team1.role_ids
+}
+```
+
+
+## Schema
+
+### Optional
+
+- `id` (String) The unique ID of the team. If provided, `name` cannot be used.
+- `name` (String) The name of the team. If provided, `id` cannot be used.
+
+### Read-Only
+
+- `role_ids` (List of String) The roles id's assigned to the team.
diff --git a/docs/resources/role.md b/docs/resources/role.md
new file mode 100644
index 00000000..e84df7ac
--- /dev/null
+++ b/docs/resources/role.md
@@ -0,0 +1,76 @@
+---
+page_title: "spectrocloud_role Resource - terraform-provider-spectrocloud"
+subcategory: ""
+description: |-
+ The role resource allows you to manage roles in Palette.
+---
+
+# spectrocloud_role (Resource)
+
+ The role resource allows you to manage roles in Palette.
+
+You can learn more about managing roles in Palette by reviewing the [Roles](https://docs.spectrocloud.com/glossary-all/#role) guide.
+
+## Example Usage
+
+```terraform
+variable "roles" {
+ type = list(string)
+ default = ["Cluster Admin", "Cluster Profile Editor"]
+}
+
+# Data source loop to retrieve multiple roles
+data "spectrocloud_role" "roles" {
+ for_each = toset(var.roles)
+ name = each.key
+}
+
+resource "spectrocloud_role" "custom_role" {
+ name = "Test Cluster Role"
+ type = "project"
+ permissions = flatten([for role in data.spectrocloud_role.roles : role.permissions])
+}
+```
+
+```
+### Importing existing role state & config
+
+```hcl
+# import existing user example
+ import {
+ to = spectrocloud_role.test_role
+ id = "{roleUID}"
+ }
+
+# To generate TF configuration.
+ terraform plan -generate-config-out=test_role.tf
+
+# To import State file
+ terraform import spectrocloud_role.test_role {roleUID}
+```
+
+
+## Schema
+
+### Required
+
+- `name` (String) The name of the role.
+- `permissions` (Set of String) The permission's assigned to the role.
+
+### Optional
+
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+- `type` (String) The role type. Allowed values are `project` or `tenant` or `project`
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `delete` (String)
+- `update` (String)
\ No newline at end of file
diff --git a/docs/resources/user.md b/docs/resources/user.md
new file mode 100644
index 00000000..aa97e7de
--- /dev/null
+++ b/docs/resources/user.md
@@ -0,0 +1,157 @@
+---
+page_title: "spectrocloud_user Resource - terraform-provider-spectrocloud"
+subcategory: ""
+description: |-
+ Create and manage projects in Palette.
+---
+
+# spectrocloud_user (Resource)
+
+ Create and manage projects in Palette.
+
+You can learn more about managing users in Palette by reviewing the [Users](https://docs.spectrocloud.com/user-management/) guide.
+
+## Example Usage
+
+An example of creating a user resource with assigned teams and custom roles in Palette.
+
+```hcl
+resource "spectrocloud_user" "user-test"{
+ first_name = "tf"
+ last_name = "test"
+ email = "test-tf@spectrocloud.com"
+ team_ids = [data.spectrocloud_team.team2.id]
+ project_role {
+ project_id = data.spectrocloud_project.default.id
+ role_ids = [for r in data.spectrocloud_role.app_roles : r.id]
+ }
+ project_role {
+ project_id = data.spectrocloud_project.ranjith.id
+ role_ids = [for r in data.spectrocloud_role.app_roles : r.id]
+ }
+
+ tenant_role = [for t in data.spectrocloud_role.tenant_roles : t.id]
+
+ workspace_role {
+ project_id = data.spectrocloud_project.default.id
+ workspace {
+ id = data.spectrocloud_workspace.workspace.id
+ role_ids = [for w in data.spectrocloud_role.workspace_roles : w.id]
+ }
+ workspace {
+ id = data.spectrocloud_workspace.workspace2.id
+ role_ids = ["66fbea622947f81fc26983e6"]
+ }
+ }
+
+ resource_role {
+ project_ids = [data.spectrocloud_project.default.id, data.spectrocloud_project.ranjith.id]
+ filter_ids = [data.spectrocloud_filter.filter.id]
+ role_ids = [for r in data.spectrocloud_role.resource_roles : r.id]
+ }
+
+ resource_role {
+ project_ids = [data.spectrocloud_project.ranjith.id]
+ filter_ids = [data.spectrocloud_filter.filter.id]
+ role_ids = [for re in data.spectrocloud_role.resource_roles_editor : re.id]
+ }
+
+}
+```
+
+The example below demonstrates how to create an user with only assigned teams.
+
+```hcl
+resource "spectrocloud_user" "user-test"{
+ first_name = "tf"
+ last_name = "test"
+ email = "test-tf@spectrocloud.com"
+ team_ids = [data.spectrocloud_team.team2.id]
+}
+
+
+```
+
+### Importing existing user states
+
+```hcl
+# import existing user example
+ import {
+ to = spectrocloud_user.test_user
+ id = "{userUID}"
+ }
+
+# To generate TF configuration.
+ terraform plan -generate-config-out=test_user.tf
+
+# To import State file
+ terraform import spectrocloud_user.test_user {userUID}
+```
+
+
+
+## Schema
+
+### Required
+
+- `email` (String) The email of the user.
+- `first_name` (String) The first name of the user.
+- `last_name` (String) The last name of the user.
+
+### Optional
+
+- `project_role` (Block Set) List of project roles to be associated with the user. (see [below for nested schema](#nestedblock--project_role))
+- `resource_role` (Block Set) (see [below for nested schema](#nestedblock--resource_role))
+- `team_ids` (List of String) The team id's assigned to the user.
+- `tenant_role` (Set of String) List of tenant role ids to be associated with the user.
+- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts))
+- `workspace_role` (Block Set) List of workspace roles to be associated with the user. (see [below for nested schema](#nestedblock--workspace_role))
+
+### Read-Only
+
+- `id` (String) The ID of this resource.
+
+
+### Nested Schema for `project_role`
+
+Required:
+
+- `project_id` (String) Project id to be associated with the user.
+- `role_ids` (Set of String) List of project role ids to be associated with the user.
+
+
+
+### Nested Schema for `resource_role`
+
+Required:
+
+- `filter_ids` (Set of String) List of filter ids.
+- `project_ids` (Set of String) Project id's to be associated with the user.
+- `role_ids` (Set of String) List of resource role ids to be associated with the user.
+
+
+
+### Nested Schema for `timeouts`
+
+Optional:
+
+- `create` (String)
+- `delete` (String)
+- `update` (String)
+
+
+
+### Nested Schema for `workspace_role`
+
+Required:
+
+- `project_id` (String) Project id to be associated with the user.
+- `workspace` (Block Set, Min: 1) List of workspace roles to be associated with the user. (see [below for nested schema](#nestedblock--workspace_role--workspace))
+
+
+### Nested Schema for `workspace_role.workspace`
+
+Required:
+
+- `id` (String) Workspace id to be associated with the user.
+- `role_ids` (Set of String) List of workspace role ids to be associated with the user.
\ No newline at end of file
diff --git a/examples/data-sources/spectrocloud_cloud_account/data-source.tf b/examples/data-sources/spectrocloud_cloud_account/data-source.tf
new file mode 100644
index 00000000..759b247e
--- /dev/null
+++ b/examples/data-sources/spectrocloud_cloud_account/data-source.tf
@@ -0,0 +1,9 @@
+data "spectrocloud_cloudaccount_aws" "aws_account" {
+ # id =
+ name = "ran-tf"
+ # context = "tenant"
+}
+
+output "aws_account" {
+ value = data.spectrocloud_cloudaccount_aws.aws_account
+}
\ No newline at end of file
diff --git a/examples/data-sources/spectrocloud_cloud_account/providers.tf b/examples/data-sources/spectrocloud_cloud_account/providers.tf
new file mode 100644
index 00000000..6343e60f
--- /dev/null
+++ b/examples/data-sources/spectrocloud_cloud_account/providers.tf
@@ -0,0 +1,18 @@
+terraform {
+ required_providers {
+ spectrocloud = {
+ version = ">= 0.13.2"
+ source = "spectrocloud/spectrocloud"
+ }
+ }
+}
+
+variable "sc_host" {}
+variable "sc_api_key" {}
+variable "sc_project_name" {}
+
+provider "spectrocloud" {
+ host = var.sc_host
+ api_key = var.sc_api_key
+ project_name = var.sc_project_name
+}
diff --git a/examples/data-sources/spectrocloud_cloud_account/terraform.template.tfvars b/examples/data-sources/spectrocloud_cloud_account/terraform.template.tfvars
new file mode 100644
index 00000000..c7e9d50b
--- /dev/null
+++ b/examples/data-sources/spectrocloud_cloud_account/terraform.template.tfvars
@@ -0,0 +1,4 @@
+# Spectro Cloud credentials
+sc_host = "{Enter Spectro Cloud API Host}" #e.g: api.spectrocloud.com (for SaaS)
+sc_api_key = "{Enter Spectro Cloud API Key}"
+sc_project_name = "{Enter Spectro Cloud Project Name}" #e.g: Default
\ No newline at end of file
diff --git a/examples/data-sources/spectrocloud_role/data-source.tf b/examples/data-sources/spectrocloud_role/data-source.tf
index cfe3bd33..dcf48362 100644
--- a/examples/data-sources/spectrocloud_role/data-source.tf
+++ b/examples/data-sources/spectrocloud_role/data-source.tf
@@ -1,6 +1,14 @@
-data "spectrocloud_role" "role1" {
- name = "Project Editor"
+data "spectrocloud_role" "role" {
+ name = "Resource Cluster Admin"
# (alternatively)
- # id = "5fd0ca727c411c71b55a359c"
+ # id = "66fbea622947f81fb62294ac"
}
+
+output "role_id" {
+ value = data.spectrocloud_role.role.id
+}
+
+output "role_permissions" {
+ value = data.spectrocloud_role.role.permissions
+}
\ No newline at end of file
diff --git a/examples/data-sources/spectrocloud_team/data-source.tf b/examples/data-sources/spectrocloud_team/data-source.tf
new file mode 100644
index 00000000..e208a80f
--- /dev/null
+++ b/examples/data-sources/spectrocloud_team/data-source.tf
@@ -0,0 +1,14 @@
+data "spectrocloud_team" "team1" {
+ name = "team2"
+
+ # (alternatively)
+ # id = "5fd0ca727c411c71b55a359c"
+}
+
+output "team-id" {
+ value = data.spectrocloud_team.team1.id
+}
+
+output "team-role-ids" {
+ value = data.spectrocloud_team.team1.role_ids
+}
\ No newline at end of file
diff --git a/examples/data-sources/spectrocloud_team/providers.tf b/examples/data-sources/spectrocloud_team/providers.tf
new file mode 100644
index 00000000..f3bdb2e0
--- /dev/null
+++ b/examples/data-sources/spectrocloud_team/providers.tf
@@ -0,0 +1,28 @@
+terraform {
+ required_providers {
+ spectrocloud = {
+ version = ">= 0.1"
+ source = "spectrocloud/spectrocloud"
+ }
+ }
+}
+
+variable "sc_host" {
+ description = "Spectro Cloud Endpoint"
+ default = "api.spectrocloud.com"
+}
+
+variable "sc_api_key" {
+ description = "Spectro Cloud API key"
+}
+
+variable "sc_project_name" {
+ description = "Spectro Cloud Project (e.g: Default)"
+ default = "Default"
+}
+
+provider "spectrocloud" {
+ host = var.sc_host
+ api_key = var.sc_api_key
+ project_name = var.sc_project_name
+}
diff --git a/examples/data-sources/spectrocloud_team/terraform.template.tfvars b/examples/data-sources/spectrocloud_team/terraform.template.tfvars
new file mode 100644
index 00000000..c7e9d50b
--- /dev/null
+++ b/examples/data-sources/spectrocloud_team/terraform.template.tfvars
@@ -0,0 +1,4 @@
+# Spectro Cloud credentials
+sc_host = "{Enter Spectro Cloud API Host}" #e.g: api.spectrocloud.com (for SaaS)
+sc_api_key = "{Enter Spectro Cloud API Key}"
+sc_project_name = "{Enter Spectro Cloud Project Name}" #e.g: Default
\ No newline at end of file
diff --git a/examples/resources/spectrocloud_role/providers.tf b/examples/resources/spectrocloud_role/providers.tf
new file mode 100644
index 00000000..f3bdb2e0
--- /dev/null
+++ b/examples/resources/spectrocloud_role/providers.tf
@@ -0,0 +1,28 @@
+terraform {
+ required_providers {
+ spectrocloud = {
+ version = ">= 0.1"
+ source = "spectrocloud/spectrocloud"
+ }
+ }
+}
+
+variable "sc_host" {
+ description = "Spectro Cloud Endpoint"
+ default = "api.spectrocloud.com"
+}
+
+variable "sc_api_key" {
+ description = "Spectro Cloud API key"
+}
+
+variable "sc_project_name" {
+ description = "Spectro Cloud Project (e.g: Default)"
+ default = "Default"
+}
+
+provider "spectrocloud" {
+ host = var.sc_host
+ api_key = var.sc_api_key
+ project_name = var.sc_project_name
+}
diff --git a/examples/resources/spectrocloud_role/resource.tf b/examples/resources/spectrocloud_role/resource.tf
new file mode 100644
index 00000000..9e00feec
--- /dev/null
+++ b/examples/resources/spectrocloud_role/resource.tf
@@ -0,0 +1,16 @@
+variable "roles" {
+ type = list(string)
+ default = ["Cluster Admin", "Cluster Profile Editor"]
+}
+
+# Data source loop to retrieve multiple roles
+data "spectrocloud_role" "roles" {
+ for_each = toset(var.roles)
+ name = each.key
+}
+
+resource "spectrocloud_role" "custom_role" {
+ name = "Test Cluster Role"
+ type = "project"
+ permissions = flatten([for role in data.spectrocloud_role.roles : role.permissions])
+}
\ No newline at end of file
diff --git a/examples/resources/spectrocloud_role/terraform.template.tfvars b/examples/resources/spectrocloud_role/terraform.template.tfvars
new file mode 100644
index 00000000..c7e9d50b
--- /dev/null
+++ b/examples/resources/spectrocloud_role/terraform.template.tfvars
@@ -0,0 +1,4 @@
+# Spectro Cloud credentials
+sc_host = "{Enter Spectro Cloud API Host}" #e.g: api.spectrocloud.com (for SaaS)
+sc_api_key = "{Enter Spectro Cloud API Key}"
+sc_project_name = "{Enter Spectro Cloud Project Name}" #e.g: Default
\ No newline at end of file
diff --git a/examples/resources/spectrocloud_user/data_source.tf b/examples/resources/spectrocloud_user/data_source.tf
new file mode 100644
index 00000000..b0d50e68
--- /dev/null
+++ b/examples/resources/spectrocloud_user/data_source.tf
@@ -0,0 +1,49 @@
+
+data "spectrocloud_project" "default" {
+ name = "Default"
+}
+
+data "spectrocloud_project" "ranjith" {
+ name = "ranjith"
+}
+
+data "spectrocloud_role" "app_roles" {
+ for_each = toset(var.app_role_var)
+ name = each.key
+}
+
+data "spectrocloud_role" "tenant_roles" {
+ for_each = toset(var.tenant_role_var)
+ name = each.key
+}
+
+data "spectrocloud_workspace" "workspace" {
+ name = "test-ws-tf"
+}
+
+data "spectrocloud_workspace" "workspace2" {
+ name = "test-ws-2"
+}
+
+data "spectrocloud_role" "workspace_roles" {
+ for_each = toset(var.workspace_role_var)
+ name = each.key
+}
+
+data "spectrocloud_filter" "filter" {
+ name = "test-tf"
+}
+
+data "spectrocloud_role" "resource_roles" {
+ for_each = toset(var.resource_role_var)
+ name = each.key
+}
+
+data "spectrocloud_role" "resource_roles_editor" {
+ for_each = toset(var.resource_role_editor_var)
+ name = each.key
+}
+
+data "spectrocloud_team" "team2" {
+ name = "team2"
+}
\ No newline at end of file
diff --git a/examples/resources/spectrocloud_user/providers.tf b/examples/resources/spectrocloud_user/providers.tf
new file mode 100644
index 00000000..4c109161
--- /dev/null
+++ b/examples/resources/spectrocloud_user/providers.tf
@@ -0,0 +1,14 @@
+terraform {
+ required_providers {
+ spectrocloud = {
+ version = ">= 0.1"
+ source = "spectrocloud/spectrocloud"
+ }
+ }
+}
+
+provider "spectrocloud" {
+ host = var.sc_host
+ api_key = var.sc_api_key
+ project_name = var.sc_project_name
+}
diff --git a/examples/resources/spectrocloud_user/resource.tf b/examples/resources/spectrocloud_user/resource.tf
new file mode 100644
index 00000000..3e5ea1b2
--- /dev/null
+++ b/examples/resources/spectrocloud_user/resource.tf
@@ -0,0 +1,53 @@
+resource "spectrocloud_user" "user-test" {
+ first_name = "tf"
+ last_name = "test"
+ email = "test-tf@spectrocloud.com"
+ team_ids = [data.spectrocloud_team.team2.id]
+ project_role {
+ project_id = data.spectrocloud_project.default.id
+ role_ids = [for r in data.spectrocloud_role.app_roles : r.id]
+ }
+ project_role {
+ project_id = data.spectrocloud_project.ranjith.id
+ role_ids = [for r in data.spectrocloud_role.app_roles : r.id]
+ }
+
+ tenant_role = [for t in data.spectrocloud_role.tenant_roles : t.id]
+
+ workspace_role {
+ project_id = data.spectrocloud_project.default.id
+ workspace {
+ id = data.spectrocloud_workspace.workspace.id
+ role_ids = [for w in data.spectrocloud_role.workspace_roles : w.id]
+ }
+ workspace {
+ id = data.spectrocloud_workspace.workspace2.id
+ role_ids = ["66fbea622947f81fc26983e6"]
+ }
+ }
+
+ resource_role {
+ project_ids = [data.spectrocloud_project.default.id, data.spectrocloud_project.ranjith.id]
+ filter_ids = [data.spectrocloud_filter.filter.id]
+ role_ids = [for r in data.spectrocloud_role.resource_roles : r.id]
+ }
+
+ resource_role {
+ project_ids = [data.spectrocloud_project.ranjith.id]
+ filter_ids = [data.spectrocloud_filter.filter.id]
+ role_ids = [for re in data.spectrocloud_role.resource_roles_editor : re.id]
+ }
+
+}
+
+# import existing user example
+#import {
+# to = spectrocloud_user.test_user
+# id = "66fcb5fe19eb6dc880776d59"
+#}
+
+# To generate TF configuration.
+#terraform plan -generate-config-out=test_user.tf
+
+# To import State file
+#terraform import spectrocloud_user.test_user 672c5ae21adfa1c28c9e37c9
\ No newline at end of file
diff --git a/examples/resources/spectrocloud_user/terraform.template.tfvars b/examples/resources/spectrocloud_user/terraform.template.tfvars
new file mode 100644
index 00000000..c7e9d50b
--- /dev/null
+++ b/examples/resources/spectrocloud_user/terraform.template.tfvars
@@ -0,0 +1,4 @@
+# Spectro Cloud credentials
+sc_host = "{Enter Spectro Cloud API Host}" #e.g: api.spectrocloud.com (for SaaS)
+sc_api_key = "{Enter Spectro Cloud API Key}"
+sc_project_name = "{Enter Spectro Cloud Project Name}" #e.g: Default
\ No newline at end of file
diff --git a/examples/resources/spectrocloud_user/variables.tf b/examples/resources/spectrocloud_user/variables.tf
new file mode 100644
index 00000000..428b7837
--- /dev/null
+++ b/examples/resources/spectrocloud_user/variables.tf
@@ -0,0 +1,43 @@
+variable "sc_host" {
+ description = "Spectro Cloud Endpoint"
+ default = "api.spectrocloud.com"
+}
+
+variable "sc_api_key" {
+ description = "Spectro Cloud API key"
+}
+
+variable "sc_project_name" {
+ description = "Spectro Cloud Project (e.g: Default)"
+ default = "Default"
+}
+
+variable "ssh_key_value" {
+ description = "ssh key value"
+ default = "ssh-rsa ...... == test@test.com"
+}
+
+variable "tenant_role_var" {
+ type = list(string)
+ default = ["Tenant Admin", "Tenant User Admin"]
+}
+
+variable "app_role_var" {
+ type = list(string)
+ default = ["App Deployment Admin", "App Deployment Editor"]
+}
+
+variable "workspace_role_var" {
+ type = list(string)
+ default = ["Workspace Admin", "Workspace Operator"]
+}
+
+variable "resource_role_var" {
+ type = list(string)
+ default = ["Resource Cluster Admin", "Resource Cluster Profile Admin"]
+}
+
+variable "resource_role_editor_var" {
+ type = list(string)
+ default = ["Resource Cluster Editor", "Resource Cluster Profile Editor"]
+}
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 7201d907..2fce15d5 100644
--- a/go.mod
+++ b/go.mod
@@ -12,7 +12,7 @@ require (
github.com/robfig/cron v1.2.0
github.com/spectrocloud/gomi v1.14.1-0.20240214074114-c19394812368
github.com/spectrocloud/hapi v1.14.1-0.20240214071352-81f589b1d86d
- github.com/spectrocloud/palette-sdk-go v0.0.0-20241022161234-3782615736bb
+ github.com/spectrocloud/palette-sdk-go v0.0.0-20241114040951-b4855be46579
github.com/stretchr/testify v1.9.0
gotest.tools v2.2.0+incompatible
k8s.io/api v0.23.5
diff --git a/go.sum b/go.sum
index f234f5f9..775b0be9 100644
--- a/go.sum
+++ b/go.sum
@@ -600,8 +600,8 @@ github.com/spectrocloud/gomi v1.14.1-0.20240214074114-c19394812368 h1:eY0BOyEbGu
github.com/spectrocloud/gomi v1.14.1-0.20240214074114-c19394812368/go.mod h1:LlZ9We4kDaELYi7Is0SVmnySuDhwphJLS6ZT4wXxFIk=
github.com/spectrocloud/hapi v1.14.1-0.20240214071352-81f589b1d86d h1:OMRbHxMJ1a+G1BYzvUYuMM0wLkYJPdnEOFx16faQ/UY=
github.com/spectrocloud/hapi v1.14.1-0.20240214071352-81f589b1d86d/go.mod h1:MktpRPnSXDTHsQrFSD+daJFQ1zMLSR+1gWOL31jVvWE=
-github.com/spectrocloud/palette-sdk-go v0.0.0-20241022161234-3782615736bb h1:LVeVFAMVdZRhtn1VY3DnDi32ts90r8/RXP5+1RZBZEA=
-github.com/spectrocloud/palette-sdk-go v0.0.0-20241022161234-3782615736bb/go.mod h1:dSlNvDS0qwUWTbrYI6P8x981mcbbRHFrBg67v5zl81U=
+github.com/spectrocloud/palette-sdk-go v0.0.0-20241114040951-b4855be46579 h1:C8daKBQJbK2DfoIEaHYNXTXaoSNasqMSVnKnc4Q3WyI=
+github.com/spectrocloud/palette-sdk-go v0.0.0-20241114040951-b4855be46579/go.mod h1:dSlNvDS0qwUWTbrYI6P8x981mcbbRHFrBg67v5zl81U=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
github.com/spf13/afero v1.6.0/go.mod h1:Ai8FlHk4v/PARR026UzYexafAt9roJ7LcLMAmO6Z93I=
diff --git a/spectrocloud/data_source_cloud_account_aws.go b/spectrocloud/data_source_cloud_account_aws.go
index cfb50739..ed164169 100644
--- a/spectrocloud/data_source_cloud_account_aws.go
+++ b/spectrocloud/data_source_cloud_account_aws.go
@@ -2,9 +2,10 @@ package spectrocloud
import (
"context"
-
+ "fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/spectrocloud/palette-sdk-go/api/models"
)
@@ -28,6 +29,13 @@ func dataSourceCloudAccountAws() *schema.Resource {
Computed: true,
ExactlyOneOf: []string{"id", "name"},
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
"depends": {
Type: schema.TypeString,
Optional: true,
@@ -48,20 +56,38 @@ func dataSourceCloudAccountAwsRead(_ context.Context, d *schema.ResourceData, m
if err != nil {
return diag.FromErr(err)
}
-
- var account *models.V1AwsAccount
- for _, a := range accounts {
-
- if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
- account = a
- break
- } else if v, ok := d.GetOk("name"); ok && v.(string) == a.Metadata.Name {
- account = a
+ var fAccount *models.V1AwsAccount
+ filteredAccounts := make([]*models.V1AwsAccount, 0)
+ for _, acc := range accounts {
+ if v, ok := d.GetOk("id"); ok && v.(string) == acc.Metadata.UID {
+ fAccount = acc
break
}
+ if v, ok := d.GetOk("name"); ok && v.(string) == acc.Metadata.Name {
+ filteredAccounts = append(filteredAccounts, acc)
+ }
+ }
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ fAccount = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found more than 1 AWS account",
+ Detail: fmt.Sprintf("more than 1 aws account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
+ }
+ } else if len(filteredAccounts) == 1 {
+ fAccount = filteredAccounts[0]
}
- if account == nil {
+ if fAccount == nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Unable to find aws cloud account",
@@ -70,8 +96,8 @@ func dataSourceCloudAccountAwsRead(_ context.Context, d *schema.ResourceData, m
return diags
}
- d.SetId(account.Metadata.UID)
- err = d.Set("name", account.Metadata.Name)
+ d.SetId(fAccount.Metadata.UID)
+ err = d.Set("name", fAccount.Metadata.Name)
if err != nil {
return diag.FromErr(err)
}
diff --git a/spectrocloud/data_source_cloud_account_aws_test.go b/spectrocloud/data_source_cloud_account_aws_test.go
new file mode 100644
index 00000000..38188eda
--- /dev/null
+++ b/spectrocloud/data_source_cloud_account_aws_test.go
@@ -0,0 +1,35 @@
+package spectrocloud
+
+import (
+ "context"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func prepareDataSourceCloudAccountAwsSchema() *schema.ResourceData {
+ d := dataSourceCloudAccountAws().TestResourceData()
+ return d
+}
+
+func TestDataSourceCloudAccountAwsRead(t *testing.T) {
+ d := prepareDataSourceCloudAccountAwsSchema()
+ var diags diag.Diagnostics
+
+ var ctx context.Context
+ _ = d.Set("name", "test-aws-account-1")
+ _ = d.Set("context", "project")
+ diags = dataSourceCloudAccountAwsRead(ctx, d, unitTestMockAPIClient)
+ assert.Equal(t, 0, len(diags))
+}
+
+func TestDataSourceCloudAccountAwsReadNegative(t *testing.T) {
+ d := prepareDataSourceCloudAccountAwsSchema()
+ var diags diag.Diagnostics
+
+ var ctx context.Context
+ _ = d.Set("name", "test-aws-account-1")
+ diags = dataSourceCloudAccountAwsRead(ctx, d, unitTestMockAPINegativeClient)
+ assertFirstDiagMessage(t, diags, "Unable to find aws cloud account")
+}
diff --git a/spectrocloud/data_source_cloud_account_azure.go b/spectrocloud/data_source_cloud_account_azure.go
index 65fd5211..d74fac70 100644
--- a/spectrocloud/data_source_cloud_account_azure.go
+++ b/spectrocloud/data_source_cloud_account_azure.go
@@ -2,6 +2,8 @@ package spectrocloud
import (
"context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -48,6 +50,13 @@ func dataSourceCloudAccountAzure() *schema.Resource {
Description: "The status of the disable properties option.",
Computed: true,
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
},
}
}
@@ -64,15 +73,35 @@ func dataSourceCloudAccountAzureRead(_ context.Context, d *schema.ResourceData,
}
var account *models.V1AzureAccount
+ filteredAccounts := make([]*models.V1AzureAccount, 0)
for _, a := range accounts {
if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
account = a
break
} else if v, ok := d.GetOk("name"); ok && v.(string) == a.Metadata.Name {
- account = a
- break
+ filteredAccounts = append(filteredAccounts, a)
+ }
+ }
+
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ account = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found multiple cloud accounts",
+ Detail: fmt.Sprintf("more than 1 account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
}
+ } else if len(filteredAccounts) == 1 {
+ account = filteredAccounts[0]
}
if account == nil {
diff --git a/spectrocloud/data_source_cloud_account_custom.go b/spectrocloud/data_source_cloud_account_custom.go
index a7efbdef..f700719e 100644
--- a/spectrocloud/data_source_cloud_account_custom.go
+++ b/spectrocloud/data_source_cloud_account_custom.go
@@ -2,8 +2,10 @@ package spectrocloud
import (
"context"
+ "fmt"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/spectrocloud/palette-sdk-go/api/models"
)
@@ -31,6 +33,13 @@ func dataSourceCloudAccountCustom() *schema.Resource {
Required: true,
Description: "The custom cloud provider name (e.g., `nutanix`).",
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
},
}
}
@@ -46,14 +55,34 @@ func dataSourceCloudAccountCustomRead(_ context.Context, d *schema.ResourceData,
return diag.FromErr(err)
}
var account *models.V1CustomAccount
+ filteredAccounts := make([]*models.V1CustomAccount, 0)
for _, a := range accounts {
if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
account = a
break
} else if v, ok := d.GetOk("name"); ok && v.(string) == a.Metadata.Name {
- account = a
- break
+ filteredAccounts = append(filteredAccounts, a)
+ }
+ }
+
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ account = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found multiple cloud accounts",
+ Detail: fmt.Sprintf("more than 1 account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
}
+ } else if len(filteredAccounts) == 1 {
+ account = filteredAccounts[0]
}
if account == nil {
diff --git a/spectrocloud/data_source_cloud_account_gcp.go b/spectrocloud/data_source_cloud_account_gcp.go
index ebd2b19c..a6b5a03b 100644
--- a/spectrocloud/data_source_cloud_account_gcp.go
+++ b/spectrocloud/data_source_cloud_account_gcp.go
@@ -2,6 +2,8 @@ package spectrocloud
import (
"context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -28,6 +30,13 @@ func dataSourceCloudAccountGcp() *schema.Resource {
Computed: true,
ExactlyOneOf: []string{"id", "name"},
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
},
}
}
@@ -44,15 +53,35 @@ func dataSourceCloudAccountGcpRead(_ context.Context, d *schema.ResourceData, m
}
var account *models.V1GcpAccount
+ filteredAccounts := make([]*models.V1GcpAccount, 0)
for _, a := range accounts {
if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
account = a
break
} else if v, ok := d.GetOk("name"); ok && v.(string) == a.Metadata.Name {
- account = a
- break
+ filteredAccounts = append(filteredAccounts, a)
+ }
+ }
+
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ account = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found multiple cloud accounts",
+ Detail: fmt.Sprintf("more than 1 account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
}
+ } else if len(filteredAccounts) == 1 {
+ account = filteredAccounts[0]
}
if account == nil {
diff --git a/spectrocloud/data_source_cloud_account_maas.go b/spectrocloud/data_source_cloud_account_maas.go
index cc35c0b1..14dd5f41 100644
--- a/spectrocloud/data_source_cloud_account_maas.go
+++ b/spectrocloud/data_source_cloud_account_maas.go
@@ -2,6 +2,8 @@ package spectrocloud
import (
"context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -37,6 +39,13 @@ func dataSourceCloudAccountMaas() *schema.Resource {
ExactlyOneOf: []string{"id", "name"},
Description: "The name of the cloud account. This can be used instead of `id` to retrieve the account details. Only one of `id` or `name` can be specified.",
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
},
}
}
@@ -53,6 +62,7 @@ func dataSourceCloudAccountMaasRead(_ context.Context, d *schema.ResourceData, m
}
var account *models.V1MaasAccount
+ filteredAccounts := make([]*models.V1MaasAccount, 0)
for _, a := range accounts {
if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
@@ -64,6 +74,26 @@ func dataSourceCloudAccountMaasRead(_ context.Context, d *schema.ResourceData, m
}
}
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ account = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found multiple cloud accounts",
+ Detail: fmt.Sprintf("more than 1 account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
+ }
+ } else if len(filteredAccounts) == 1 {
+ account = filteredAccounts[0]
+ }
+
if account == nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
diff --git a/spectrocloud/data_source_cloud_account_openstack.go b/spectrocloud/data_source_cloud_account_openstack.go
index 7140c334..ee411eb1 100644
--- a/spectrocloud/data_source_cloud_account_openstack.go
+++ b/spectrocloud/data_source_cloud_account_openstack.go
@@ -2,6 +2,8 @@ package spectrocloud
import (
"context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -27,6 +29,13 @@ func dataSourceCloudAccountOpenStack() *schema.Resource {
ExactlyOneOf: []string{"id", "name"},
Description: "The name of the OpenStack cloud account. Either `id` or `name` must be provided, but not both.",
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
},
}
}
@@ -43,6 +52,7 @@ func dataSourceCloudAccountOpenStackRead(_ context.Context, d *schema.ResourceDa
}
var account *models.V1OpenStackAccount
+ filteredAccounts := make([]*models.V1OpenStackAccount, 0)
for _, a := range accounts {
if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
@@ -54,6 +64,26 @@ func dataSourceCloudAccountOpenStackRead(_ context.Context, d *schema.ResourceDa
}
}
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ account = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found multiple cloud accounts",
+ Detail: fmt.Sprintf("more than 1 account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
+ }
+ } else if len(filteredAccounts) == 1 {
+ account = filteredAccounts[0]
+ }
+
if account == nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
diff --git a/spectrocloud/data_source_cloud_account_tencent.go b/spectrocloud/data_source_cloud_account_tencent.go
index 0e04ddb0..ef753345 100644
--- a/spectrocloud/data_source_cloud_account_tencent.go
+++ b/spectrocloud/data_source_cloud_account_tencent.go
@@ -2,6 +2,8 @@ package spectrocloud
import (
"context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -32,6 +34,13 @@ func dataSourceCloudAccountTencent() *schema.Resource {
ExactlyOneOf: []string{"id", "name"},
Description: "The name of the Tencent cloud account. Either `id` or `name` must be provided, but not both.",
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
},
}
}
@@ -48,6 +57,7 @@ func dataSourceCloudAccountTencentRead(_ context.Context, d *schema.ResourceData
}
var account *models.V1TencentAccount
+ filteredAccounts := make([]*models.V1TencentAccount, 0)
for _, a := range accounts {
if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
@@ -59,6 +69,26 @@ func dataSourceCloudAccountTencentRead(_ context.Context, d *schema.ResourceData
}
}
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ account = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found multiple cloud accounts",
+ Detail: fmt.Sprintf("more than 1 account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
+ }
+ } else if len(filteredAccounts) == 1 {
+ account = filteredAccounts[0]
+ }
+
if account == nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
diff --git a/spectrocloud/data_source_cloud_account_test.go b/spectrocloud/data_source_cloud_account_test.go
index 7b94aa98..739a250b 100644
--- a/spectrocloud/data_source_cloud_account_test.go
+++ b/spectrocloud/data_source_cloud_account_test.go
@@ -51,6 +51,7 @@ func TestReadAWSAccountFuncName(t *testing.T) {
var ctx context.Context
_ = d.Set("name", "test-aws-account-1")
+ _ = d.Set("context", "project")
diags = dataSourceCloudAccountAwsRead(ctx, d, unitTestMockAPIClient)
assert.Equal(t, 0, len(diags))
}
diff --git a/spectrocloud/data_source_cloud_account_vsphere.go b/spectrocloud/data_source_cloud_account_vsphere.go
index 577079a7..e60ad581 100644
--- a/spectrocloud/data_source_cloud_account_vsphere.go
+++ b/spectrocloud/data_source_cloud_account_vsphere.go
@@ -2,6 +2,8 @@ package spectrocloud
import (
"context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -27,6 +29,13 @@ func dataSourceCloudAccountVsphere() *schema.Resource {
ExactlyOneOf: []string{"id", "name"},
Description: "The name of the vSphere cloud account. Either `id` or `name` must be provided, but not both.",
},
+ "context": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "",
+ ValidateFunc: validation.StringInSlice([]string{"", "project", "tenant"}, false),
+ Description: "The context of the cluster. Allowed values are `project` or `tenant` or ``. ",
+ },
},
}
}
@@ -43,6 +52,7 @@ func dataSourceCloudAccountVsphereRead(_ context.Context, d *schema.ResourceData
}
var account *models.V1VsphereAccount
+ filteredAccounts := make([]*models.V1VsphereAccount, 0)
for _, a := range accounts {
if v, ok := d.GetOk("id"); ok && v.(string) == a.Metadata.UID {
@@ -54,6 +64,26 @@ func dataSourceCloudAccountVsphereRead(_ context.Context, d *schema.ResourceData
}
}
+ if len(filteredAccounts) > 1 {
+ if accContext, ok := d.GetOk("context"); ok && accContext != "" {
+ for _, ac := range filteredAccounts {
+ if ac.Metadata.Annotations["scope"] == accContext {
+ account = ac
+ break
+ }
+ }
+ } else {
+ diags = append(diags, diag.Diagnostic{
+ Severity: diag.Error,
+ Summary: "Found multiple cloud accounts",
+ Detail: fmt.Sprintf("more than 1 account found for name - '%s'. Kindly re-try with `context` set, Allowed value `project` or `tenant`", d.Get("name").(string)),
+ })
+ return diags
+ }
+ } else if len(filteredAccounts) == 1 {
+ account = filteredAccounts[0]
+ }
+
if account == nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
diff --git a/spectrocloud/data_source_role.go b/spectrocloud/data_source_role.go
index 848ae584..8e5baa40 100644
--- a/spectrocloud/data_source_role.go
+++ b/spectrocloud/data_source_role.go
@@ -2,6 +2,7 @@ package spectrocloud
import (
"context"
+ "github.com/spectrocloud/palette-sdk-go/api/models"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -23,6 +24,15 @@ func dataSourceRole() *schema.Resource {
Computed: true,
Optional: true,
},
+ "permissions": {
+ Type: schema.TypeSet,
+ Computed: true,
+ Set: schema.HashString,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Description: "List of permissions associated with the role. ",
+ },
},
}
}
@@ -30,15 +40,28 @@ func dataSourceRole() *schema.Resource {
func dataSourceRoleRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
c := getV1ClientWithResourceContext(m, "")
var diags diag.Diagnostics
+ var role *models.V1Role
+ var err error
+ if i, ok := d.GetOk("id"); ok {
+ role, err = c.GetRoleByID(i.(string))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ }
if v, ok := d.GetOk("name"); ok {
- role, err := c.GetRole(v.(string))
+ role, err = c.GetRole(v.(string))
if err != nil {
return diag.FromErr(err)
}
+ }
+ if role != nil {
d.SetId(role.Metadata.UID)
if err := d.Set("name", role.Metadata.Name); err != nil {
return diag.FromErr(err)
}
+ if err := d.Set("permissions", role.Spec.Permissions); err != nil {
+ return diag.FromErr(err)
+ }
}
return diags
}
diff --git a/spectrocloud/data_source_team.go b/spectrocloud/data_source_team.go
new file mode 100644
index 00000000..a62c408d
--- /dev/null
+++ b/spectrocloud/data_source_team.go
@@ -0,0 +1,66 @@
+package spectrocloud
+
+import (
+ "context"
+ "github.com/spectrocloud/palette-sdk-go/api/models"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceTeam() *schema.Resource {
+ return &schema.Resource{
+ ReadContext: dataSourceTeamRead,
+
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Computed: true,
+ Optional: true,
+ ConflictsWith: []string{"name"},
+ Description: "The unique ID of the team. If provided, `name` cannot be used.",
+ },
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "The name of the team. If provided, `id` cannot be used.",
+ },
+ "role_ids": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Description: "The roles id's assigned to the team.",
+ },
+ },
+ }
+}
+
+func dataSourceTeamRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := getV1ClientWithResourceContext(m, "")
+ var diags diag.Diagnostics
+ var team *models.V1Team
+ var err error
+ if v, ok := d.GetOk("name"); ok {
+ team, err = c.GetTeamWithName(v.(string))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ } else {
+ if val, okay := d.GetOk("id"); okay && val != "" {
+ team, err = c.GetTeam(val.(string))
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ }
+ if team != nil {
+ d.SetId(team.Metadata.UID)
+ if err := d.Set("name", team.Metadata.Name); err != nil {
+ return diag.FromErr(err)
+ }
+ if err := d.Set("role_ids", team.Spec.Roles); err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ return diags
+}
diff --git a/spectrocloud/provider.go b/spectrocloud/provider.go
index 2433011e..029d7638 100644
--- a/spectrocloud/provider.go
+++ b/spectrocloud/provider.go
@@ -140,8 +140,12 @@ func New(_ string) func() *schema.Provider {
"spectrocloud_workspace": resourceWorkspace(),
"spectrocloud_alert": resourceAlert(),
"spectrocloud_ssh_key": resourceSSHKey(),
+ "spectrocloud_user": resourceUser(),
+ "spectrocloud_role": resourceRole(),
},
DataSourcesMap: map[string]*schema.Resource{
+ "spectrocloud_team": dataSourceTeam(),
+
"spectrocloud_user": dataSourceUser(),
"spectrocloud_project": dataSourceProject(),
diff --git a/spectrocloud/resource_role.go b/spectrocloud/resource_role.go
new file mode 100644
index 00000000..21bd31e7
--- /dev/null
+++ b/spectrocloud/resource_role.go
@@ -0,0 +1,165 @@
+package spectrocloud
+
+import (
+ "context"
+ "fmt"
+ "github.com/spectrocloud/palette-sdk-go/api/models"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+)
+
+func resourceRole() *schema.Resource {
+ return &schema.Resource{
+ CreateContext: resourceRoleCreate,
+ ReadContext: resourceRoleRead,
+ UpdateContext: resourceRoleUpdate,
+ DeleteContext: resourceRoleDelete,
+ Importer: &schema.ResourceImporter{
+ StateContext: resourceRoleImport,
+ },
+ Description: "The role resource allows you to manage roles in Palette.",
+
+ Timeouts: &schema.ResourceTimeout{
+ Create: schema.DefaultTimeout(10 * time.Minute),
+ Update: schema.DefaultTimeout(10 * time.Minute),
+ Delete: schema.DefaultTimeout(10 * time.Minute),
+ },
+ SchemaVersion: 2,
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The name of the role.",
+ },
+ "type": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "project",
+ ValidateFunc: validation.StringInSlice([]string{"project", "tenant", "resource"}, false),
+ Description: "The role type. Allowed values are `project` or `tenant` or `project`",
+ },
+ "permissions": {
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Description: "The permission's assigned to the role.",
+ },
+ },
+ }
+}
+
+func resourceRoleCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := getV1ClientWithResourceContext(m, "tenant")
+ var diags diag.Diagnostics
+ role := toRole(d)
+ uid, err := c.CreateRole(role)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ d.SetId(uid)
+ return diags
+}
+
+func resourceRoleRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := getV1ClientWithResourceContext(m, "tenant")
+ var diags diag.Diagnostics
+ role, err := c.GetRoleByID(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ err = flattenRole(d, role)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ return diags
+}
+
+func resourceRoleUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := getV1ClientWithResourceContext(m, "tenant")
+ var diags diag.Diagnostics
+ role := toRole(d)
+ err := c.UpdateRole(role, d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ return diags
+}
+
+func resourceRoleDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+ c := getV1ClientWithResourceContext(m, "tenant")
+ var diags diag.Diagnostics
+ err := c.DeleteRole(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ return diags
+}
+
+func resourceRoleImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
+ c := getV1ClientWithResourceContext(m, "tenant")
+ _, err := c.GetRoleByID(d.Id())
+ if err != nil {
+ return nil, err
+ }
+
+ diags := resourceRoleRead(ctx, d, m)
+ if diags.HasError() {
+ return nil, fmt.Errorf("could not read role for import: %v", diags)
+ }
+ return []*schema.ResourceData{d}, nil
+}
+
+func convertInterfaceSliceToStringSlice(input []interface{}) ([]string, error) {
+ var output []string
+ for _, item := range input {
+ str, ok := item.(string)
+ if !ok {
+ return nil, fmt.Errorf("item %v is not a string", item)
+ }
+ output = append(output, str)
+ }
+ return output, nil
+}
+
+func toRole(d *schema.ResourceData) *models.V1Role {
+ name := d.Get("name").(string)
+ roleType := d.Get("type").(string)
+ permission, _ := convertInterfaceSliceToStringSlice(d.Get("permissions").(*schema.Set).List())
+ return &models.V1Role{
+ Metadata: &models.V1ObjectMeta{
+ Annotations: map[string]string{
+ "scope": roleType,
+ },
+ LastModifiedTimestamp: models.V1Time{},
+ Name: name,
+ },
+ Spec: &models.V1RoleSpec{
+ Permissions: permission,
+ Scope: models.V1Scope(roleType),
+ Type: "user",
+ },
+ Status: &models.V1RoleStatus{
+ IsEnabled: true,
+ },
+ }
+}
+
+func flattenRole(d *schema.ResourceData, role *models.V1Role) error {
+ var err error
+ err = d.Set("name", role.Metadata.Name)
+ if err != nil {
+ return err
+ }
+ err = d.Set("type", role.Spec.Scope)
+ if err != nil {
+ return err
+ }
+ err = d.Set("permissions", role.Spec.Permissions)
+ if err != nil {
+ return err
+ }
+ return nil
+}
diff --git a/spectrocloud/resource_role_test.go b/spectrocloud/resource_role_test.go
new file mode 100644
index 00000000..40cbad1b
--- /dev/null
+++ b/spectrocloud/resource_role_test.go
@@ -0,0 +1,80 @@
+package spectrocloud
+
+import (
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/spectrocloud/palette-sdk-go/api/models"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestToRole(t *testing.T) {
+
+ d := resourceRole().TestResourceData()
+ err := d.Set("name", "test-role")
+ if err != nil {
+ return
+ }
+ err = d.Set("type", "project")
+ if err != nil {
+ return
+ }
+ err = d.Set("permissions", []interface{}{"bbb"})
+ if err != nil {
+ return
+ }
+
+ role := toRole(d)
+
+ expected := &models.V1Role{
+ Metadata: &models.V1ObjectMeta{
+ Annotations: map[string]string{
+ "scope": "project",
+ },
+ Name: "test-role",
+ },
+ Spec: &models.V1RoleSpec{
+ Permissions: []string{"bbb"},
+ Scope: models.V1Scope("project"),
+ Type: "user",
+ },
+ Status: &models.V1RoleStatus{
+ IsEnabled: true,
+ },
+ }
+
+ assert.Equal(t, expected, role)
+}
+
+func TestFlattenRole(t *testing.T) {
+ d := schema.TestResourceDataRaw(t, map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "type": {
+ Type: schema.TypeString,
+ Required: true,
+ },
+ "permissions": {
+ Type: schema.TypeSet,
+ Required: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ },
+ }, map[string]interface{}{})
+
+ role := &models.V1Role{
+ Metadata: &models.V1ObjectMeta{
+ Name: "test-role",
+ },
+ Spec: &models.V1RoleSpec{
+ Permissions: []string{"read", "write"},
+ Scope: models.V1Scope("admin"),
+ },
+ }
+
+ err := flattenRole(d, role)
+ assert.NoError(t, err)
+ assert.Equal(t, "test-role", d.Get("name"))
+ assert.Equal(t, "admin", d.Get("type"))
+ assert.ElementsMatch(t, []interface{}{"read", "write"}, d.Get("permissions").(*schema.Set).List())
+}
diff --git a/spectrocloud/resource_user.go b/spectrocloud/resource_user.go
new file mode 100644
index 00000000..7813d742
--- /dev/null
+++ b/spectrocloud/resource_user.go
@@ -0,0 +1,721 @@
+package spectrocloud
+
+import (
+ "bytes"
+ "context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+ "github.com/spectrocloud/palette-sdk-go/client"
+ "regexp"
+ "sort"
+ "time"
+
+ "github.com/spectrocloud/palette-sdk-go/api/models"
+
+ "github.com/hashicorp/terraform-plugin-sdk/v2/diag"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func resourceUser() *schema.Resource {
+ return &schema.Resource{
+ CreateContext: resourceUserCreate,
+ ReadContext: resourceUserRead,
+ UpdateContext: resourceUserUpdate,
+ DeleteContext: resourceUserDelete,
+ Importer: &schema.ResourceImporter{
+ StateContext: resourceUserImport,
+ },
+
+ Description: "Create and manage projects in Palette.",
+
+ Timeouts: &schema.ResourceTimeout{
+ Create: schema.DefaultTimeout(10 * time.Minute),
+ Update: schema.DefaultTimeout(10 * time.Minute),
+ Delete: schema.DefaultTimeout(10 * time.Minute),
+ },
+
+ SchemaVersion: 2,
+ Schema: map[string]*schema.Schema{
+ "first_name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The first name of the user.",
+ },
+ "last_name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The last name of the user.",
+ },
+ "email": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ ValidateFunc: validation.StringMatch(
+ regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`),
+ "must be a valid email address",
+ ),
+ Description: "The email of the user.",
+ },
+ "team_ids": {
+ Type: schema.TypeList,
+ Optional: true,
+ ForceNew: true,
+ Elem: &schema.Schema{Type: schema.TypeString},
+ Description: "The team id's assigned to the user.",
+ },
+ "project_role": {
+ Type: schema.TypeSet,
+ Set: resourceUserProjectRoleMappingHash,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "project_id": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Project id to be associated with the user.",
+ },
+ "role_ids": {
+ Type: schema.TypeSet,
+ Required: true,
+ Set: schema.HashString,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Description: "List of project role ids to be associated with the user. ",
+ },
+ },
+ },
+ Description: "List of project roles to be associated with the user. ",
+ },
+ "tenant_role": {
+ Type: schema.TypeSet,
+ Optional: true,
+ Set: schema.HashString,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Description: "List of tenant role ids to be associated with the user. ",
+ },
+ "workspace_role": {
+ Type: schema.TypeSet,
+ Set: resourceUserWorkspaceRoleMappingHash,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "project_id": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Project id to be associated with the user.",
+ },
+ "workspace": {
+ Type: schema.TypeSet,
+ Set: resourceUserWorkspaceRoleMappingHashInternal,
+ Required: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Workspace id to be associated with the user.",
+ },
+ "role_ids": {
+ Type: schema.TypeSet,
+ Set: schema.HashString,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Description: "List of workspace role ids to be associated with the user.",
+ },
+ },
+ },
+ Description: "List of workspace roles to be associated with the user. ",
+ },
+ },
+ },
+ Description: "List of workspace roles to be associated with the user. ",
+ },
+ "resource_role": {
+ Type: schema.TypeSet,
+ Set: resourceUserResourceRoleMappingHash,
+ Optional: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "project_ids": {
+ Type: schema.TypeSet,
+ Set: schema.HashString,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Description: "Project id's to be associated with the user.",
+ },
+ "filter_ids": {
+ Type: schema.TypeSet,
+ Set: schema.HashString,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Description: "List of filter ids.",
+ },
+ "role_ids": {
+ Type: schema.TypeSet,
+ Set: schema.HashString,
+ Required: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ Description: "List of resource role ids to be associated with the user.",
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func resourceUserCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+
+ c := getV1ClientWithResourceContext(m, "tenant")
+ var diags diag.Diagnostics
+ user := toUser(d)
+ uid, err := c.CreateUser(user)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ d.SetId(uid)
+ //creating roles
+ if pRoles, ok := d.GetOk("project_role"); ok && pRoles != nil {
+ projectRole := toUserProjectRoleMapping(d)
+ err := c.AssociateUserProjectRole(uid, projectRole)
+ if err != nil {
+ _ = c.DeleteUser(uid)
+ return diag.FromErr(err)
+ }
+ }
+
+ if rRoles, ok := d.GetOk("tenant_role"); ok && rRoles != nil {
+ tenantRole := toUserTenantRoleMapping(d)
+ err := c.AssociateUserTenantRole(uid, tenantRole)
+ if err != nil {
+ _ = c.DeleteUser(uid)
+ return diag.FromErr(err)
+ }
+ }
+
+ if wRoles, ok := d.GetOk("workspace_role"); ok && wRoles != nil {
+ workspaceRole := toUserWorkspaceRoleMapping(d)
+ err := c.AssociateUserWorkspaceRole(uid, workspaceRole)
+ if err != nil {
+ _ = c.DeleteUser(uid)
+ return diag.FromErr(err)
+ }
+ }
+
+ if rRoles, ok := d.GetOk("resource_role"); ok && rRoles != nil {
+ resourceRoles := toUserResourceRoleMapping(d)
+ for _, role := range resourceRoles {
+ err := c.CreateUserResourceRole(uid, role)
+ if err != nil {
+ _ = c.DeleteUser(uid)
+ return diag.FromErr(err)
+ }
+ }
+ }
+
+ return diags
+}
+
+func resourceUserRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+
+ c := getV1ClientWithResourceContext(m, "tenant")
+ var diags diag.Diagnostics
+
+ email := d.Get("email").(string)
+ user, err := c.GetUserSummaryByEmail(email)
+ if err != nil {
+ return diag.FromErr(err)
+ } else if user == nil {
+ // Deleted - Terraform will recreate it
+ d.SetId("")
+ return diags
+ }
+ err = flattenUser(user, d, c)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ return diags
+}
+
+func resourceUserUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+
+ c := getV1ClientWithResourceContext(m, "tenant")
+ uid := d.Id()
+ var diags diag.Diagnostics
+
+ if d.HasChanges("project_role") {
+ ops, _ := d.GetChange("project_role")
+ if len(ops.(*schema.Set).List()) > 0 {
+ _ = deleteProjectResourceRoles(c, ops, uid)
+ }
+ projectRole := toUserProjectRoleMapping(d)
+ if projectRole != nil {
+ err := c.AssociateUserProjectRole(uid, projectRole)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ }
+ if d.HasChanges("tenant_role") {
+ tenantRole := toUserTenantRoleMapping(d)
+ err := c.AssociateUserTenantRole(uid, tenantRole)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ if d.HasChanges("workspace_role") {
+ ows, _ := d.GetChange("workspace_role")
+ if len(ows.(*schema.Set).List()) > 0 {
+ _ = deleteWorkspaceResourceRoles(c, ows, uid)
+ }
+ workspaceRole := toUserWorkspaceRoleMapping(d)
+ if len(workspaceRole.Workspaces) > 0 {
+ err := c.AssociateUserWorkspaceRole(uid, workspaceRole)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ }
+
+ }
+ if d.HasChanges("resource_role") {
+ err := deleteUserResourceRoles(c, uid)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ resourceRoles := toUserResourceRoleMapping(d)
+ for _, role := range resourceRoles {
+ err := c.CreateUserResourceRole(uid, role)
+ if err != nil {
+ return diag.FromErr(err)
+ }
+ }
+ }
+
+ return diags
+}
+
+func resourceUserDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics {
+
+ c := getV1ClientWithResourceContext(m, "tenant")
+ var diags diag.Diagnostics
+
+ err := c.DeleteUser(d.Id())
+ if err != nil {
+ return diag.FromErr(err)
+ }
+
+ return diags
+}
+
+func toUserResourceRoleMapping(d *schema.ResourceData) []*models.V1ResourceRolesUpdateEntity {
+ if resourceRoles, ok := d.GetOk("resource_role"); ok && resourceRoles != nil {
+ resourceRoleEntities := make([]*models.V1ResourceRolesUpdateEntity, 0)
+ for _, re := range d.Get("resource_role").(*schema.Set).List() {
+ resourceEntity := &models.V1ResourceRolesUpdateEntity{
+ FilterRefs: setToStringArray(re.(map[string]interface{})["filter_ids"]),
+ ProjectUids: setToStringArray(re.(map[string]interface{})["project_ids"]),
+ Roles: setToStringArray(re.(map[string]interface{})["role_ids"]),
+ }
+ resourceRoleEntities = append(resourceRoleEntities, resourceEntity)
+ }
+ return resourceRoleEntities
+ }
+ return nil
+}
+
+func toUserProjectRoleMapping(d *schema.ResourceData) *models.V1ProjectRolesPatch {
+ if projectRoles, ok := d.GetOk("project_role"); ok && projectRoles != nil {
+ //var role *models.V1ProjectRolesPatch
+ var projects []*models.V1ProjectRolesPatchProjectsItems0
+ for _, r := range projectRoles.(*schema.Set).List() {
+ projects = append(projects, &models.V1ProjectRolesPatchProjectsItems0{
+ ProjectUID: r.(map[string]interface{})["project_id"].(string),
+ Roles: setToStringArray(r.(map[string]interface{})["role_ids"]),
+ })
+ }
+ return &models.V1ProjectRolesPatch{
+ Projects: projects,
+ }
+ }
+
+ return nil
+}
+
+func toUserTenantRoleMapping(d *schema.ResourceData) *models.V1UserRoleUIDs {
+ roles := make([]string, 0)
+ if d.Get("tenant_role") != nil {
+ for _, role := range d.Get("tenant_role").(*schema.Set).List() {
+ roles = append(roles, role.(string))
+ }
+ }
+
+ return &models.V1UserRoleUIDs{
+ Roles: roles,
+ }
+}
+
+func toUserWorkspaceRoleMapping(d *schema.ResourceData) *models.V1WorkspacesRolesPatch {
+ workspaces := make([]*models.V1WorkspaceRolesPatch, 0)
+ workspaceRoleMappings := d.Get("workspace_role").(*schema.Set).List()
+
+ for _, mapping := range workspaceRoleMappings {
+ data := mapping.(map[string]interface{})
+
+ for _, workspace := range data["workspace"].(*schema.Set).List() {
+ workspaceData := workspace.(map[string]interface{})
+ roles := make([]string, 0)
+ if workspaceData["role_ids"] != nil {
+ for _, role := range workspaceData["role_ids"].(*schema.Set).List() {
+ roles = append(roles, role.(string))
+ }
+ }
+ workspaces = append(workspaces, &models.V1WorkspaceRolesPatch{
+ UID: workspaceData["id"].(string),
+ Roles: roles,
+ })
+ }
+
+ }
+
+ return &models.V1WorkspacesRolesPatch{
+ Workspaces: workspaces,
+ }
+}
+
+func setToStringArray(ids interface{}) []string {
+ idList := make([]string, 0)
+ for _, id := range ids.(*schema.Set).List() {
+ idList = append(idList, id.(string))
+ }
+ return idList
+}
+
+func deleteWorkspaceResourceRoles(c *client.V1Client, oldWs interface{}, userUID string) error {
+ oldWorkspaces := oldWs.(*schema.Set).List()
+ for _, p := range oldWorkspaces {
+
+ inWS := make([]*models.V1WorkspaceRolesPatch, 0)
+ for _, ws := range p.(map[string]interface{})["workspace"].(*schema.Set).List() {
+ inWS = append(inWS, &models.V1WorkspaceRolesPatch{
+ Roles: []string{},
+ UID: ws.(map[string]interface{})["id"].(string),
+ })
+ }
+ deleteWS := &models.V1WorkspacesRolesPatch{
+ Workspaces: inWS,
+ }
+ _ = c.AssociateUserWorkspaceRole(userUID, deleteWS)
+ }
+ return nil
+}
+
+func deleteProjectResourceRoles(c *client.V1Client, oldPs interface{}, userUID string) error {
+ oldProjectRoles := oldPs.(*schema.Set).List()
+
+ for _, p := range oldProjectRoles {
+ deletePR := &models.V1ProjectRolesPatch{
+ Projects: []*models.V1ProjectRolesPatchProjectsItems0{
+ {
+ ProjectUID: p.(map[string]interface{})["project_id"].(string),
+ Roles: []string{},
+ },
+ },
+ }
+ _ = c.AssociateUserProjectRole(userUID, deletePR)
+ }
+ return nil
+}
+
+func deleteUserResourceRoles(c *client.V1Client, userUID string) error {
+ resourceRoles, _ := c.GetUserResourceRoles(userUID)
+ for _, re := range resourceRoles {
+ err := c.DeleteUserResourceRoles(userUID, re.UID)
+ if err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+func flattenUser(user *models.V1UserSummary, d *schema.ResourceData, c *client.V1Client) error {
+ if user != nil {
+ if err := d.Set("first_name", user.Spec.FirstName); err != nil {
+ return err
+ }
+ if err := d.Set("last_name", user.Spec.LastName); err != nil {
+ return err
+ }
+ if err := d.Set("email", user.Spec.EmailID); err != nil {
+ return err
+ }
+
+ if user.Spec.Teams != nil {
+ var teamIds []string
+ for _, team := range user.Spec.Teams {
+ teamIds = append(teamIds, team.UID)
+ }
+ if err := d.Set("team_ids", teamIds); err != nil {
+ return err
+ }
+ }
+ if err := flattenUserProjectRoleMapping(d, c); err != nil {
+ return err
+ }
+ if err := flattenUserTenantRoleMapping(d, c); err != nil {
+ return err
+ }
+ if err := flattenUserWorkspaceRoleMapping(d, c); err != nil {
+ return err
+ }
+ if err := flattenUserResourceRoleMapping(d, c); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func flattenUserResourceRoleMapping(d *schema.ResourceData, c *client.V1Client) error {
+ userUID := d.Id()
+ resourceRoles, err := c.GetUserResourceRoles(userUID)
+ if err != nil {
+ return err
+ }
+ rRoles := make([]interface{}, 0)
+ for _, rr := range resourceRoles {
+ rRoles = append(rRoles, map[string]interface{}{
+ "project_ids": convertSummaryToIDS(rr.ProjectUids),
+ "filter_ids": convertSummaryToIDS(rr.FilterRefs),
+ "role_ids": convertSummaryToIDS(rr.Roles),
+ })
+ }
+ if err := d.Set("resource_role", rRoles); err != nil {
+ return err
+ }
+ return nil
+}
+
+func flattenUserWorkspaceRoleMapping(d *schema.ResourceData, c *client.V1Client) error {
+ userUID := d.Id()
+ workspaceRoles, err := c.GetUserWorkspaceRole(userUID)
+ if err != nil {
+ return err
+ }
+ wRoles := make([]interface{}, 0)
+ for _, w := range workspaceRoles.Projects {
+ wsRoles := make([]interface{}, 0)
+ for _, wr := range w.Workspaces {
+ wsIDS := make([]string, 0)
+ for _, ri := range wr.Roles {
+ wsIDS = append(wsIDS, ri.UID)
+ }
+ wsRoles = append(wsRoles, map[string]interface{}{
+ "id": wr.UID,
+ "role_ids": wsIDS,
+ })
+ }
+ wRoles = append(wRoles, map[interface{}]interface{}{
+ "project_id": w.UID,
+ "workspace": wsRoles,
+ })
+ }
+ if err := d.Set("workspace_role", wRoles); err != nil {
+ return err
+ }
+ return nil
+}
+
+func flattenUserTenantRoleMapping(d *schema.ResourceData, c *client.V1Client) error {
+ userUID := d.Id()
+ tenantRoles, err := c.GetUserTenantRole(userUID)
+ if err != nil {
+ return err
+ }
+ var tRoles []string
+ for _, t := range tenantRoles.Roles {
+ tRoles = append(tRoles, t.UID)
+ }
+ if err := d.Set("tenant_role", tRoles); err != nil {
+ return err
+ }
+ return nil
+}
+
+func flattenUserProjectRoleMapping(d *schema.ResourceData, c *client.V1Client) error {
+ userUID := d.Id()
+ projectRoles, err := c.GetUserProjectRole(userUID)
+ if err != nil {
+ return err
+ }
+ pRoles := make([]interface{}, 0)
+ for _, p := range projectRoles.Projects {
+ if len(p.Roles) > 0 {
+ roles := make([]string, 0)
+ for _, r := range p.Roles {
+ roles = append(roles, r.UID)
+ }
+ pRoles = append(pRoles, map[string]interface{}{
+ "project_id": p.UID,
+ "role_ids": roles,
+ })
+ }
+ }
+ if err := d.Set("project_role", pRoles); err != nil {
+ return err
+ }
+ return nil
+}
+
+func toUser(d *schema.ResourceData) *models.V1UserEntity {
+ fName := d.Get("first_name").(string)
+ lName := d.Get("last_name").(string)
+ user := &models.V1UserEntity{
+ Metadata: &models.V1ObjectMeta{
+ Name: fName + " " + lName,
+ },
+ Spec: &models.V1UserSpecEntity{
+ EmailID: d.Get("email").(string),
+ FirstName: fName,
+ LastName: lName,
+ },
+ }
+ if teams, ok := d.GetOk("team_ids"); ok && teams != nil {
+ user.Spec.Teams = convertToStrings(teams.([]interface{}))
+ }
+ return user
+}
+
+func convertToStrings(input []interface{}) []string {
+ var output []string
+ for _, v := range input {
+ if str, ok := v.(string); ok {
+ output = append(output, str)
+ }
+ }
+ return output
+}
+
+func convertSummaryToIDS(sum []*models.V1UIDSummary) []string {
+ var out []string
+ for _, v := range sum {
+ out = append(out, v.UID)
+ }
+ return out
+}
+
+func resourceUserResourceRoleMappingHash(i interface{}) int {
+ var buf bytes.Buffer
+ m := i.(map[string]interface{})
+
+ // Sort the roles to ensure order does not affect the hash
+ pids := make([]string, len(m["project_ids"].(*schema.Set).List()))
+ for i, pid := range m["project_ids"].(*schema.Set).List() {
+ pids[i] = pid.(string)
+ }
+ sort.Strings(pids)
+
+ fids := make([]string, len(m["filter_ids"].(*schema.Set).List()))
+ for i, fid := range m["filter_ids"].(*schema.Set).List() {
+ fids[i] = fid.(string)
+ }
+ sort.Strings(fids)
+
+ rids := make([]string, len(m["role_ids"].(*schema.Set).List()))
+ for i, rid := range m["role_ids"].(*schema.Set).List() {
+ rids[i] = rid.(string)
+ }
+ sort.Strings(rids)
+
+ //buf.WriteString(fmt.Sprintf("%s-", m["project_id"].(string)))
+
+ for _, id := range pids {
+ buf.WriteString(fmt.Sprintf("%s-", id))
+ }
+ for _, id := range fids {
+ buf.WriteString(fmt.Sprintf("%s-", id))
+ }
+ for _, id := range rids {
+ buf.WriteString(fmt.Sprintf("%s-", id))
+ }
+
+ return int(hash(buf.String()))
+}
+
+func resourceUserWorkspaceRoleMappingHash(i interface{}) int {
+ var buf bytes.Buffer
+ m := i.(map[string]interface{})
+
+ // Hash project id
+ if v, ok := m["project_id"].(string); ok {
+ h := schema.HashString(v)
+ buf.WriteString(fmt.Sprintf("%d-", h))
+ }
+
+ // Hash workspaces
+ if v, ok := m["workspace"].(*schema.Set); ok {
+ // Sort workspace hashes to ensure consistent ordering
+ workspaces := v.List()
+ hashes := make([]int, len(workspaces))
+ for i, workspaceInterface := range workspaces {
+ workspace := workspaceInterface.(map[string]interface{})
+ hashes[i] = resourceUserWorkspaceRoleMappingHashInternal(workspace)
+ }
+ sort.Ints(hashes)
+
+ for _, h := range hashes {
+ buf.WriteString(fmt.Sprintf("%d-", h))
+ }
+ }
+
+ return int(hash(buf.String()))
+}
+
+func resourceUserWorkspaceRoleMappingHashInternal(workspace interface{}) int {
+ var buf bytes.Buffer
+ m := workspace.(map[string]interface{})
+ // Sort the roles to ensure order does not affect the hash
+ roles := make([]string, len(m["role_ids"].(*schema.Set).List()))
+ for i, role := range m["role_ids"].(*schema.Set).List() {
+ roles[i] = role.(string)
+ }
+ sort.Strings(roles)
+
+ buf.WriteString(fmt.Sprintf("%s-", m["id"].(string)))
+
+ for _, role := range roles {
+ buf.WriteString(fmt.Sprintf("%s-", role))
+ }
+
+ return int(hash(buf.String()))
+}
+
+func resourceUserProjectRoleMappingHash(i interface{}) int {
+ var buf bytes.Buffer
+ m := i.(map[string]interface{})
+
+ // Sort the roles to ensure order does not affect the hash
+ roles := make([]string, len(m["role_ids"].(*schema.Set).List()))
+ for i, role := range m["role_ids"].(*schema.Set).List() {
+ roles[i] = role.(string)
+ }
+ sort.Strings(roles)
+
+ buf.WriteString(fmt.Sprintf("%s-", m["project_id"].(string)))
+
+ for _, role := range roles {
+ buf.WriteString(fmt.Sprintf("%s-", role))
+ }
+
+ return int(hash(buf.String()))
+}
diff --git a/spectrocloud/resource_user_import.go b/spectrocloud/resource_user_import.go
new file mode 100644
index 00000000..269fd54c
--- /dev/null
+++ b/spectrocloud/resource_user_import.go
@@ -0,0 +1,24 @@
+package spectrocloud
+
+import (
+ "context"
+ "fmt"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func resourceUserImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) {
+ c := getV1ClientWithResourceContext(m, "tenant")
+ user, err := c.GetUserByID(d.Id())
+ if err != nil {
+ return nil, err
+ }
+ err = d.Set("email", user.Spec.EmailID)
+ if err != nil {
+ return nil, err
+ }
+ diags := resourceUserRead(ctx, d, m)
+ if diags.HasError() {
+ return nil, fmt.Errorf("could not read user for import: %v", diags)
+ }
+ return []*schema.ResourceData{d}, nil
+}
diff --git a/spectrocloud/resource_user_test.go b/spectrocloud/resource_user_test.go
new file mode 100644
index 00000000..8325771b
--- /dev/null
+++ b/spectrocloud/resource_user_test.go
@@ -0,0 +1,204 @@
+package spectrocloud
+
+import (
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+ "github.com/spectrocloud/palette-sdk-go/api/models"
+ "github.com/stretchr/testify/assert"
+ "testing"
+)
+
+func TestConvertSummaryToIDS(t *testing.T) {
+ tests := []struct {
+ name string
+ input []*models.V1UIDSummary
+ expected []string
+ }{
+ {
+ name: "Multiple UIDs",
+ input: []*models.V1UIDSummary{
+ {UID: "uid1"},
+ {UID: "uid2"},
+ {UID: "uid3"},
+ },
+ expected: []string{"uid1", "uid2", "uid3"},
+ },
+ {
+ name: "Empty input",
+ input: []*models.V1UIDSummary{},
+ expected: []string(nil),
+ },
+ {
+ name: "Single UID",
+ input: []*models.V1UIDSummary{
+ {UID: "singleUID"},
+ },
+ expected: []string{"singleUID"},
+ },
+ {
+ name: "Nil input",
+ input: nil,
+ expected: []string(nil),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := convertSummaryToIDS(tt.input)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestConvertToStrings(t *testing.T) {
+ tests := []struct {
+ name string
+ input []interface{}
+ expected []string
+ }{
+ {
+ name: "All strings",
+ input: []interface{}{"one", "two", "three"},
+ expected: []string{"one", "two", "three"},
+ },
+ {
+ name: "Mixed types",
+ input: []interface{}{"one", 2, "three", 4.0, true},
+ expected: []string{"one", "three"},
+ },
+ {
+ name: "No strings",
+ input: []interface{}{1, 2, 3, 4.5, false},
+ expected: []string(nil),
+ },
+ {
+ name: "Empty input",
+ input: []interface{}{},
+ expected: []string(nil),
+ },
+ {
+ name: "Nil input",
+ input: nil,
+ expected: []string(nil),
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ result := convertToStrings(tt.input)
+ assert.Equal(t, tt.expected, result)
+ })
+ }
+}
+
+func TestToUser(t *testing.T) {
+ resourceData := schema.TestResourceDataRaw(t, map[string]*schema.Schema{
+ "first_name": {Type: schema.TypeString, Required: true},
+ "last_name": {Type: schema.TypeString, Required: true},
+ "email": {Type: schema.TypeString, Required: true},
+ "team_ids": {Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}},
+ }, map[string]interface{}{
+ "first_name": "John",
+ "last_name": "Doe",
+ "email": "johndoe@example.com",
+ "team_ids": []interface{}{"team1", "team2"},
+ })
+
+ user := toUser(resourceData)
+
+ expectedUser := &models.V1UserEntity{
+ Metadata: &models.V1ObjectMeta{
+ Name: "John Doe",
+ },
+ Spec: &models.V1UserSpecEntity{
+ EmailID: "johndoe@example.com",
+ FirstName: "John",
+ LastName: "Doe",
+ Teams: []string{"team1", "team2"},
+ },
+ }
+
+ assert.Equal(t, expectedUser, user)
+}
+
+func TestToUserNoTeams(t *testing.T) {
+ resourceData := schema.TestResourceDataRaw(t, map[string]*schema.Schema{
+ "first_name": {Type: schema.TypeString, Required: true},
+ "last_name": {Type: schema.TypeString, Required: true},
+ "email": {Type: schema.TypeString, Required: true},
+ "team_ids": {Type: schema.TypeList, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}},
+ }, map[string]interface{}{
+ "first_name": "Alice",
+ "last_name": "Smith",
+ "email": "alice@example.com",
+ })
+
+ user := toUser(resourceData)
+
+ expectedUser := &models.V1UserEntity{
+ Metadata: &models.V1ObjectMeta{
+ Name: "Alice Smith",
+ },
+ Spec: &models.V1UserSpecEntity{
+ EmailID: "alice@example.com",
+ FirstName: "Alice",
+ LastName: "Smith",
+ Teams: nil,
+ },
+ }
+
+ assert.Equal(t, expectedUser, user)
+}
+
+func TestSetToStringArray(t *testing.T) {
+ // Create a schema.Set with some string values
+ input := schema.NewSet(schema.HashString, []interface{}{"id1", "id2", "id3"})
+
+ // Call the function with the set
+ result := setToStringArray(input)
+
+ // Define the expected output
+ expected := []string{"id1", "id2", "id3"}
+
+ // Assert that the result matches the expected output
+ assert.ElementsMatch(t, expected, result)
+}
+
+func TestSetToStringArrayEmptySet(t *testing.T) {
+ // Create an empty schema.Set
+ input := schema.NewSet(schema.HashString, []interface{}{})
+
+ // Call the function with the empty set
+ result := setToStringArray(input)
+
+ // Define the expected output for an empty set
+ expected := []string{}
+
+ // Assert that the result matches the expected output
+ assert.Equal(t, expected, result)
+}
+
+func TestToUserWorkspaceRoleMappingEmpty(t *testing.T) {
+ d := schema.TestResourceDataRaw(t, map[string]*schema.Schema{
+ "workspace_role": {
+ Type: schema.TypeSet,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "workspace": {
+ Type: schema.TypeSet,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "id": {Type: schema.TypeString},
+ "role_ids": {Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}},
+ },
+ },
+ },
+ },
+ },
+ },
+ }, map[string]interface{}{"workspace_role": []interface{}{}})
+
+ result := toUserWorkspaceRoleMapping(d)
+ expected := &models.V1WorkspacesRolesPatch{Workspaces: []*models.V1WorkspaceRolesPatch{}}
+
+ assert.Equal(t, expected, result)
+}
diff --git a/templates/resources/role.md.tmpl b/templates/resources/role.md.tmpl
new file mode 100644
index 00000000..ffd3e236
--- /dev/null
+++ b/templates/resources/role.md.tmpl
@@ -0,0 +1,52 @@
+---
+page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
+subcategory: ""
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+
+You can learn more about managing roles in Palette by reviewing the [Roles](https://docs.spectrocloud.com/glossary-all/#role) guide.
+
+## Example Usage
+
+```terraform
+variable "roles" {
+ type = list(string)
+ default = ["Cluster Admin", "Cluster Profile Editor"]
+}
+
+# Data source loop to retrieve multiple roles
+data "spectrocloud_role" "roles" {
+ for_each = toset(var.roles)
+ name = each.key
+}
+
+resource "spectrocloud_role" "custom_role" {
+ name = "Test Cluster Role"
+ type = "project"
+ permissions = flatten([for role in data.spectrocloud_role.roles : role.permissions])
+}
+```
+
+```
+### Importing existing role state & config
+
+```hcl
+# import existing user example
+ import {
+ to = spectrocloud_role.test_role
+ id = "{roleUID}"
+ }
+
+# To generate TF configuration.
+ terraform plan -generate-config-out=test_role.tf
+
+# To import State file
+ terraform import spectrocloud_role.test_role {roleUID}
+```
+
+{{ .SchemaMarkdown | trimspace }}
\ No newline at end of file
diff --git a/templates/resources/user.md.tmpl b/templates/resources/user.md.tmpl
new file mode 100644
index 00000000..c399cc49
--- /dev/null
+++ b/templates/resources/user.md.tmpl
@@ -0,0 +1,92 @@
+---
+page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}"
+subcategory: ""
+description: |-
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+---
+
+# {{.Name}} ({{.Type}})
+
+{{ .Description | plainmarkdown | trimspace | prefixlines " " }}
+
+You can learn more about managing users in Palette by reviewing the [Users](https://docs.spectrocloud.com/user-management/) guide.
+
+## Example Usage
+
+An example of creating a user resource with assigned teams and custom roles in Palette.
+
+```hcl
+resource "spectrocloud_user" "user-test"{
+ first_name = "tf"
+ last_name = "test"
+ email = "test-tf@spectrocloud.com"
+ team_ids = [data.spectrocloud_team.team2.id]
+ project_role {
+ project_id = data.spectrocloud_project.default.id
+ role_ids = [for r in data.spectrocloud_role.app_roles : r.id]
+ }
+ project_role {
+ project_id = data.spectrocloud_project.ranjith.id
+ role_ids = [for r in data.spectrocloud_role.app_roles : r.id]
+ }
+
+ tenant_role = [for t in data.spectrocloud_role.tenant_roles : t.id]
+
+ workspace_role {
+ project_id = data.spectrocloud_project.default.id
+ workspace {
+ id = data.spectrocloud_workspace.workspace.id
+ role_ids = [for w in data.spectrocloud_role.workspace_roles : w.id]
+ }
+ workspace {
+ id = data.spectrocloud_workspace.workspace2.id
+ role_ids = ["66fbea622947f81fc26983e6"]
+ }
+ }
+
+ resource_role {
+ project_ids = [data.spectrocloud_project.default.id, data.spectrocloud_project.ranjith.id]
+ filter_ids = [data.spectrocloud_filter.filter.id]
+ role_ids = [for r in data.spectrocloud_role.resource_roles : r.id]
+ }
+
+ resource_role {
+ project_ids = [data.spectrocloud_project.ranjith.id]
+ filter_ids = [data.spectrocloud_filter.filter.id]
+ role_ids = [for re in data.spectrocloud_role.resource_roles_editor : re.id]
+ }
+
+}
+```
+
+The example below demonstrates how to create an user with only assigned teams.
+
+```hcl
+resource "spectrocloud_user" "user-test"{
+ first_name = "tf"
+ last_name = "test"
+ email = "test-tf@spectrocloud.com"
+ team_ids = [data.spectrocloud_team.team2.id]
+}
+
+
+```
+
+### Importing existing user states
+
+```hcl
+# import existing user example
+ import {
+ to = spectrocloud_user.test_user
+ id = "{userUID}"
+ }
+
+# To generate TF configuration.
+ terraform plan -generate-config-out=test_user.tf
+
+# To import State file
+ terraform import spectrocloud_user.test_user {userUID}
+```
+
+
+{{ .SchemaMarkdown | trimspace }}
\ No newline at end of file
diff --git a/tests/mockApiServer/routes/mockCloudAccounts.go b/tests/mockApiServer/routes/mockCloudAccounts.go
index 005bb34d..0fba013c 100644
--- a/tests/mockApiServer/routes/mockCloudAccounts.go
+++ b/tests/mockApiServer/routes/mockCloudAccounts.go
@@ -16,6 +16,9 @@ func getAccountResponse(cloud string) interface{} {
Metadata: &models.V1ObjectMeta{
Name: "test-aws-account-1",
UID: "test-aws-account-id-1",
+ Annotations: map[string]string{
+ "scope": "project",
+ },
},
Spec: &models.V1AwsCloudAccount{
AccessKey: "test-access-key",