diff --git a/docs/data-sources/kubernetes_clusters.md b/docs/data-sources/kubernetes_clusters.md index 402e8b5..f46072b 100644 --- a/docs/data-sources/kubernetes_clusters.md +++ b/docs/data-sources/kubernetes_clusters.md @@ -41,7 +41,7 @@ data "wiz_kubernetes_clusters" "myclusters" { - OpenShift - Kubernetes - `external_ids` (List of String) The ID(s) to search by. i.e `Azure Subscription ID` or `AWS account number`. -- `first` (Number) How many matches to return. +- `first` (Number) How many matches to return, maximum is `500` per page. - Defaults to `50`. - `kind` (List of String) Query Kubernetes Cluster of specific kind(s) or cloud provider(s). - Allowed values: @@ -51,7 +51,9 @@ data "wiz_kubernetes_clusters" "myclusters" { - OKE - OPEN_SHIFT - SELF_HOSTED -- `search` (String) Free text search. +- `max_pages` (Number) How many pages to return. 0 means all pages. + - Defaults to `0`. +- `search` (String) Free text search. Specify empty string to return all kubernetes clusters ### Read-Only diff --git a/internal/acceptance/data_source_kubernetes_clusters_test.go b/internal/acceptance/data_source_kubernetes_clusters_test.go new file mode 100644 index 0000000..9150ffb --- /dev/null +++ b/internal/acceptance/data_source_kubernetes_clusters_test.go @@ -0,0 +1,84 @@ +package acceptance + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +// TestAccDatasourceWizKubernetesClusters_basic tests the basic functionality of the datasource +// wiz_kubernetes_clusters. The assumption is that at least two clusters exist in the Wiz tenant in order +// to validate pagination functionality +func TestAccDatasourceWizKubernetesClusters_basic(t *testing.T) { + resource.UnitTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProviderFactories: providerFactories, + Steps: []resource.TestStep{ + { + Config: testAccDatasourceWizKubernetesClustersBasic(1), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr( + // check first kubernetes cluster has an id that matches the UUID regex + "data.wiz_kubernetes_clusters.foo", + "kubernetes_clusters.0.id", + regexp.MustCompile(UUIDPattern), + ), + resource.TestMatchResourceAttr( + // check first kubernetes cluster has a name that is set to a non-empty string + "data.wiz_kubernetes_clusters.foo", + "kubernetes_clusters.0.name", + regexp.MustCompile(`\w`), + ), + resource.TestMatchResourceAttr( + // check cloud_account_block has id that matches the UUID regex + "data.wiz_kubernetes_clusters.foo", + "kubernetes_clusters.0.cloud_account.0.id", + regexp.MustCompile(UUIDPattern), + ), + resource.TestMatchResourceAttr( + // check cloud_account_block has external_id set to a non-empty string, different cloud providers have different formats + "data.wiz_kubernetes_clusters.foo", + "kubernetes_clusters.0.cloud_account.0.external_id", + regexp.MustCompile(`\w`), + ), + resource.TestMatchResourceAttr( + // check cloud_account_block has name set to a non-empty string + "data.wiz_kubernetes_clusters.foo", + "kubernetes_clusters.0.cloud_account.0.name", + regexp.MustCompile(`\w`), + ), + resource.TestMatchResourceAttr( + // check cloud_account_block has cloud_provider set to a non-empty string + "data.wiz_kubernetes_clusters.foo", + "kubernetes_clusters.0.cloud_account.0.cloud_provider", + regexp.MustCompile(`\w`), + ), + ), + }, + { + Config: testAccDatasourceWizKubernetesClustersBasic(2), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr( + // check that the second kubernetes cluster has an id that matches the UUID regex + "data.wiz_kubernetes_clusters.foo", + "kubernetes_clusters.1.id", + regexp.MustCompile(`\w`), + ), + ), + }, + }, + }) +} + +func testAccDatasourceWizKubernetesClustersBasic(maxPages int) string { + return fmt.Sprintf(` + data "wiz_kubernetes_clusters" "foo" { + first = 1 + max_pages = %d + search = "" + } + +`, maxPages) +} diff --git a/internal/provider/data_source_kubernetes_clusters.go b/internal/provider/data_source_kubernetes_clusters.go index b9b36cd..e7912d5 100644 --- a/internal/provider/data_source_kubernetes_clusters.go +++ b/internal/provider/data_source_kubernetes_clusters.go @@ -36,12 +36,18 @@ func dataSourceWizKubernetesClusters() *schema.Resource { Type: schema.TypeInt, Optional: true, Default: 50, - Description: "How many matches to return.", + Description: "How many matches to return, maximum is `500` per page.", + }, + "max_pages": { + Type: schema.TypeInt, + Optional: true, + Default: 0, + Description: "How many pages to return. 0 means all pages.", }, "search": { Type: schema.TypeString, Optional: true, - Description: "Free text search.", + Description: "Free text search. Specify empty string to return all kubernetes clusters", }, "external_ids": { Type: schema.TypeList, @@ -164,6 +170,10 @@ func dataSourceWizKubernetesClustersRead(ctx context.Context, d *schema.Resource if b { identifier.WriteString(utils.PrettyPrint(a)) } + maxPages, b := d.GetOk("max_pages") + if b { + identifier.WriteString(utils.PrettyPrint(maxPages)) + } h := sha1.New() h.Write([]byte(identifier.String())) hashID := hex.EncodeToString(h.Sum(nil)) @@ -240,14 +250,15 @@ func dataSourceWizKubernetesClustersRead(ctx context.Context, d *schema.Resource // process the request data := &ReadKubernetesClusters{} - requestDiags := client.ProcessRequest(ctx, m, vars, data, query, "kubernetesClusters", "read") + requestDiags, allData := client.ProcessPagedRequest(ctx, m, vars, data, query, "kubernetesClusters", "read", maxPages.(int)) + tflog.Debug(ctx, fmt.Sprintf("allData: %s", utils.PrettyPrint(allData))) diags = append(diags, requestDiags...) if len(diags) > 0 { return diags } - clusters := flattenClusters(ctx, &data.KubernetesClusters.Nodes) + clusters := flattenClusters(ctx, allData) if err := d.Set("kubernetes_clusters", clusters); err != nil { return append(diags, diag.FromErr(err)...) @@ -257,28 +268,33 @@ func dataSourceWizKubernetesClustersRead(ctx context.Context, d *schema.Resource } -func flattenClusters(ctx context.Context, clusters *[]*wiz.KubernetesCluster) []interface{} { +func flattenClusters(ctx context.Context, clusters []interface{}) []interface{} { tflog.Info(ctx, "flattenClusters called...") tflog.Debug(ctx, fmt.Sprintf("Clusters: %s", utils.PrettyPrint(clusters))) // walk the slice and construct the list - var output = make([]interface{}, 0, 0) - for _, b := range *clusters { - clusterMap := make(map[string]interface{}) - clusterMap["id"] = b.ID - clusterMap["name"] = b.Name + var output = make([]interface{}, 0) + for _, b := range clusters { + readClusters := b.(*ReadKubernetesClusters) + for _, cluster := range readClusters.KubernetesClusters.Nodes { + tflog.Debug(ctx, fmt.Sprintf("cluster: %s", utils.PrettyPrint(cluster))) + rootMap := make(map[string]interface{}) + rootMap["id"] = cluster.ID + rootMap["name"] = cluster.Name + + clusterMap := make(map[string]interface{}) + clusterMap["cloud_provider"] = cluster.CloudAccount.CloudProvider + clusterMap["external_id"] = cluster.CloudAccount.ExternalID + clusterMap["id"] = cluster.CloudAccount.ID + clusterMap["name"] = cluster.CloudAccount.Name - accMap := make(map[string]interface{}) - accMap["cloud_provider"] = b.CloudAccount.CloudProvider - accMap["external_id"] = b.CloudAccount.ExternalID - accMap["id"] = b.CloudAccount.ID - accMap["name"] = b.CloudAccount.Name + cloudAccountMap := make([]interface{}, 0) + cloudAccountMap = append(cloudAccountMap, clusterMap) + rootMap["cloud_account"] = cloudAccountMap - cloudAccountMap := make([]interface{}, 0, 0) - cloudAccountMap = append(cloudAccountMap, accMap) - clusterMap["cloud_account"] = cloudAccountMap + output = append(output, rootMap) - output = append(output, clusterMap) + } } tflog.Debug(ctx, fmt.Sprintf("flattenClusters output: %s", utils.PrettyPrint(output))) diff --git a/internal/provider/data_source_kubernetes_clusters_test.go b/internal/provider/data_source_kubernetes_clusters_test.go index 9229743..5650a0d 100644 --- a/internal/provider/data_source_kubernetes_clusters_test.go +++ b/internal/provider/data_source_kubernetes_clusters_test.go @@ -19,25 +19,53 @@ func TestFlattenKubernetesClusters(t *testing.T) { "cloud_provider": "AWS", "external_id": "151668690081", "id": "31e5304c-baca-54fa-bff3-aaf493eaeae0", - "name": "MYK8S_TENANT_STAGE", + "name": "AWS", }, }, }, - } - - var clusterLinks = &[]*wiz.KubernetesCluster{ - { - ID: "8f137cbc-0810-55ff-acd6-f7574eb0d071", - Name: "24x7-dev", - CloudAccount: *&wiz.CloudAccount{ - ID: "31e5304c-baca-54fa-bff3-aaf493eaeae0", - ExternalID: "151668690081", - CloudProvider: "AWS", - Name: "MYK8S_TENANT_STAGE", + map[string]interface{}{ + "id": "8f137cbc-0810-55ff-acd6-f7574eb0d072", + "name": "24x7-prod", + "cloud_account": []interface{}{ + map[string]interface{}{ + "cloud_provider": "AZURE", + "external_id": "31e5304c-baca-54fa-bff3-aaf493eaeae2", + "id": "31e5304c-baca-54fa-bff3-aaf493eaeae0", + "name": "AZURE", + }, }, }, } + clusters := &ReadKubernetesClusters{ + KubernetesClusters: wiz.KubernetesClusterConnection{ + Nodes: []*wiz.KubernetesCluster{ + { + ID: "8f137cbc-0810-55ff-acd6-f7574eb0d071", + Name: "24x7-dev", + CloudAccount: wiz.CloudAccount{ + ID: "31e5304c-baca-54fa-bff3-aaf493eaeae0", + ExternalID: "151668690081", + CloudProvider: "AWS", + Name: "AWS", + }, + }, + { + ID: "8f137cbc-0810-55ff-acd6-f7574eb0d072", + Name: "24x7-prod", + CloudAccount: wiz.CloudAccount{ + ID: "31e5304c-baca-54fa-bff3-aaf493eaeae0", + ExternalID: "31e5304c-baca-54fa-bff3-aaf493eaeae2", + CloudProvider: "AZURE", + Name: "AZURE", + }, + }, + }, + }} + + clusterLinks := make([]interface{}, 0) + clusterLinks = append(clusterLinks, clusters) + flattened := flattenClusters(ctx, clusterLinks) if !reflect.DeepEqual(flattened, expected) {