diff --git a/cloudstack/data_source_cloudstack_domain.go b/cloudstack/data_source_cloudstack_domain.go new file mode 100644 index 00000000..324160cc --- /dev/null +++ b/cloudstack/data_source_cloudstack_domain.go @@ -0,0 +1,133 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "log" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackDomain() *schema.Resource { + return &schema.Resource{ + Read: dataSourceCloudstackDomainRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + // Computed values + "domain_id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "network_domain": { + Type: schema.TypeString, + Computed: true, + }, + "parent_domain_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + } +} + +func dataSourceCloudstackDomainRead(d *schema.ResourceData, meta interface{}) error { + log.Printf("Domain Data Source Read Started") + + cs := meta.(*cloudstack.CloudStackClient) + p := cs.Domain.NewListDomainsParams() + + var filterName, filterValue string + var filterByName, filterByID bool + + // Apply filters if provided + if filters, filtersOk := d.GetOk("filter"); filtersOk { + for _, f := range filters.(*schema.Set).List() { + m := f.(map[string]interface{}) + name := m["name"].(string) + value := m["value"].(string) + + switch name { + case "name": + p.SetName(value) + filterName = value + filterByName = true + log.Printf("[DEBUG] Filtering by name: %s", value) + case "id": + p.SetId(value) + filterValue = value + filterByID = true + log.Printf("[DEBUG] Filtering by ID: %s", value) + } + } + } + + csDomains, err := cs.Domain.ListDomains(p) + if err != nil { + return fmt.Errorf("failed to list domains: %s", err) + } + + log.Printf("[DEBUG] Found %d domains from CloudStack API", len(csDomains.Domains)) + + var domain *cloudstack.Domain + + // If we have results from the API call, select the appropriate domain + if len(csDomains.Domains) > 0 { + // If we filtered by ID or name through the API, we should have a specific result + if filterByID || filterByName { + // Since we used API filtering, the first result should be our match + domain = csDomains.Domains[0] + log.Printf("[DEBUG] Using API-filtered domain: %s", domain.Name) + } else { + // If no filters were applied, we need to handle this case + // This shouldn't happen with the current schema as filters are required + return fmt.Errorf("no filter criteria specified") + } + } + + if domain == nil { + if filterByName { + return fmt.Errorf("no domain found with name: %s", filterName) + } else if filterByID { + return fmt.Errorf("no domain found with ID: %s", filterValue) + } else { + return fmt.Errorf("no domain found matching the specified criteria") + } + } + + log.Printf("[DEBUG] Selected domain: %s (ID: %s)", domain.Name, domain.Id) + + return domainDescriptionAttributes(d, domain) +} + +func domainDescriptionAttributes(d *schema.ResourceData, domain *cloudstack.Domain) error { + d.SetId(domain.Id) + d.Set("domain_id", domain.Id) + d.Set("name", domain.Name) + d.Set("network_domain", domain.Networkdomain) + d.Set("parent_domain_id", domain.Parentdomainid) + + return nil +} diff --git a/cloudstack/data_source_cloudstack_domain_test.go b/cloudstack/data_source_cloudstack_domain_test.go new file mode 100644 index 00000000..3a6e98b5 --- /dev/null +++ b/cloudstack/data_source_cloudstack_domain_test.go @@ -0,0 +1,133 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package cloudstack + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAccCloudstackDomainDataSource_basic(t *testing.T) { + resourceName := "data.cloudstack_domain.my_domain" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudstackDomainDataSource_basic(), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudstackDomainDataSourceExists(resourceName), + resource.TestCheckResourceAttr(resourceName, "name", "ROOT"), + ), + }, + }, + }) +} + +func testAccCheckCloudstackDomainDataSourceExists(n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Domain ID is set") + } + + return nil + } +} + +func TestAccCloudstackDomainDataSource_invalidName(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudstackDomainDataSource_invalidName(), + ExpectError: regexp.MustCompile("no domain found with name: badgerbearocto"), + }, + }, + }) +} + +func testAccCloudstackDomainDataSource_basic() string { + return ` +data "cloudstack_domain" "my_domain" { + filter { + name = "name" + value = "ROOT" + } +} +` +} + +func testAccCloudstackDomainDataSource_invalidName() string { + return ` +data "cloudstack_domain" "my_domain" { + filter { + name = "name" + value = "badgerbearocto" + } +} +` +} + +func TestAccCloudstackDomainDataSource_byID(t *testing.T) { + domainResourceName := "cloudstack_domain.test_domain" + dataSourceName := "data.cloudstack_domain.my_domain_by_id" + testDomainName := "test-domain-" + id.UniqueId() + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudstackDomainDataSource_byID(testDomainName), + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudstackDomainDataSourceExists(dataSourceName), + resource.TestCheckResourceAttrPair(dataSourceName, "name", domainResourceName, "name"), + resource.TestCheckResourceAttrPair(dataSourceName, "domain_id", domainResourceName, "id"), + ), + }, + }, + }) +} + +func testAccCloudstackDomainDataSource_byID(domainName string) string { + return fmt.Sprintf(` +resource "cloudstack_domain" "test_domain" { + name = "%s" +} + +data "cloudstack_domain" "my_domain_by_id" { + filter { + name = "id" + value = cloudstack_domain.test_domain.id + } +} +`, domainName) +} diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 877325ee..fb4b1d6c 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -90,6 +90,7 @@ func Provider() *schema.Provider { "cloudstack_user": dataSourceCloudstackUser(), "cloudstack_vpn_connection": dataSourceCloudstackVPNConnection(), "cloudstack_pod": dataSourceCloudstackPod(), + "cloudstack_domain": dataSourceCloudstackDomain(), "cloudstack_physicalnetwork": dataSourceCloudStackPhysicalNetwork(), }, diff --git a/website/docs/d/domain.html.markdown b/website/docs/d/domain.html.markdown new file mode 100644 index 00000000..d7318b04 --- /dev/null +++ b/website/docs/d/domain.html.markdown @@ -0,0 +1,39 @@ +--- +layout: default +page_title: "CloudStack: cloudstack_domain Data Source" +sidebar_current: "docs-cloudstack-datasource-domain" +description: |- + Retrieves information about a Domain +--- + +# CloudStack: cloudstack_domain Data Source + +A `cloudstack_domain` data source retrieves information about a domain within CloudStack. + +## Example Usage + +```hcl +data "cloudstack_domain" "my_domain" { + filter { + name = "name" + value = "ROOT" + } +} +``` + +## Argument Reference + +The following arguments are supported: + +* `filter` - (Required) A block to filter the domains. The filter block supports the following: + * `name` - (Required) The name of the filter. + * `value` - (Required) The value of the filter. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the domain. +* `name` - The name of the domain. +* `network_domain` - The network domain for the domain. +* `parent_domain_id` - The ID of the parent domain.