From c13c005af353ebe0c50ec150c9b5a74a065a72ac Mon Sep 17 00:00:00 2001 From: Haroon-Dweikat-Ntx Date: Tue, 10 Sep 2024 11:30:39 +0300 Subject: [PATCH] Feat/v4 categories on v4-temp-design (#21) * Feat/1.9.3 (#633) Co-authored-by: Abhishekism9450 <32683845+Abhishekism9450@users.noreply.github.com> Co-authored-by: Deepak Muley Co-authored-by: Abhishek * Feat/1.9.4 (#645) Co-authored-by: Frederic M <43849398+fad3t@users.noreply.github.com> Co-authored-by: ArtemProt Co-authored-by: Abhishekism9450 <32683845+Abhishekism9450@users.noreply.github.com> * new tf design * import changes * package name change for fc * package name for fc is foundationCentral * package name to foundationcentral * fixes around acctest * examples folder * v4 design * some fixes after merging * datasource for subnets,vpcs, fips * datasource for pbrs * lint fixes. go error (gomnd, gosimple, golint) * go checks, magic numbers(gomnd) * fix config testcase as base client will differ in sdks * datasourc for route tables * resource for static route * resource for subnets * adding go mod for public repo * lint fixes * lint fix * lint fix for client name * test config as client will be different for sdks * adding crud for fips * address groups v4 * service groups * resource for service groups * crud for service groups * CRUD for address groups * data source for network security * CRUD for network security * microseg sdk pointing to internals * datasource for directory services * CRUD for directory service * datasource for saml * CRUD for idp * CRUD auth policy * delete Operation for directory service * CRUD for user groups * datasource for categories * Crud and tcs for categories * tcs for category * test for subnet * docs for subnet * tcs for fips * lint fixes * lint fix in fips * lint fix * docs for fip * docs and tcs for vpc * tests and docs for pbrs * docs for route table * docs for static route * lint fixes * testcases for address groups * fixing lint issues * lint fix * docs for address groups * test and docs for service groups * fix CRUD and info for auth policies, there is a bug in CRUD , and info . identities.reserved and entities.reserved treated as JSONString * fix bug on update auth policy * acc test for authorization policy * authorization policy v4 docs * docs and tcs for NSP * resource tests for NSP * tcs for NSP * fixing go mod for external sdks * fixing lint issues * fixing go lint issues * test and docs for categories * dummy values in test_config * remove all other modules * rename module from v4 to v2 * add examples * rename package from prism to prismv2 * rename package from prism to prismv2 --------- Co-authored-by: Abhishek Chaudhary Co-authored-by: Abhishekism9450 <32683845+Abhishekism9450@users.noreply.github.com> Co-authored-by: Deepak Muley Co-authored-by: Abhishek Co-authored-by: Frederic M <43849398+fad3t@users.noreply.github.com> Co-authored-by: ArtemProt --- examples/categories_v2/main.tf | 36 ++ examples/categories_v2/terraform.tfvars | 4 + examples/categories_v2/variables.tf | 10 + go.mod | 1 + go.sum | 2 + nutanix/provider/provider.go | 4 + nutanix/sdks/v4/prism/prism.go | 2 +- .../data_source_nutanix_categories_v2.go | 225 ++++++++++++ .../data_source_nutanix_categories_v2_test.go | 100 ++++++ .../data_source_nutanix_category_v2.go | 339 ++++++++++++++++++ .../data_source_nutanix_category_v2_test.go | 44 +++ .../prismv2/resource_nutanix_categories_v2.go | 234 ++++++++++++ .../resource_nutanix_categories_v2_test.go | 111 ++++++ test_config_v4.json | 16 + website/docs/d/categories_v2.html.markdown | 69 ++++ website/docs/d/category_v2.html.markdown | 62 ++++ 16 files changed, 1258 insertions(+), 1 deletion(-) create mode 100644 examples/categories_v2/main.tf create mode 100644 examples/categories_v2/terraform.tfvars create mode 100644 examples/categories_v2/variables.tf create mode 100644 nutanix/services/v2/prismv2/data_source_nutanix_categories_v2.go create mode 100644 nutanix/services/v2/prismv2/data_source_nutanix_categories_v2_test.go create mode 100644 nutanix/services/v2/prismv2/data_source_nutanix_category_v2.go create mode 100644 nutanix/services/v2/prismv2/data_source_nutanix_category_v2_test.go create mode 100644 nutanix/services/v2/prismv2/resource_nutanix_categories_v2.go create mode 100644 nutanix/services/v2/prismv2/resource_nutanix_categories_v2_test.go create mode 100644 test_config_v4.json create mode 100644 website/docs/d/categories_v2.html.markdown create mode 100644 website/docs/d/category_v2.html.markdown diff --git a/examples/categories_v2/main.tf b/examples/categories_v2/main.tf new file mode 100644 index 000000000..03c498beb --- /dev/null +++ b/examples/categories_v2/main.tf @@ -0,0 +1,36 @@ +terraform{ + required_providers { + nutanix = { + source = "nutanix/nutanix" + version = "1.3.0" + } + } +} + +#definig nutanix configuration +provider "nutanix"{ + username = var.nutanix_username + password = var.nutanix_password + endpoint = var.nutanix_endpoint + port = 9440 + insecure = true +} + + + +#creating category +resource "nutanix_category_v2" "example" { + key = "category_example_key" + value = "category_example_value" + description = "category example description" +} + + +#pull all categories data +data "nutanix_categories_v2" "clusters"{} + + +# get category by ext id +data "nutanix_category_v2" "example" { + ext_id = resource.nutanix_category_v2.example.ext_id +} \ No newline at end of file diff --git a/examples/categories_v2/terraform.tfvars b/examples/categories_v2/terraform.tfvars new file mode 100644 index 000000000..0965271af --- /dev/null +++ b/examples/categories_v2/terraform.tfvars @@ -0,0 +1,4 @@ +#define values to the variables to be used in terraform file +nutanix_username = "admin" +nutanix_password = "password" +nutanix_endpoint = "10.xx.xx.xx" diff --git a/examples/categories_v2/variables.tf b/examples/categories_v2/variables.tf new file mode 100644 index 000000000..4cb0b2cbd --- /dev/null +++ b/examples/categories_v2/variables.tf @@ -0,0 +1,10 @@ +#define the type of variables to be used in terraform file +variable "nutanix_username" { + type = string +} +variable "nutanix_password" { + type = string +} +variable "nutanix_endpoint" { + type = string +} \ No newline at end of file diff --git a/go.mod b/go.mod index 9185d6583..c5fd8d494 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/nutanix-core/ntnx-api-golang-sdk-internal/iam-go-client/v16 v16.8.0-5280 github.com/nutanix-core/ntnx-api-golang-sdk-internal/clustermgmt-go-client/v16 v16.9.0-8538 github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.9.0-8500 + github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4 v4.0.1-beta.1 github.com/spf13/cast v1.3.1 github.com/stretchr/testify v1.7.0 gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 26a09d853..463f95ea0 100644 --- a/go.sum +++ b/go.sum @@ -458,6 +458,8 @@ github.com/nutanix-core/ntnx-api-golang-sdk-internal/clustermgmt-go-client/v16 v github.com/nutanix-core/ntnx-api-golang-sdk-internal/clustermgmt-go-client/v16 v16.9.0-8538/go.mod h1:Wt2vo6h0QCGvQGKyY2Tw9OOU0dhhtjRL5nTd0Lx8Gho= github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.9.0-8500 h1:UPGaPcMuM30BTQ6FflAgF5LP/8t8/zVDFIOeZAtXn+8= github.com/nutanix-core/ntnx-api-golang-sdk-internal/prism-go-client/v16 v16.9.0-8500/go.mod h1:qmOw/29LhPpII8cDmbTL0OF3btwp97ss7nFcQz72NDM= +github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4 v4.0.1-beta.1 h1:hvy3QCc2SgVidYxTq0rRPOazJOt1PP8A86kW7j6sywU= +github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4 v4.0.1-beta.1/go.mod h1:Yhk+xD4mN90OKEHnk5ARf97CX5p4+MEC/B/YIVoZeZ0= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= diff --git a/nutanix/provider/provider.go b/nutanix/provider/provider.go index 15b417e66..d25f0d1cf 100644 --- a/nutanix/provider/provider.go +++ b/nutanix/provider/provider.go @@ -19,6 +19,7 @@ import ( "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/networkingv2" "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/iamv2" "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/storagecontainersv2" + "github.com/terraform-providers/terraform-provider-nutanix/nutanix/services/v2/prismv2" ) var requiredProviderFields map[string][]string = map[string][]string{ @@ -255,6 +256,8 @@ func Provider() *schema.Provider { "nutanix_storage_container_v2": storagecontainersv2.DatasourceNutanixStorageContainerV2(), "nutanix_storage_containers_v2": storagecontainersv2.DatasourceNutanixStorageContainersV2(), "nutanix_storage_container_stats_info_v2": storagecontainersv2.DatasourceNutanixStorageStatsInfoV2(), + "nutanix_category_v2": prismv2.DatasourceNutanixCategoryV2(), + "nutanix_categories_v2": prismv2.DatasourceNutanixCategoriesV2(), }, ResourcesMap: map[string]*schema.Resource{ "nutanix_virtual_machine": prism.ResourceNutanixVirtualMachine(), @@ -321,6 +324,7 @@ func Provider() *schema.Provider { "nutanix_authorization_policy_v2": iamv2.ResourceNutanixAuthPoliciesV2(), "nutanix_saml_identity_providers_v2": iamv2.ResourceNutanixSamlIdpV2(), "nutanix_storage_containers_v2": storagecontainersv2.ResourceNutanixStorageContainersV2(), + "nutanix_category_v2": prismv2.ResourceNutanixCategoriesV2(), }, ConfigureContextFunc: providerConfigure, } diff --git a/nutanix/sdks/v4/prism/prism.go b/nutanix/sdks/v4/prism/prism.go index ef6511bca..bc6341530 100644 --- a/nutanix/sdks/v4/prism/prism.go +++ b/nutanix/sdks/v4/prism/prism.go @@ -30,7 +30,7 @@ func NewPrismClient(credentials client.Credentials) (*Client, error) { } f := &Client{ - TaskRefAPI: api.NewTasksApi(baseClient), + TaskRefAPI: api.NewTasksApi(baseClient), CategoriesAPIInstance: api.NewCategoriesApi(baseClient), } diff --git a/nutanix/services/v2/prismv2/data_source_nutanix_categories_v2.go b/nutanix/services/v2/prismv2/data_source_nutanix_categories_v2.go new file mode 100644 index 000000000..506e067e7 --- /dev/null +++ b/nutanix/services/v2/prismv2/data_source_nutanix_categories_v2.go @@ -0,0 +1,225 @@ +package prismv2 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + import1 "github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4/models/prism/v4/config" + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func DatasourceNutanixCategoriesV2() *schema.Resource { + return &schema.Resource{ + ReadContext: DatasourceNutanixCategoriesV2Read, + Schema: map[string]*schema.Schema{ + "page": { + Type: schema.TypeInt, + Optional: true, + }, + "limit": { + Type: schema.TypeInt, + Optional: true, + }, + "filter": { + Type: schema.TypeString, + Optional: true, + }, + "order_by": { + Type: schema.TypeString, + Optional: true, + }, + "expand": { + Type: schema.TypeString, + Optional: true, + }, + "select": { + Type: schema.TypeString, + Optional: true, + }, + "categories": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ext_id": { + Type: schema.TypeString, + Computed: true, + }, + "key": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "owner_uuid": { + Type: schema.TypeString, + Computed: true, + }, + "associations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_type": { + Type: schema.TypeString, + Computed: true, + }, + "resource_group": { + Type: schema.TypeString, + Computed: true, + }, + "count": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "detailed_associations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_type": { + Type: schema.TypeString, + Computed: true, + }, + "resource_group": { + Type: schema.TypeString, + Computed: true, + }, + "resource_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "links": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rel": { + Type: schema.TypeString, + Computed: true, + }, + "href": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixCategoriesV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).PrismAPI + + // initialize query params + var filter, orderBy, expand, selects *string + var page, limit *int + + if pagef, ok := d.GetOk("page"); ok { + page = utils.IntPtr(pagef.(int)) + } else { + page = nil + } + if limitf, ok := d.GetOk("limit"); ok { + limit = utils.IntPtr(limitf.(int)) + } else { + limit = nil + } + if filterf, ok := d.GetOk("filter"); ok { + filter = utils.StringPtr(filterf.(string)) + } else { + filter = nil + } + if order, ok := d.GetOk("order_by"); ok { + orderBy = utils.StringPtr(order.(string)) + } else { + orderBy = nil + } + if expandf, ok := d.GetOk("expand"); ok { + expand = utils.StringPtr(expandf.(string)) + } else { + expand = nil + } + if selectf, ok := d.GetOk("select"); ok { + selects = utils.StringPtr(selectf.(string)) + } else { + selects = nil + } + resp, err := conn.CategoriesAPIInstance.ListCategories(page, limit, filter, orderBy, expand, selects) + if err != nil { + return diag.Errorf("error while fetching categories : %v", err) + } + + checkResp := resp.Data + + if checkResp != nil { + getResp := resp.Data.GetValue().([]import1.Category) + + if err := d.Set("categories", flattenCategoriesEntities(getResp)); err != nil { + return diag.FromErr(err) + } + } + + d.SetId(resource.UniqueId()) + return nil +} + +func flattenCategoriesEntities(pr []import1.Category) []interface{} { + if len(pr) > 0 { + ctgList := make([]interface{}, len(pr)) + + for k, v := range pr { + ctg := make(map[string]interface{}) + + ctg["ext_id"] = v.ExtId + ctg["key"] = v.Key + ctg["value"] = v.Value + ctg["type"] = flattenCategoryType(v.Type) + ctg["description"] = v.Description + ctg["owner_uuid"] = v.OwnerUuid + ctg["associations"] = flattenAssociationSummary(v.Associations) + ctg["detailed_associations"] = flattenAssociationDetail(v.DetailedAssociations) + ctg["tenant_id"] = v.TenantId + ctg["links"] = flattenLinks(v.Links) + + ctgList[k] = ctg + } + return ctgList + } + return nil +} diff --git a/nutanix/services/v2/prismv2/data_source_nutanix_categories_v2_test.go b/nutanix/services/v2/prismv2/data_source_nutanix_categories_v2_test.go new file mode 100644 index 000000000..78c9b122c --- /dev/null +++ b/nutanix/services/v2/prismv2/data_source_nutanix_categories_v2_test.go @@ -0,0 +1,100 @@ +package prismv2_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const datasourceNameCatgs = "data.nutanix_categories_v2.test" + +func TestAccNutanixCategoriesDataSourceV2_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoriesDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.#"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.key"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.value"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.type"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.associations.#"), + ), + }, + }, + }) +} + +func TestAccNutanixCategoriesDataSourceV2_WithFilter(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoriesDataSourceConfigWithFilter(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.#"), + resource.TestCheckResourceAttr(datasourceNameCatgs, "categories.#", "1"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.key"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.value"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.type"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.associations.#"), + ), + }, + }, + }) +} + +func TestAccNutanixCategoriesDataSourceV2_WithLimit(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoriesDataSourceConfigWithLimit(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.#"), + resource.TestCheckResourceAttr(datasourceNameCatgs, "categories.#", "2"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.key"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.value"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.type"), + resource.TestCheckResourceAttrSet(datasourceNameCatgs, "categories.0.associations.#"), + ), + }, + }, + }) +} + +func testAccCategoriesDataSourceConfig() string { + return (` + data "nutanix_categories_v2" "test" { } + `) +} + +func testAccCategoriesDataSourceConfigWithFilter() string { + return (` + + data "nutanix_categories_v2" "dtest" { } + + locals{ + kk = data.nutanix_categories_v2.dtest.categories.0.key + } + data "nutanix_categories_v2" "test" { + filter = "key eq '${local.kk}'" + depends_on = [ + data.nutanix_categories_v2.dtest + ] + } + `) +} + +func testAccCategoriesDataSourceConfigWithLimit() string { + return (` + data "nutanix_categories_v2" "test" { + limit = 2 + } + `) +} diff --git a/nutanix/services/v2/prismv2/data_source_nutanix_category_v2.go b/nutanix/services/v2/prismv2/data_source_nutanix_category_v2.go new file mode 100644 index 000000000..1c64de793 --- /dev/null +++ b/nutanix/services/v2/prismv2/data_source_nutanix_category_v2.go @@ -0,0 +1,339 @@ +package prismv2 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + import2 "github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4/models/common/v1/response" + import1 "github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4/models/prism/v4/config" + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func DatasourceNutanixCategoryV2() *schema.Resource { + return &schema.Resource{ + ReadContext: DatasourceNutanixCategoryV2Read, + Schema: map[string]*schema.Schema{ + "ext_id": { + Type: schema.TypeString, + Required: true, + }, + "expand": { + Type: schema.TypeString, + Optional: true, + }, + "key": { + Type: schema.TypeString, + Computed: true, + }, + "value": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "description": { + Type: schema.TypeString, + Computed: true, + }, + "owner_uuid": { + Type: schema.TypeString, + Computed: true, + }, + "associations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_type": { + Type: schema.TypeString, + Computed: true, + }, + "resource_group": { + Type: schema.TypeString, + Computed: true, + }, + "count": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "detailed_associations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_type": { + Type: schema.TypeString, + Computed: true, + }, + "resource_group": { + Type: schema.TypeString, + Computed: true, + }, + "resource_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "tenant_id": { + Type: schema.TypeString, + Computed: true, + }, + "links": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "rel": { + Type: schema.TypeString, + Computed: true, + }, + "href": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func DatasourceNutanixCategoryV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).PrismAPI + + extID := d.Get("ext_id") + var expand *string + if expandf, ok := d.GetOk("expand"); ok { + expand = utils.StringPtr(expandf.(string)) + } else { + expand = nil + } + resp, err := conn.CategoriesAPIInstance.GetCategoryById(utils.StringPtr(extID.(string)), expand) + if err != nil { + return diag.Errorf("error while fetching category : %v", err) + } + + getResp := resp.Data.GetValue().(import1.Category) + + if err := d.Set("key", getResp.Key); err != nil { + return diag.FromErr(err) + } + if err := d.Set("value", getResp.Value); err != nil { + return diag.FromErr(err) + } + if err := d.Set("type", flattenCategoryType(getResp.Type)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set("owner_uuid", getResp.OwnerUuid); err != nil { + return diag.FromErr(err) + } + if err := d.Set("associations", flattenAssociationSummary(getResp.Associations)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("detailed_associations", flattenAssociationDetail(getResp.DetailedAssociations)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("tenant_id", getResp.TenantId); err != nil { + return diag.FromErr(err) + } + if err := d.Set("links", flattenLinks(getResp.Links)); err != nil { + return diag.FromErr(err) + } + + d.SetId(*getResp.ExtId) + return nil +} + +func flattenCategoryType(pr *import1.CategoryType) string { + const two, three, four = 2, 3, 4 + if pr != nil { + if *pr == import1.CategoryType(two) { + return "USER" + } + if *pr == import1.CategoryType(three) { + return "SYSTEM" + } + if *pr == import1.CategoryType(four) { + return "INTERNAL" + } + } + return "UNKNOWN" +} + +func flattenAssociationSummary(pr []import1.AssociationSummary) []interface{} { + if len(pr) > 0 { + associationList := make([]interface{}, len(pr)) + + for k, v := range pr { + assn := make(map[string]interface{}) + + assn["category_id"] = v.CategoryId + assn["count"] = v.Count + assn["resource_group"] = flattenResourceGroup(v.ResourceGroup) + assn["resource_type"] = flattenResourceType(v.ResourceType) + + associationList[k] = assn + } + return associationList + } + return nil +} + +func flattenAssociationDetail(pr []import1.AssociationDetail) []interface{} { + if len(pr) > 0 { + detailList := make([]interface{}, len(pr)) + + for k, v := range pr { + detail := make(map[string]interface{}) + + detail["category_id"] = v.CategoryId + detail["resource_group"] = flattenResourceGroup(v.ResourceGroup) + detail["resource_type"] = flattenResourceType(v.ResourceType) + detail["resource_id"] = v.ResourceId + + detailList[k] = detail + } + return detailList + } + return nil +} + +func flattenResourceGroup(pr *import1.ResourceGroup) string { + const two, three = 2, 3 + if pr != nil { + if *pr == import1.ResourceGroup(two) { + return "ENTITY" + } + if *pr == import1.ResourceGroup(three) { + return "POLICY" + } + } + return "UNKNOWN" +} + +func flattenResourceType(pr *import1.ResourceType) string { + const ( + two, three, four, five, six, seven, eight, nine, ten, eleven, twelve, thirteen, fourteen, fifteen, + sixteen, seventeen, eighteen, nineteen, twenty, twentyone, twentytwo, twentythree, twentyfour, twentyfive, + twentysix = 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26 + ) + if pr != nil { + if *pr == import1.ResourceType(two) { + return "VM" + } + if *pr == import1.ResourceType(three) { + return "MH_VM" + } + if *pr == import1.ResourceType(four) { + return "IMAGE" + } + if *pr == import1.ResourceType(five) { + return "SUBNET" + } + if *pr == import1.ResourceType(six) { + return "CLUSTER" + } + if *pr == import1.ResourceType(seven) { + return "HOST" + } + if *pr == import1.ResourceType(eight) { + return "REPORT" + } + if *pr == import1.ResourceType(nine) { + return "MARKETPLACE_ITEM" + } + if *pr == import1.ResourceType(ten) { + return "BLUEPRINT" + } + if *pr == import1.ResourceType(eleven) { + return "APP" + } + if *pr == import1.ResourceType(twelve) { + return "VOLUMEGROUP" + } + if *pr == import1.ResourceType(thirteen) { + return "IMAGE_PLACEMENT_POLICY" + } + if *pr == import1.ResourceType(fourteen) { + return "NETWORK_SECURITY_POLICY" + } + if *pr == import1.ResourceType(fifteen) { + return "NETWORK_SECURITY_RULE" + } + if *pr == import1.ResourceType(sixteen) { + return "VM_HOST_AFFINITY_POLICY" + } + if *pr == import1.ResourceType(seventeen) { + return "VM_VM_ANTI_AFFINITY_POLICY" + } + if *pr == import1.ResourceType(eighteen) { + return "QOS_POLICY" + } + if *pr == import1.ResourceType(nineteen) { + return "NGT_POLICY" + } + if *pr == import1.ResourceType(twenty) { + return "PROTECTION_RULE" + } + if *pr == import1.ResourceType(twentyone) { + return "ACCESS_CONTROL_POLICY" + } + if *pr == import1.ResourceType(twentytwo) { + return "STORAGE_POLICY" + } + if *pr == import1.ResourceType(twentythree) { + return "IMAGE_RATE_LIMIT" + } + if *pr == import1.ResourceType(twentyfour) { + return "RECOVERY_PLAN" + } + if *pr == import1.ResourceType(twentyfive) { + return "BUNDLE" + } + if *pr == import1.ResourceType(twentysix) { + return "POLICY_SCHEMA" + } + } + return "UNKNOWN" +} + +func flattenLinks(pr []import2.ApiLink) []map[string]interface{} { + if len(pr) > 0 { + linkList := make([]map[string]interface{}, len(pr)) + + for k, v := range pr { + links := map[string]interface{}{} + if v.Href != nil { + links["href"] = v.Href + } + if v.Rel != nil { + links["rel"] = v.Rel + } + + linkList[k] = links + } + return linkList + } + return nil +} diff --git a/nutanix/services/v2/prismv2/data_source_nutanix_category_v2_test.go b/nutanix/services/v2/prismv2/data_source_nutanix_category_v2_test.go new file mode 100644 index 000000000..33031a04a --- /dev/null +++ b/nutanix/services/v2/prismv2/data_source_nutanix_category_v2_test.go @@ -0,0 +1,44 @@ +package prismv2_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const datasourceNameCatg = "data.nutanix_category_v2.test" + +func TestAccNutanixCategoryDataSourceV2_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoryDataSourceConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(datasourceNameCatg, "description"), + resource.TestCheckResourceAttrSet(datasourceNameCatg, "key"), + resource.TestCheckResourceAttrSet(datasourceNameCatg, "value"), + resource.TestCheckResourceAttrSet(datasourceNameCatg, "type"), + resource.TestCheckResourceAttrSet(datasourceNameCatg, "associations.#"), + resource.TestCheckResourceAttrSet(datasourceNameCatg, "detailed_associations.#"), + ), + }, + }, + }) +} + +func testAccCategoryDataSourceConfig() string { + return (` + data "nutanix_categories_v2" "dtest" { } + + data "nutanix_category_v2" "test" { + ext_id = data.nutanix_categories_v2.dtest.categories.0.ext_id + + depends_on = [ + data.nutanix_categories_v2.dtest + ] + } + `) +} diff --git a/nutanix/services/v2/prismv2/resource_nutanix_categories_v2.go b/nutanix/services/v2/prismv2/resource_nutanix_categories_v2.go new file mode 100644 index 000000000..0df4331cb --- /dev/null +++ b/nutanix/services/v2/prismv2/resource_nutanix_categories_v2.go @@ -0,0 +1,234 @@ +package prismv2 + +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" + import1 "github.com/nutanix/ntnx-api-golang-clients/prism-go-client/v4/models/prism/v4/config" + + conns "github.com/terraform-providers/terraform-provider-nutanix/nutanix" + "github.com/terraform-providers/terraform-provider-nutanix/utils" +) + +func ResourceNutanixCategoriesV2() *schema.Resource { + return &schema.Resource{ + CreateContext: ResourceNutanixCategoriesV2Create, + ReadContext: ResourceNutanixCategoriesV2Read, + UpdateContext: ResourceNutanixCategoriesV2Update, + DeleteContext: ResourceNutanixCategoriesV2Delete, + Schema: map[string]*schema.Schema{ + "key": { + Type: schema.TypeString, + Required: true, + }, + "value": { + Type: schema.TypeString, + Required: true, + }, + "type": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice([]string{"USER", "INTERNAL", "SYSTEM"}, false), + }, + "description": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "owner_uuid": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "associations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_type": { + Type: schema.TypeString, + Computed: true, + }, + "resource_group": { + Type: schema.TypeString, + Computed: true, + }, + "count": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "detailed_associations": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "category_id": { + Type: schema.TypeString, + Computed: true, + }, + "resource_type": { + Type: schema.TypeString, + Computed: true, + }, + "resource_group": { + Type: schema.TypeString, + Computed: true, + }, + "resource_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func ResourceNutanixCategoriesV2Create(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).PrismAPI + + input := &import1.Category{} + + if key, ok := d.GetOk("key"); ok { + input.Key = utils.StringPtr(key.(string)) + } + if val, ok := d.GetOk("value"); ok { + input.Value = utils.StringPtr(val.(string)) + } + if types, ok := d.GetOk("type"); ok { + const two, three, four = 2, 3, 4 + subMap := map[string]interface{}{ + "USER": two, + "SYSTEM": three, + "INTERNAL": four, + } + + pInt := subMap[types.(string)] + p := import1.CategoryType(pInt.(int)) + + input.Type = &p + } + if desc, ok := d.GetOk("description"); ok { + input.Description = utils.StringPtr(desc.(string)) + } + if ownerUUID, ok := d.GetOk("owner_uuid"); ok { + input.OwnerUuid = utils.StringPtr(ownerUUID.(string)) + } + + resp, err := conn.CategoriesAPIInstance.CreateCategory(input) + if err != nil { + return diag.Errorf("error while creating category: %v", err) + } + + getResp := resp.Data.GetValue().(import1.Category) + + d.SetId(*getResp.ExtId) + return ResourceNutanixCategoriesV2Read(ctx, d, meta) +} + +func ResourceNutanixCategoriesV2Read(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).PrismAPI + + resp, err := conn.CategoriesAPIInstance.GetCategoryById(utils.StringPtr(d.Id()), nil) + if err != nil { + return diag.Errorf("error while fetching category : %v", err) + } + + getResp := resp.Data.GetValue().(import1.Category) + + if err := d.Set("key", getResp.Key); err != nil { + return diag.FromErr(err) + } + if err := d.Set("value", getResp.Value); err != nil { + return diag.FromErr(err) + } + if err := d.Set("type", flattenCategoryType(getResp.Type)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("description", getResp.Description); err != nil { + return diag.FromErr(err) + } + if err := d.Set("owner_uuid", getResp.OwnerUuid); err != nil { + return diag.FromErr(err) + } + if err := d.Set("associations", flattenAssociationSummary(getResp.Associations)); err != nil { + return diag.FromErr(err) + } + if err := d.Set("detailed_associations", flattenAssociationDetail(getResp.DetailedAssociations)); err != nil { + return diag.FromErr(err) + } + return nil +} + +func ResourceNutanixCategoriesV2Update(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).PrismAPI + updatedInput := import1.Category{} + resp, err := conn.CategoriesAPIInstance.GetCategoryById(utils.StringPtr(d.Id()), nil) + + if err != nil { + return diag.Errorf("error while fetching categories : %v", err) + } + + updatedInput = resp.Data.GetValue().(import1.Category) + + if d.HasChange("value") { + updatedInput.Value = utils.StringPtr(d.Get("value").(string)) + } + if d.HasChange("description") { + updatedInput.Description = utils.StringPtr(d.Get("description").(string)) + } + if d.HasChange("type") { + const two, three, four = 2, 3, 4 + subMap := map[string]interface{}{ + "USER": two, + "SYSTEM": three, + "INTERNAL": four, + } + + pInt := subMap[d.Get("type").(string)] + p := import1.CategoryType(pInt.(int)) + updatedInput.Type = &p + } + if d.HasChange("owner_uuid") { + updatedInput.OwnerUuid = utils.StringPtr(d.Get("owner_uuid").(string)) + } + + upResp, er := conn.CategoriesAPIInstance.UpdateCategoryById(utils.StringPtr(d.Id()), &updatedInput) + if er != nil { + return diag.Errorf("error while updating categories : %v", err) + } + + updatedRes := upResp.Data.GetValue().(import1.Category) + + if updatedRes.ExtId != nil { + fmt.Println("Category updated successfully") + } + return ResourceNutanixCategoriesV2Read(ctx, d, meta) +} + +func ResourceNutanixCategoriesV2Delete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + conn := meta.(*conns.Client).PrismAPI + + resp, err := conn.CategoriesAPIInstance.DeleteCategoryById(utils.StringPtr(d.Id())) + if err != nil { + return diag.Errorf("error while deleting category : %v", err) + } + + if resp == nil { + fmt.Println("Category deleted successfully.") + } + + return nil +} diff --git a/nutanix/services/v2/prismv2/resource_nutanix_categories_v2_test.go b/nutanix/services/v2/prismv2/resource_nutanix_categories_v2_test.go new file mode 100644 index 000000000..7d4e8bb25 --- /dev/null +++ b/nutanix/services/v2/prismv2/resource_nutanix_categories_v2_test.go @@ -0,0 +1,111 @@ +package prismv2_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + acc "github.com/terraform-providers/terraform-provider-nutanix/nutanix/acctest" +) + +const resourceNameCategory = "nutanix_category_v2.test" + +func TestAccNutanixCategoryV2_Basic(t *testing.T) { + r := acctest.RandInt() + value := fmt.Sprintf("test category value-%d", r) + desc := "test category description" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoryV2Config(r, value, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceNameCategory, "key", fmt.Sprintf("test-cat-%d", r)), + resource.TestCheckResourceAttr(resourceNameCategory, "value", value), + resource.TestCheckResourceAttr(resourceNameCategory, "description", desc), + resource.TestCheckResourceAttr(resourceNameCategory, "type", "USER"), + ), + }, + }, + }) +} + +func TestAccNutanixCategoryV2_Update(t *testing.T) { + r := acctest.RandInt() + value := fmt.Sprintf("test category value-%d", r) + updatedValue := fmt.Sprintf("test category value updated-%d", r) + desc := "test category description" + updateDesc := "test category description updated" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoryV2Config(r, value, desc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceNameCategory, "key", fmt.Sprintf("test-cat-%d", r)), + resource.TestCheckResourceAttr(resourceNameCategory, "value", value), + resource.TestCheckResourceAttr(resourceNameCategory, "description", desc), + resource.TestCheckResourceAttr(resourceNameCategory, "type", "USER"), + ), + }, + { + Config: testAccCategoryV2ConfigUpdated(r, updatedValue, updateDesc), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceNameCategory, "key", fmt.Sprintf("test-cat-%d", r)), + resource.TestCheckResourceAttr(resourceNameCategory, "value", updatedValue), + resource.TestCheckResourceAttr(resourceNameCategory, "description", updateDesc), + resource.TestCheckResourceAttr(resourceNameCategory, "type", "USER"), + ), + }, + }, + }) +} + +func TestAccNutanixCategoryV2_WithNoKey(t *testing.T) { + r := acctest.RandInt() + value := fmt.Sprintf("test category value-%d", r) + desc := "test category description" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCategoryV2ConfigWithNoKey(r, value, desc), + ExpectError: regexp.MustCompile("Missing required argument"), + }, + }, + }) +} + +func testAccCategoryV2Config(r int, val, desc string) string { + return fmt.Sprintf(` + resource "nutanix_category_v2" "test" { + key = "test-cat-%d" + value = "%[2]s" + description = "%[3]s" + } +`, r, val, desc) +} + +func testAccCategoryV2ConfigUpdated(r int, val, desc string) string { + return fmt.Sprintf(` + resource "nutanix_category_v2" "test" { + key = "test-cat-%d" + value = "%[2]s" + description = "%[3]s" + } +`, r, val, desc) +} + +func testAccCategoryV2ConfigWithNoKey(r int, val, desc string) string { + return fmt.Sprintf(` + resource "nutanix_category_v2" "test" { + value = "%[2]s" + description = "%[3]s" + } +`, r, val, desc) +} diff --git a/test_config_v4.json b/test_config_v4.json new file mode 100644 index 000000000..028c10e7b --- /dev/null +++ b/test_config_v4.json @@ -0,0 +1,16 @@ +{ + "auth_policies": { + "limit": 1, + "role": "00000000-0000-0000-0000-000000000000", + "display_name": "auth_policies_test", + "description": "auth policies description ", + "description_update": "auth policies description test", + "authorization_policy_type": "USER_DEFINED", + "identities": [ + "{\"user\":{\"uuid\":{\"anyof\":[\"00000000-0000-0000-0000-000000000000\"]}}}" + ], + "entities": [ + "{\"*\":{\"*\":{\"eq\":\"*\"}}}" + ] + } +} \ No newline at end of file diff --git a/website/docs/d/categories_v2.html.markdown b/website/docs/d/categories_v2.html.markdown new file mode 100644 index 000000000..478b30702 --- /dev/null +++ b/website/docs/d/categories_v2.html.markdown @@ -0,0 +1,69 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_categories_v2" +sidebar_current: "docs-nutanix-datasource-categories-v4" +description: |- + Fetch a list of categories with pagination, filtering, sorting, selection and optional expansion of associated entity counts. +--- + +# nutanix_categories_v2 +List categories + + +## Example + +```hcl + + data "nutanix_categories_v2" "test" { } + + data "nutanix_categories_v2" "test2" { + filter = "key eq '{}'" + } + +``` + + +## Argument Reference + +The following arguments are supported: + +* `page`: (Optional) A URL query parameter that specifies the page number of the result set. It must be a positive integer between 0 and the maximum number of pages that are available for that resource. Any number out of this range might lead to no results. +* `limit`: (Optional) A URL query parameter that specifies the total number of records returned in the result set. Must be a positive integer between 1 and 100. Any number out of this range will lead to a validation error. If the limit is not provided, a default value of 50 records will be returned in the result set. +* `filter`: (Optional) A URL query parameter that allows clients to filter a collection of resources. +* `order_by`: (Optional) A URL query parameter that allows clients to specify the sort criteria for the returned list of objects. Resources can be sorted in ascending order using asc or descending order using desc. If asc or desc are not specified, the resources will be sorted in ascending order by default +* `expand`: (Optional) A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. +* `select`: (Optional) A URL query parameter that allows clients to request a specific set of properties for each entity or complex type. + +* `categories`: List of categories + +## categories + +* `ext_id`: The extID for the category. +* `key`: The key of a category when it is represented in key:value format. +* `value`: The value of a category when it is represented in key:value format +* `type`: Denotes the type of a category. +There are three types of categories: SYSTEM, INTERNAL, and USER. +* `description`: A string consisting of the description of the category as defined by the user. +* `owner_uuid`: This field contains the UUID of a user who owns the category. +* `associations`: This field gives basic information about resources that are associated to the category. +* `detailed_associations`: This field gives detailed information about resources that are associated to the category. +* `tenant_id`: A globally unique identifier that represents the tenant that owns this entity. +* `links`: A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. + + +### associations +* `category_id`: External identifier for the given category, used across all v4 apis/entities/resources where categories are referenced. +* `resource_type`: An enum denoting the associated resource types. Resource types are further grouped into 2 types - entity or a policy. +* `resource_group`: An enum denoting the resource group. +Resources can be organised into either an entity or a policy. +* `count`: Count of associations of a particular type of entity or policy + +### detailed_associations +* `category_id`: External identifier for the given category, used across all v4 apis/entities/resources where categories are referenced. +* `resource_type`: An enum denoting the associated resource types. Resource types are further grouped into 2 types - entity or a policy. +* `resource_group`: An enum denoting the resource group. +Resources can be organised into either an entity or a policy. +* `resource_id`: The UUID of the entity or policy associated with the particular category. + + +See detailed information in [Nutanix Categories v4](https://developers.nutanix.com/api-reference?namespace=prism&version=v4.0.b1). \ No newline at end of file diff --git a/website/docs/d/category_v2.html.markdown b/website/docs/d/category_v2.html.markdown new file mode 100644 index 000000000..6a81cf7fb --- /dev/null +++ b/website/docs/d/category_v2.html.markdown @@ -0,0 +1,62 @@ +--- +layout: "nutanix" +page_title: "NUTANIX: nutanix_category_v2" +sidebar_current: "docs-nutanix-datasource-category-v4" +description: |- + Fetch details of a category with the given external identifier. +--- + +# nutanix_category_v2 +Fetch a category + + +## Example + +```hcl + + data "nutanix_category_v2" "test" { + ext_id = {{ ext_id of category}} + } + +``` + + +## Argument Reference + +The following arguments are supported: + +* `ext_id`: (Required) The extID for the category. +* `expand`: (Optional) A URL query parameter that allows clients to request related resources when a resource that satisfies a particular request is retrieved. + +## Attributes Reference + +The following attributes are exported: + +* `key`: The key of a category when it is represented in key:value format. +* `value`: The value of a category when it is represented in key:value format +* `type`: Denotes the type of a category. +There are three types of categories: SYSTEM, INTERNAL, and USER. +* `description`: A string consisting of the description of the category as defined by the user. +* `owner_uuid`: This field contains the UUID of a user who owns the category. +* `associations`: This field gives basic information about resources that are associated to the category. +* `detailed_associations`: This field gives detailed information about resources that are associated to the category. +* `tenant_id`: A globally unique identifier that represents the tenant that owns this entity. +* `links`: A HATEOAS style link for the response. Each link contains a user-friendly name identifying the link and an address for retrieving the particular resource. + + +### associations +* `category_id`: External identifier for the given category, used across all v4 apis/entities/resources where categories are referenced. +* `resource_type`: An enum denoting the associated resource types. Resource types are further grouped into 2 types - entity or a policy. +* `resource_group`: An enum denoting the resource group. +Resources can be organised into either an entity or a policy. +* `count`: Count of associations of a particular type of entity or policy + +### detailed_associations +* `category_id`: External identifier for the given category, used across all v4 apis/entities/resources where categories are referenced. +* `resource_type`: An enum denoting the associated resource types. Resource types are further grouped into 2 types - entity or a policy. +* `resource_group`: An enum denoting the resource group. +Resources can be organised into either an entity or a policy. +* `resource_id`: The UUID of the entity or policy associated with the particular category. + + +See detailed information in [Nutanix Category v4](https://developers.nutanix.com/api-reference?namespace=prism&version=v4.0.b1). \ No newline at end of file