diff --git a/cloudstack/data_source_cloudstack_cluster.go b/cloudstack/data_source_cloudstack_cluster.go new file mode 100644 index 00000000..ee1adcde --- /dev/null +++ b/cloudstack/data_source_cloudstack_cluster.go @@ -0,0 +1,255 @@ +// +// 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" + "reflect" + "regexp" + "strings" + + "github.com/apache/cloudstack-go/v2/cloudstack" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func dataSourceCloudstackCluster() *schema.Resource { + return &schema.Resource{ + Read: datasourceCloudStackClusterRead, + Schema: map[string]*schema.Schema{ + "filter": dataSourceFiltersSchema(), + + //Computed values + "id": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "cluster_type": { + Type: schema.TypeString, + Computed: true, + }, + "hypervisor": { + Type: schema.TypeString, + Computed: true, + }, + "pod_id": { + Type: schema.TypeString, + Computed: true, + }, + "pod_name": { + Type: schema.TypeString, + Computed: true, + }, + "zone_id": { + Type: schema.TypeString, + Computed: true, + }, + "zone_name": { + Type: schema.TypeString, + Computed: true, + }, + "allocation_state": { + Type: schema.TypeString, + Computed: true, + }, + "managed_state": { + Type: schema.TypeString, + Computed: true, + }, + "cpu_overcommit_ratio": { + Type: schema.TypeString, + Computed: true, + }, + "memory_overcommit_ratio": { + Type: schema.TypeString, + Computed: true, + }, + "arch": { + Type: schema.TypeString, + Computed: true, + }, + "ovm3vip": { + Type: schema.TypeString, + Computed: true, + }, + "capacity": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "capacity_allocated": { + Type: schema.TypeInt, + Computed: true, + }, + "capacity_total": { + Type: schema.TypeInt, + Computed: true, + }, + "capacity_used": { + Type: schema.TypeInt, + Computed: true, + }, + "cluster_id": { + Type: schema.TypeString, + Computed: true, + }, + "cluster_name": { + Type: schema.TypeString, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Computed: true, + }, + "percent_used": { + Type: schema.TypeInt, + Computed: true, + }, + "pod_id": { + Type: schema.TypeString, + Computed: true, + }, + "pod_name": { + Type: schema.TypeString, + Computed: true, + }, + "type": { + Type: schema.TypeString, + Computed: true, + }, + "zone_id": { + Type: schema.TypeString, + Computed: true, + }, + "zone_name": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + } +} + +func dsFlattenClusterCapacity(capacity []cloudstack.ClusterCapacity) []map[string]interface{} { + cap := make([]map[string]interface{}, len(capacity)) + for i, c := range capacity { + cap[i] = map[string]interface{}{ + "capacity_allocated": c.Capacityallocated, + "capacity_total": c.Capacitytotal, + "capacity_used": c.Capacityused, + "cluster_id": c.Clusterid, + "cluster_name": c.Clustername, + "name": c.Name, + "percent_used": c.Percentused, + "pod_id": c.Podid, + "pod_name": c.Podname, + "type": c.Type, + "zone_id": c.Zoneid, + "zone_name": c.Zonename, + } + } + return cap +} + +func datasourceCloudStackClusterRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + p := cs.Cluster.NewListClustersParams() + + csClusters, err := cs.Cluster.ListClusters(p) + if err != nil { + return fmt.Errorf("failed to list clusters: %s", err) + } + + filters := d.Get("filter") + + for _, cluster := range csClusters.Clusters { + match, err := applyClusterFilters(cluster, filters.(*schema.Set)) + if err != nil { + return err + } + if match { + return clusterDescriptionAttributes(d, cluster) + } + } + + return fmt.Errorf("no clusters found") +} + +func clusterDescriptionAttributes(d *schema.ResourceData, cluster *cloudstack.Cluster) error { + d.SetId(cluster.Id) + + fields := map[string]interface{}{ + "id": cluster.Id, + "name": cluster.Name, + "cluster_type": cluster.Clustertype, + "hypervisor": cluster.Hypervisortype, + "pod_id": cluster.Podid, + "pod_name": cluster.Podname, + "zone_id": cluster.Zoneid, + "zone_name": cluster.Zonename, + "allocation_state": cluster.Allocationstate, + "managed_state": cluster.Managedstate, + "cpu_overcommit_ratio": cluster.Cpuovercommitratio, + "memory_overcommit_ratio": cluster.Memoryovercommitratio, + "arch": cluster.Arch, + "ovm3vip": cluster.Ovm3vip, + "capacity": dsFlattenClusterCapacity(cluster.Capacity), + } + + for k, v := range fields { + if err := d.Set(k, v); err != nil { + log.Printf("[WARN] Error setting %s: %s", k, err) + } + } + + return nil +} + +func applyClusterFilters(cluster *cloudstack.Cluster, filters *schema.Set) (bool, error) { + val := reflect.ValueOf(cluster).Elem() + + for _, f := range filters.List() { + filter := f.(map[string]interface{}) + r, err := regexp.Compile(filter["value"].(string)) + if err != nil { + return false, fmt.Errorf("invalid regex: %s", err) + } + updatedName := strings.ReplaceAll(filter["name"].(string), "_", "") + clusterField := val.FieldByNameFunc(func(fieldName string) bool { + if strings.EqualFold(fieldName, updatedName) { + updatedName = fieldName + return true + } + return false + }).String() + + if r.MatchString(clusterField) { + return true, nil + } + } + + return false, nil +} diff --git a/cloudstack/data_source_cloudstack_cluster_test.go b/cloudstack/data_source_cloudstack_cluster_test.go new file mode 100644 index 00000000..e25b4d16 --- /dev/null +++ b/cloudstack/data_source_cloudstack_cluster_test.go @@ -0,0 +1,76 @@ +// +// 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 ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccClusterDataSource_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testClusterDataSourceConfig_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.cloudstack_cluster.test", "name", "terraform-test-cluster"), + ), + }, + }, + }) +} + +const testClusterDataSourceConfig_basic = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } +} + +data "cloudstack_pod" "pod" { + filter { + name = "name" + value = "POD0" + } +} + +# Create a cluster first +resource "cloudstack_cluster" "test_cluster" { + name = "terraform-test-cluster" + cluster_type = "CloudManaged" + hypervisor = "KVM" + pod_id = data.cloudstack_pod.pod.id + zone_id = data.cloudstack_zone.zone.id + arch = "x86_64" +} + +# Then query it with the data source +data "cloudstack_cluster" "test" { + filter { + name = "name" + value = "terraform-test-cluster" + } + depends_on = [cloudstack_cluster.test_cluster] +} +` diff --git a/cloudstack/provider.go b/cloudstack/provider.go index b52b3d3c..f4eedc10 100644 --- a/cloudstack/provider.go +++ b/cloudstack/provider.go @@ -93,6 +93,7 @@ func Provider() *schema.Provider { "cloudstack_domain": dataSourceCloudstackDomain(), "cloudstack_physical_network": dataSourceCloudStackPhysicalNetwork(), "cloudstack_role": dataSourceCloudstackRole(), + "cloudstack_cluster": dataSourceCloudstackCluster(), }, ResourcesMap: map[string]*schema.Resource{ diff --git a/cloudstack/resource_cloudstack_cluster.go b/cloudstack/resource_cloudstack_cluster.go index 48375359..7599af02 100644 --- a/cloudstack/resource_cloudstack_cluster.go +++ b/cloudstack/resource_cloudstack_cluster.go @@ -20,8 +20,13 @@ package cloudstack import ( + "fmt" + "log" + "strings" + "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) func resourceCloudStackCluster() *schema.Resource { @@ -33,115 +38,140 @@ func resourceCloudStackCluster() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Schema: map[string]*schema.Schema{ - "allocation_state": { - Description: "Allocation state of this cluster for allocation of new resources", - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - "cluster_name": { - Description: "the cluster name", - Type: schema.TypeString, - Required: true, + "name": { + Type: schema.TypeString, + Required: true, }, "cluster_type": { - Description: "ype of the cluster: CloudManaged, ExternalManaged", - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "CloudManaged", + "ExternalManaged", + }, false), + }, + "hypervisor": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice([]string{ + "XenServer", + "KVM", + "VMware", + "Hyperv", + "BareMetal", + "Simulator", + "Ovm3", + }, false), + }, + "pod_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "zone_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "allocation_state": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "arch": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "x86_64", + "aarch64", + }, false), }, "guest_vswitch_name": { - Description: "Name of virtual switch used for guest traffic in the cluster. This would override zone wide traffic label setting.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, }, "guest_vswitch_type": { - Description: "Type of virtual switch used for guest traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "vmwaresvs", + "vmwaredvs", + }, false), }, - "hypervisor": { - Description: "hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3", - Type: schema.TypeString, - Required: true, - }, - "ovm3_cluster": { - Description: "Ovm3 native OCFS2 clustering enabled for cluster", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + "ovm3cluster": { + Type: schema.TypeString, + Optional: true, }, - "ovm3_pool": { - Description: "Ovm3 native pooling enabled for cluster", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + "ovm3pool": { + Type: schema.TypeString, + Optional: true, }, - "ovm3_vip": { - Description: "Ovm3 vip to use for pool (and cluster)", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + "ovm3vip": { + Type: schema.TypeString, + Optional: true, + Computed: true, }, "password": { - Description: "the password for the host", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Sensitive: true, }, "public_vswitch_name": { - Description: "Name of virtual switch used for public traffic in the cluster. This would override zone wide traffic label setting.", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, }, "public_vswitch_type": { - Description: "Type of virtual switch used for public traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)", - Type: schema.TypeString, - Optional: true, - ForceNew: true, - }, - "pod_id": { - Description: "Type of virtual switch used for public traffic in the cluster. Allowed values are, vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch)", - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Optional: true, + ValidateFunc: validation.StringInSlice([]string{ + "vmwaresvs", + "vmwaredvs", + }, false), }, "url": { - Description: "the URL", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, }, "username": { - Description: "the username for the cluster", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, }, "vsm_ip_address": { - Description: "the ipaddress of the VSM associated with this cluster", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, }, "vsm_password": { - Description: "the password for the VSM associated with this cluster", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Sensitive: true, }, "vsm_username": { - Description: "the username for the VSM associated with this cluster", - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, }, - "zone_id": { - Description: "the Zone ID for the cluster", - Type: schema.TypeString, - Required: true, + "pod_name": { + Type: schema.TypeString, + Computed: true, + }, + "zone_name": { + Type: schema.TypeString, + Computed: true, + }, + "managed_state": { + Type: schema.TypeString, + Computed: true, + }, + "cpu_overcommit_ratio": { + Type: schema.TypeString, + Computed: true, + }, + "memory_overcommit_ratio": { + Type: schema.TypeString, + Computed: true, }, }, } @@ -150,57 +180,82 @@ func resourceCloudStackCluster() *schema.Resource { func resourceCloudStackClusterCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - p := cs.Cluster.NewAddClusterParams(d.Get("cluster_name").(string), d.Get("cluster_type").(string), d.Get("hypervisor").(string), d.Get("pod_id").(string), d.Get("zone_id").(string)) - if v, ok := d.GetOk("allocation_state"); ok { - p.SetAllocationstate(v.(string)) + name := d.Get("name").(string) + clusterType := d.Get("cluster_type").(string) + hypervisor := d.Get("hypervisor").(string) + podID := d.Get("pod_id").(string) + zoneID := d.Get("zone_id").(string) + + // Create a new parameter struct + p := cs.Cluster.NewAddClusterParams(name, clusterType, hypervisor, podID, zoneID) + + // Set optional parameters + if allocationState, ok := d.GetOk("allocation_state"); ok { + p.SetAllocationstate(allocationState.(string)) } - if v, ok := d.GetOk("guest_vswitch_name"); ok { - p.SetGuestvswitchname(v.(string)) + + if arch, ok := d.GetOk("arch"); ok { + p.SetArch(arch.(string)) } - if v, ok := d.GetOk("guest_vswitch_type"); ok { - p.SetGuestvswitchtype(v.(string)) + + if guestVSwitchName, ok := d.GetOk("guest_vswitch_name"); ok { + p.SetGuestvswitchname(guestVSwitchName.(string)) } - if v, ok := d.GetOk("hypervisor"); ok { - p.SetHypervisor(v.(string)) + + if guestVSwitchType, ok := d.GetOk("guest_vswitch_type"); ok { + p.SetGuestvswitchtype(guestVSwitchType.(string)) } - if v, ok := d.GetOk("ovm3_cluster"); ok { - p.SetOvm3cluster(v.(string)) + + if ovm3cluster, ok := d.GetOk("ovm3cluster"); ok { + p.SetOvm3cluster(ovm3cluster.(string)) } - if v, ok := d.GetOk("ovm3_pool"); ok { - p.SetOvm3pool(v.(string)) + + if ovm3pool, ok := d.GetOk("ovm3pool"); ok { + p.SetOvm3pool(ovm3pool.(string)) } - if v, ok := d.GetOk("ovm3_vip"); ok { - p.SetOvm3vip(v.(string)) + + if ovm3vip, ok := d.GetOk("ovm3vip"); ok { + p.SetOvm3vip(ovm3vip.(string)) } - if v, ok := d.GetOk("password"); ok { - p.SetPassword(v.(string)) + + if password, ok := d.GetOk("password"); ok { + p.SetPassword(password.(string)) } - if v, ok := d.GetOk("public_vswitch_name"); ok { - p.SetPublicvswitchname(v.(string)) + + if publicVSwitchName, ok := d.GetOk("public_vswitch_name"); ok { + p.SetPublicvswitchname(publicVSwitchName.(string)) } - if v, ok := d.GetOk("public_vswitch_type"); ok { - p.SetPublicvswitchtype(v.(string)) + + if publicVSwitchType, ok := d.GetOk("public_vswitch_type"); ok { + p.SetPublicvswitchtype(publicVSwitchType.(string)) } - if v, ok := d.GetOk("url"); ok { - p.SetUrl(v.(string)) + + if url, ok := d.GetOk("url"); ok { + p.SetUrl(url.(string)) } - if v, ok := d.GetOk("username"); ok { - p.SetUsername(v.(string)) + + if username, ok := d.GetOk("username"); ok { + p.SetUsername(username.(string)) } - if v, ok := d.GetOk("vsm_ip_address"); ok { - p.SetVsmipaddress(v.(string)) + + if vsmIPAddress, ok := d.GetOk("vsm_ip_address"); ok { + p.SetVsmipaddress(vsmIPAddress.(string)) } - if v, ok := d.GetOk("vsm_password"); ok { - p.SetVsmpassword(v.(string)) + + if vsmPassword, ok := d.GetOk("vsm_password"); ok { + p.SetVsmpassword(vsmPassword.(string)) } - if v, ok := d.GetOk("vsm_username"); ok { - p.SetVsmusername(v.(string)) + + if vsmUsername, ok := d.GetOk("vsm_username"); ok { + p.SetVsmusername(vsmUsername.(string)) } + log.Printf("[DEBUG] Creating Cluster %s", name) r, err := cs.Cluster.AddCluster(p) if err != nil { - return err + return fmt.Errorf("Error creating Cluster %s: %s", name, err) } + d.SetId(r.Id) return resourceCloudStackClusterRead(d, meta) @@ -209,18 +264,30 @@ func resourceCloudStackClusterCreate(d *schema.ResourceData, meta interface{}) e func resourceCloudStackClusterRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - r, _, err := cs.Cluster.GetClusterByID(d.Id()) + // Get the Cluster details + c, count, err := cs.Cluster.GetClusterByID(d.Id()) if err != nil { + if count == 0 { + log.Printf("[DEBUG] Cluster %s does no longer exist", d.Get("name").(string)) + d.SetId("") + return nil + } return err } - d.Set("allocation_state", r.Allocationstate) - d.Set("cluster_type", r.Clustertype) - d.Set("hypervisor", r.Hypervisortype) - d.Set("cluster_name", r.Name) - d.Set("ovm3_vip", r.Ovm3vip) - d.Set("pod_id", r.Podid) - d.Set("zone_id", r.Zoneid) + d.Set("name", c.Name) + d.Set("cluster_type", c.Clustertype) + d.Set("hypervisor", c.Hypervisortype) + d.Set("pod_id", c.Podid) + d.Set("pod_name", c.Podname) + d.Set("zone_id", c.Zoneid) + d.Set("zone_name", c.Zonename) + d.Set("allocation_state", c.Allocationstate) + d.Set("managed_state", c.Managedstate) + d.Set("cpu_overcommit_ratio", c.Cpuovercommitratio) + d.Set("memory_overcommit_ratio", c.Memoryovercommitratio) + d.Set("arch", c.Arch) + d.Set("ovm3vip", c.Ovm3vip) return nil } @@ -228,23 +295,22 @@ func resourceCloudStackClusterRead(d *schema.ResourceData, meta interface{}) err func resourceCloudStackClusterUpdate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + // Create a new parameter struct p := cs.Cluster.NewUpdateClusterParams(d.Id()) - if v, ok := d.GetOk("allocation_state"); ok { - p.SetAllocationstate(v.(string)) - } - if v, ok := d.GetOk("cluster_name"); ok { - p.SetClustername(v.(string)) - } - if v, ok := d.GetOk("cluster_type"); ok { - p.SetClustertype(v.(string)) + + if d.HasChange("name") { + p.SetClustername(d.Get("name").(string)) } - if v, ok := d.GetOk("hypervisor"); ok { - p.SetHypervisor(v.(string)) + + if d.HasChange("allocation_state") { + p.SetAllocationstate(d.Get("allocation_state").(string)) } + // Note: managed_state is a computed field and cannot be set directly + _, err := cs.Cluster.UpdateCluster(p) if err != nil { - return err + return fmt.Errorf("Error updating Cluster %s: %s", d.Get("name").(string), err) } return resourceCloudStackClusterRead(d, meta) @@ -253,9 +319,21 @@ func resourceCloudStackClusterUpdate(d *schema.ResourceData, meta interface{}) e func resourceCloudStackClusterDelete(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - _, err := cs.Cluster.DeleteCluster(cs.Cluster.NewDeleteClusterParams(d.Id())) + // Create a new parameter struct + p := cs.Cluster.NewDeleteClusterParams(d.Id()) + + log.Printf("[DEBUG] Deleting Cluster %s", d.Get("name").(string)) + _, err := cs.Cluster.DeleteCluster(p) + if err != nil { - return err + // This is a very poor way to be told the ID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil + } + + return fmt.Errorf("Error deleting Cluster %s: %s", d.Get("name").(string), err) } return nil diff --git a/cloudstack/resource_cloudstack_cluster_test.go b/cloudstack/resource_cloudstack_cluster_test.go index 6c4ec939..bc77faeb 100644 --- a/cloudstack/resource_cloudstack_cluster_test.go +++ b/cloudstack/resource_cloudstack_cluster_test.go @@ -20,46 +20,153 @@ package cloudstack import ( + "fmt" "testing" + "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackCluster_basic(t *testing.T) { + var cluster cloudstack.Cluster + resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackClusterDestroy, Steps: []resource.TestStep{ { Config: testAccCloudStackCluster_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackClusterExists( + "cloudstack_cluster.foo", &cluster), + testAccCheckCloudStackClusterAttributes(&cluster), + resource.TestCheckResourceAttr( + "cloudstack_cluster.foo", "name", "terraform-cluster"), + ), }, }, }) } -const testAccCloudStackCluster_basic = ` -resource "cloudstack_zone" "test" { - name = "acc_zone" - dns1 = "8.8.8.8" - dns2 = "8.8.8.8" - internal_dns1 = "8.8.4.4" - internal_dns2 = "8.8.4.4" - network_type = "Advanced" - domain = "cloudstack.apache.org" +func TestAccCloudStackCluster_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackClusterDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackCluster_basic, + }, + + { + ResourceName: "cloudstack_cluster.foo", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{ + "password", "vsm_password", + }, + }, + }, + }) +} + +func testAccCheckCloudStackClusterExists( + n string, cluster *cloudstack.Cluster) 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 Cluster ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + c, count, err := cs.Cluster.GetClusterByID(rs.Primary.ID) + if err != nil { + return err + } + + if count == 0 { + return fmt.Errorf("Cluster not found") + } + + *cluster = *c + + return nil + } +} + +func testAccCheckCloudStackClusterAttributes( + cluster *cloudstack.Cluster) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if cluster.Name != "terraform-cluster" { + return fmt.Errorf("Bad name: %s", cluster.Name) + } + + if cluster.Clustertype != "CloudManaged" { + return fmt.Errorf("Bad cluster type: %s", cluster.Clustertype) + } + + if cluster.Hypervisortype != "KVM" { + return fmt.Errorf("Bad hypervisor: %s", cluster.Hypervisortype) + } + + return nil + } +} + +func testAccCheckCloudStackClusterDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_cluster" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Cluster ID is set") + } + + _, count, err := cs.Cluster.GetClusterByID(rs.Primary.ID) + if err != nil { + return nil + } + + if count > 0 { + return fmt.Errorf("Cluster %s still exists", rs.Primary.ID) + } + } + + return nil } -resource "cloudstack_pod" "test" { - allocation_state = "Disabled" - gateway = "172.30.0.1" - name = "acc_pod" - netmask = "255.255.240.0" - start_ip = "172.30.0.2" - zone_id = cloudstack_zone.test.id + +const testAccCloudStackCluster_basic = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } } -resource "cloudstack_cluster" "test" { - cluster_name = "acc_cluster" - cluster_type = "CloudManaged" - hypervisor = "KVM" - pod_id = cloudstack_pod.test.id - zone_id = cloudstack_zone.test.id + +resource "cloudstack_pod" "foopod" { + name = "terraform-pod" + zone_id = data.cloudstack_zone.zone.id + gateway = "192.168.56.1" + netmask = "255.255.255.0" + start_ip = "192.168.56.2" + end_ip = "192.168.56.254" } -` + +resource "cloudstack_cluster" "foo" { + name = "terraform-cluster" + cluster_type = "CloudManaged" + hypervisor = "KVM" + pod_id = cloudstack_pod.foopod.id + zone_id = data.cloudstack_zone.zone.id + arch = "x86_64" +}` diff --git a/cloudstack/resource_cloudstack_pod.go b/cloudstack/resource_cloudstack_pod.go index efbaddd7..e73e31ac 100644 --- a/cloudstack/resource_cloudstack_pod.go +++ b/cloudstack/resource_cloudstack_pod.go @@ -20,6 +20,8 @@ package cloudstack import ( + "fmt" + "log" "strings" "github.com/apache/cloudstack-go/v2/cloudstack" @@ -35,42 +37,46 @@ func resourceCloudStackPod() *schema.Resource { Importer: &schema.ResourceImporter{ State: schema.ImportStatePassthrough, }, + Schema: map[string]*schema.Schema{ - "allocation_state": { - Description: "Allocation state of this Pod for allocation of new resources", - Type: schema.TypeString, - Optional: true, + "name": { + Type: schema.TypeString, + Required: true, }, - "end_ip": { - Description: "he ending IP address for the Pod", - Type: schema.TypeString, - Optional: true, - Computed: true, + "zone_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, "gateway": { - Description: "the gateway for the Pod", - Type: schema.TypeString, - Required: true, - }, - "name": { - Description: "the name of the Pod", - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, }, "netmask": { - Description: "the netmask for the Pod", - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, }, "start_ip": { - Description: "the starting IP address for the Pod", - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, }, - "zone_id": { - Description: "the Zone ID in which the Pod will be created", - Type: schema.TypeString, - Required: true, + "end_ip": { + Type: schema.TypeString, + Required: true, + }, + "allocation_state": { + Type: schema.TypeString, + Computed: true, + }, + "zone_name": { + Type: schema.TypeString, + Computed: true, + }, + // VLAN ID is not directly settable in the CreatePodParams + // It's returned in the response but can't be set during creation + "vlan_id": { + Type: schema.TypeString, + Computed: true, }, }, } @@ -79,32 +85,34 @@ func resourceCloudStackPod() *schema.Resource { func resourceCloudStackPodCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - p := cs.Pod.NewCreatePodParams(d.Get("name").(string), d.Get("zone_id").(string)) - if v, ok := d.GetOk("allocation_state"); ok { - p.SetAllocationstate(v.(string)) - } - if v, ok := d.GetOk("end_ip"); ok { - p.SetEndip(v.(string)) - } - if v, ok := d.GetOk("gateway"); ok { - p.SetGateway(v.(string)) - } - if v, ok := d.GetOk("netmask"); ok { - p.SetNetmask(v.(string)) - } - if v, ok := d.GetOk("start_ip"); ok { - p.SetStartip(v.(string)) - } - if v, ok := d.GetOk("zone_id"); ok { - p.SetZoneid(v.(string)) + name := d.Get("name").(string) + zoneID := d.Get("zone_id").(string) + gateway := d.Get("gateway").(string) + netmask := d.Get("netmask").(string) + startIP := d.Get("start_ip").(string) + + // Create a new parameter struct + p := cs.Pod.NewCreatePodParams(name, zoneID) + + // Set required parameters + p.SetGateway(gateway) + p.SetNetmask(netmask) + p.SetStartip(startIP) + + // Set optional parameters + if endIP, ok := d.GetOk("end_ip"); ok { + p.SetEndip(endIP.(string)) } - r, err := cs.Pod.CreatePod(p) + // Note: VLAN ID is not directly settable in the CreatePodParams + + log.Printf("[DEBUG] Creating Pod %s", name) + pod, err := cs.Pod.CreatePod(p) if err != nil { - return err + return fmt.Errorf("Error creating Pod %s: %s", name, err) } - d.SetId(r.Id) + d.SetId(pod.Id) return resourceCloudStackPodRead(d, meta) } @@ -112,18 +120,41 @@ func resourceCloudStackPodCreate(d *schema.ResourceData, meta interface{}) error func resourceCloudStackPodRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - r, _, err := cs.Pod.GetPodByID(d.Id()) + // Get the Pod details + p := cs.Pod.NewListPodsParams() + p.SetId(d.Id()) + + pods, err := cs.Pod.ListPods(p) if err != nil { - return err + return fmt.Errorf("Error getting Pod %s: %s", d.Id(), err) + } + + if pods.Count == 0 { + log.Printf("[DEBUG] Pod %s does no longer exist", d.Id()) + d.SetId("") + return nil + } + + pod := pods.Pods[0] + + d.Set("name", pod.Name) + d.Set("zone_id", pod.Zoneid) + d.Set("zone_name", pod.Zonename) + d.Set("gateway", pod.Gateway) + d.Set("netmask", pod.Netmask) + d.Set("allocation_state", pod.Allocationstate) + + if len(pod.Startip) > 0 { + d.Set("start_ip", pod.Startip[0]) + } + + if len(pod.Endip) > 0 { + d.Set("end_ip", pod.Endip[0]) } - d.Set("allocation_state", r.Allocationstate) - d.Set("end_ip", strings.Join(r.Endip, " ")) - d.Set("gateway", r.Gateway) - d.Set("name", r.Name) - d.Set("netmask", r.Netmask) - d.Set("start_ip", strings.Join(r.Startip, " ")) - d.Set("zone_id", r.Zoneid) + if len(pod.Vlanid) > 0 { + d.Set("vlan_id", pod.Vlanid[0]) + } return nil } @@ -131,29 +162,32 @@ func resourceCloudStackPodRead(d *schema.ResourceData, meta interface{}) error { func resourceCloudStackPodUpdate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) + // Create a new parameter struct p := cs.Pod.NewUpdatePodParams(d.Id()) - if v, ok := d.GetOk("allocation_state"); ok { - p.SetAllocationstate(v.(string)) - } - if v, ok := d.GetOk("end_ip"); ok { - p.SetEndip(v.(string)) + + if d.HasChange("name") { + p.SetName(d.Get("name").(string)) } - if v, ok := d.GetOk("gateway"); ok { - p.SetGateway(v.(string)) + + if d.HasChange("gateway") { + p.SetGateway(d.Get("gateway").(string)) } - if v, ok := d.GetOk("name"); ok { - p.SetName(v.(string)) + + if d.HasChange("netmask") { + p.SetNetmask(d.Get("netmask").(string)) } - if v, ok := d.GetOk("netmask"); ok { - p.SetNetmask(v.(string)) + + if d.HasChange("start_ip") { + p.SetStartip(d.Get("start_ip").(string)) } - if v, ok := d.GetOk("start_ip"); ok { - p.SetStartip(v.(string)) + + if d.HasChange("end_ip") { + p.SetEndip(d.Get("end_ip").(string)) } _, err := cs.Pod.UpdatePod(p) if err != nil { - return err + return fmt.Errorf("Error updating Pod %s: %s", d.Get("name").(string), err) } return resourceCloudStackPodRead(d, meta) @@ -162,9 +196,21 @@ func resourceCloudStackPodUpdate(d *schema.ResourceData, meta interface{}) error func resourceCloudStackPodDelete(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - _, err := cs.Pod.DeletePod(cs.Pod.NewDeletePodParams(d.Id())) + // Create a new parameter struct + p := cs.Pod.NewDeletePodParams(d.Id()) + + log.Printf("[DEBUG] Deleting Pod %s", d.Get("name").(string)) + _, err := cs.Pod.DeletePod(p) + if err != nil { - return err + // This is a very poor way to be told the ID does no longer exist :( + if strings.Contains(err.Error(), fmt.Sprintf( + "Invalid parameter id value=%s due to incorrect long value format, "+ + "or entity does not exist", d.Id())) { + return nil + } + + return fmt.Errorf("Error deleting Pod %s: %s", d.Get("name").(string), err) } return nil diff --git a/cloudstack/resource_cloudstack_pod_test.go b/cloudstack/resource_cloudstack_pod_test.go index f80dd87d..7b7c8ab0 100644 --- a/cloudstack/resource_cloudstack_pod_test.go +++ b/cloudstack/resource_cloudstack_pod_test.go @@ -20,64 +20,148 @@ package cloudstack import ( + "fmt" "testing" + "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccCloudStackPod_basic(t *testing.T) { + var pod cloudstack.Pod + resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackPodDestroy, Steps: []resource.TestStep{ { Config: testAccCloudStackPod_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackPodExists( + "cloudstack_pod.foo", &pod), + testAccCheckCloudStackPodAttributes(&pod), + resource.TestCheckResourceAttr( + "cloudstack_pod.foo", "name", "terraform-pod"), + ), }, - // { - // Config: testAccCloudStackPod_update, - // Check: resource.ComposeTestCheckFunc( - // resource.TestCheckResourceAttr("cloudstack_pod.test", "name", "accpod2"), - // ), - // }, }, }) } -const testAccCloudStackPod_basic = ` -resource "cloudstack_zone" "test" { - name = "acc_zone" - dns1 = "8.8.8.8" - dns2 = "8.8.8.8" - internal_dns1 = "8.8.4.4" - internal_dns2 = "8.8.4.4" - network_type = "Advanced" - domain = "cloudstack.apache.org" +func TestAccCloudStackPod_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackPodDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackPod_basic, + }, + + { + ResourceName: "cloudstack_pod.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckCloudStackPodExists( + n string, pod *cloudstack.Pod) 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 Pod ID is set") + } + + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + p := cs.Pod.NewListPodsParams() + p.SetId(rs.Primary.ID) + + list, err := cs.Pod.ListPods(p) + if err != nil { + return err + } + + if list.Count != 1 || list.Pods[0].Id != rs.Primary.ID { + return fmt.Errorf("Pod not found") + } + + *pod = *list.Pods[0] + + return nil + } } -resource "cloudstack_pod" "test" { - name = "acc_pod" - allocation_state = "Disabled" - gateway = "172.29.0.1" - netmask = "255.255.240.0" - start_ip = "172.29.0.2" - zone_id = cloudstack_zone.test.id + +func testAccCheckCloudStackPodAttributes( + pod *cloudstack.Pod) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if pod.Name != "terraform-pod" { + return fmt.Errorf("Bad name: %s", pod.Name) + } + + if pod.Gateway != "192.168.56.1" { + return fmt.Errorf("Bad gateway: %s", pod.Gateway) + } + + if pod.Netmask != "255.255.255.0" { + return fmt.Errorf("Bad netmask: %s", pod.Netmask) + } + + return nil + } +} + +func testAccCheckCloudStackPodDestroy(s *terraform.State) error { + cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "cloudstack_pod" { + continue + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No Pod ID is set") + } + + p := cs.Pod.NewListPodsParams() + p.SetId(rs.Primary.ID) + + list, err := cs.Pod.ListPods(p) + if err != nil { + return nil + } + + if list.Count > 0 { + return fmt.Errorf("Pod %s still exists", rs.Primary.ID) + } + } + + return nil } -` - -const testAccCloudStackPod_update = ` -resource "cloudstack_zone" "test" { - name = "acc_zone" - dns1 = "8.8.8.8" - dns2 = "8.8.8.8" - internal_dns1 = "8.8.4.4" - internal_dns2 = "8.8.4.4" - network_type = "Advanced" - domain = "cloudstack.apache.org" + +const testAccCloudStackPod_basic = ` +data "cloudstack_zone" "zone" { + filter { + name = "name" + value = "Sandbox-simulator" + } } -resource "cloudstack_pod" "test" { - name = "acc_pod2" - allocation_state = "Disabled" - gateway = "172.29.0.1" - netmask = "255.255.240.0" - start_ip = "172.29.0.3" - zone_id = cloudstack_zone.test.id -` + +# Create a pod in the zone +resource "cloudstack_pod" "foo" { + name = "terraform-pod" + zone_id = data.cloudstack_zone.zone.id + gateway = "192.168.56.1" + netmask = "255.255.255.0" + start_ip = "192.168.56.2" + end_ip = "192.168.56.254" +}` diff --git a/go.mod b/go.mod index c09a7760..80df39ea 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect + github.com/kisielk/errcheck v1.9.0 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect @@ -70,12 +71,12 @@ require ( github.com/zclconf/go-cty v1.14.3 // indirect go.uber.org/mock v0.5.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/mod v0.18.0 // indirect + golang.org/x/mod v0.23.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect - golang.org/x/tools v0.22.0 // indirect + golang.org/x/tools v0.30.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect google.golang.org/grpc v1.62.1 // indirect diff --git a/go.sum b/go.sum index 3c302c02..cebfca28 100644 --- a/go.sum +++ b/go.sum @@ -103,6 +103,8 @@ github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgf github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= +github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -171,6 +173,8 @@ golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZv golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0= golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= +golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= @@ -210,6 +214,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.22.0 h1:gqSGLZqv+AI9lIQzniJ0nZDRG5GBPsSi+DRNHWNz6yA= golang.org/x/tools v0.22.0/go.mod h1:aCwcsjqvq7Yqt6TNyX7QMU2enbQ/Gt0bo6krSeEri+c= +golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= +golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -230,4 +236,4 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= \ No newline at end of file +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/website/docs/d/cluster.html.markdown b/website/docs/d/cluster.html.markdown new file mode 100644 index 00000000..95a70685 --- /dev/null +++ b/website/docs/d/cluster.html.markdown @@ -0,0 +1,74 @@ +--- +subcategory: "Cluster" +layout: "cloudstack" +page_title: "CloudStack: cloudstack_cluster" +description: |- + Gets information about a cluster. +--- + +# cloudstack_cluster + +Use this data source to get information about a cluster for use in other resources. + +## Example Usage + +```hcl +data "cloudstack_cluster" "cluster" { + filter { + name = "name" + value = "cluster-1" + } +} + +output "cluster_id" { + value = data.cloudstack_cluster.cluster.id +} +``` + +## Argument Reference + +The following arguments are supported: + +* `filter` - (Required) One or more name/value pairs to filter off of. See detailed documentation below. + +### Filter Arguments + +* `name` - (Required) The name of the field to filter on. This can be any of the fields returned by the CloudStack API. +* `value` - (Required) The value to filter on. This should be a regular expression. + +## Attributes Reference + +The following attributes are exported: + +* `id` - The ID of the cluster. +* `name` - The name of the cluster. +* `cluster_type` - Type of the cluster: CloudManaged, ExternalManaged. +* `hypervisor` - Hypervisor type of the cluster: XenServer, KVM, VMware, Hyperv, BareMetal, Simulator, Ovm3. +* `pod_id` - The Pod ID for the cluster. +* `pod_name` - The name of the pod where the cluster is created. +* `zone_id` - The Zone ID for the cluster. +* `zone_name` - The name of the zone where the cluster is created. +* `allocation_state` - The allocation state of the cluster. +* `managed_state` - The managed state of the cluster. +* `cpu_overcommit_ratio` - The CPU overcommit ratio of the cluster. +* `memory_overcommit_ratio` - The memory overcommit ratio of the cluster. +* `arch` - The CPU arch of the cluster. +* `ovm3vip` - Ovm3 vip used for pool (and cluster). +* `capacity` - The capacity information of the cluster. See Capacity below for more details. + +### Capacity + +The `capacity` attribute supports the following: + +* `capacity_allocated` - The capacity allocated. +* `capacity_total` - The total capacity. +* `capacity_used` - The capacity used. +* `cluster_id` - The ID of the cluster. +* `cluster_name` - The name of the cluster. +* `name` - The name of the capacity. +* `percent_used` - The percentage of capacity used. +* `pod_id` - The ID of the pod. +* `pod_name` - The name of the pod. +* `type` - The type of the capacity. +* `zone_id` - The ID of the zone. +* `zone_name` - The name of the zone. \ No newline at end of file diff --git a/website/docs/r/cluster.html.markdown b/website/docs/r/cluster.html.markdown index c5da1946..8fd9e06e 100644 --- a/website/docs/r/cluster.html.markdown +++ b/website/docs/r/cluster.html.markdown @@ -1,26 +1,24 @@ --- +subcategory: "Cluster" layout: "cloudstack" page_title: "CloudStack: cloudstack_cluster" -sidebar_current: "docs-cloudstack-resource-cluster" description: |- - Adds a new cluster + Creates a cluster. --- # cloudstack_cluster -Adds a new cluster +Creates a cluster. ## Example Usage -Basic usage: - ```hcl -resource "cloudstack_cluster" "example" { - cluster_name = "example" - cluster_type = "CloudManaged" - hypervisor = "KVM" - pod_id = cloudstack_pod.example.id - zone_id = cloudstack_zone.example.id +resource "cloudstack_cluster" "default" { + name = "cluster-1" + cluster_type = "CloudManaged" + hypervisor = "KVM" + pod_id = "1" + zone_id = "1" } ``` @@ -28,6 +26,28 @@ resource "cloudstack_cluster" "example" { The following arguments are supported: +<<<<<<< HEAD +* `name` - (Required) The name of the cluster. +* `cluster_type` - (Required) Type of the cluster: CloudManaged, ExternalManaged. +* `hypervisor` - (Required) Hypervisor type of the cluster: XenServer, KVM, VMware, Hyperv, BareMetal, Simulator, Ovm3. +* `pod_id` - (Required) The Pod ID for the cluster. +* `zone_id` - (Required) The Zone ID for the cluster. +* `allocation_state` - (Optional) Allocation state of this cluster for allocation of new resources. +* `arch` - (Optional) The CPU arch of the cluster. Valid options are: x86_64, aarch64. +* `guest_vswitch_name` - (Optional) Name of virtual switch used for guest traffic in the cluster. This would override zone wide traffic label setting. +* `guest_vswitch_type` - (Optional) Type of virtual switch used for guest traffic in the cluster. Allowed values are: vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch). +* `ovm3cluster` - (Optional) Ovm3 native OCFS2 clustering enabled for cluster. +* `ovm3pool` - (Optional) Ovm3 native pooling enabled for cluster. +* `ovm3vip` - (Optional) Ovm3 vip to use for pool (and cluster). +* `password` - (Optional) The password for the host. +* `public_vswitch_name` - (Optional) Name of virtual switch used for public traffic in the cluster. This would override zone wide traffic label setting. +* `public_vswitch_type` - (Optional) Type of virtual switch used for public traffic in the cluster. Allowed values are: vmwaresvs (for VMware standard vSwitch) and vmwaredvs (for VMware distributed vSwitch). +* `url` - (Optional) The URL for the cluster. +* `username` - (Optional) The username for the cluster. +* `vsm_ip_address` - (Optional) The IP address of the VSM associated with this cluster. +* `vsm_password` - (Optional) The password for the VSM associated with this cluster. +* `vsm_username` - (Optional) The username for the VSM associated with this cluster. +======= * `allocation_state` - (Optional) Allocation state of this cluster for allocation of new resources. * `cluster_name` - (Required) the cluster name. * `cluster_type` - (Required) type of the cluster: CloudManaged, ExternalManaged. @@ -48,11 +68,27 @@ The following arguments are supported: * `vsm_username` - (Optional) the username for the VSM associated with this cluster. * `zone_id` - (Required) the Zone ID for the cluster. +>>>>>>> apache/main ## Attributes Reference The following attributes are exported: +<<<<<<< HEAD +* `id` - The ID of the cluster. +* `pod_name` - The name of the pod where the cluster is created. +* `zone_name` - The name of the zone where the cluster is created. +* `managed_state` - The managed state of the cluster. +* `cpu_overcommit_ratio` - The CPU overcommit ratio of the cluster. +* `memory_overcommit_ratio` - The memory overcommit ratio of the cluster. + +## Import + +Clusters can be imported; use `` as the import ID. For example: + +```shell +terraform import cloudstack_cluster.default 5fb02d7f-9513-4f96-9fbe-b5d167f4e90b +======= * `id` - The instance ID. @@ -65,3 +101,4 @@ example: ```shell terraform import cloudstack_cluster.example 5cf69677-7e4b-4bf4-b868-f0b02bb72ee0 ``` +>>>>>>> apache/main diff --git a/website/docs/r/pod.html.markdown b/website/docs/r/pod.html.markdown index 412ef1d5..6978fc96 100644 --- a/website/docs/r/pod.html.markdown +++ b/website/docs/r/pod.html.markdown @@ -1,27 +1,25 @@ --- +subcategory: "Pod" layout: "cloudstack" page_title: "CloudStack: cloudstack_pod" -sidebar_current: "docs-cloudstack-resource-pod" description: |- - Creates a new Pod. + Creates a pod. --- # cloudstack_pod -Creates a new Pod. +Creates a pod. ## Example Usage -Basic usage: - ```hcl -resource "cloudstack_pod" "example" { - allocation_state = "Disabled" - gateway = "172.29.0.1" - name = "example" - netmask = "255.255.240.0" - start_ip = "172.29.0.2" - zone_id = cloudstack_zone.example.id +resource "cloudstack_pod" "default" { + name = "pod-1" + zone_id = "1" + gateway = "10.1.1.1" + netmask = "255.255.255.0" + start_ip = "10.1.1.100" + end_ip = "10.1.1.200" } ``` @@ -29,6 +27,15 @@ resource "cloudstack_pod" "example" { The following arguments are supported: +<<<<<<< HEAD +* `name` - (Required) The name of the pod. +* `zone_id` - (Required) The Zone ID in which the pod will be created. +* `gateway` - (Required) The gateway for the pod. +* `netmask` - (Required) The netmask for the pod. +* `start_ip` - (Required) The starting IP address for the pod. +* `end_ip` - (Required) The ending IP address for the pod. +* `allocation_state` - (Optional) Allocation state of this pod for allocation of new resources. +======= * `allocation_state` - (Optional) allocation state of this Pod for allocation of new resources. * `end_ip` - (Optional) the ending IP address for the Pod. * `gateway` - (Required) the gateway for the Pod. @@ -37,11 +44,25 @@ The following arguments are supported: * `start_ip` - (Required) the starting IP address for the Pod. * `zone_id` - (Required) the Zone ID in which the Pod will be created. +>>>>>>> apache/main ## Attributes Reference The following attributes are exported: +<<<<<<< HEAD +* `id` - The ID of the pod. +* `allocation_state` - The allocation state of the pod. +* `zone_name` - The name of the zone where the pod is created. +* `vlan_id` - The VLAN ID associated with the pod. + +## Import + +Pods can be imported; use `` as the import ID. For example: + +```shell +terraform import cloudstack_pod.default 5fb02d7f-9513-4f96-9fbe-b5d167f4e90b +======= * `id` - The instance ID. @@ -54,3 +75,4 @@ example: ```shell terraform import cloudstack_pod.example 5cf69677-7e4b-4bf4-b868-f0b02bb72ee0 ``` +>>>>>>> apache/main