You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by vi...@apache.org on 2024/03/07 09:06:44 UTC
(cloudstack-terraform-provider) branch main updated: Add Hosts resource and Pod data source (#69)
This is an automated email from the ASF dual-hosted git repository.
vishesh pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git
The following commit(s) were added to refs/heads/main by this push:
new c7f0776 Add Hosts resource and Pod data source (#69)
c7f0776 is described below
commit c7f077646e3f7a3204d062ccca0c3d6e0803debe
Author: Fábio Matavelli <fa...@gmail.com>
AuthorDate: Thu Mar 7 09:06:39 2024 +0000
Add Hosts resource and Pod data source (#69)
---
cloudstack/data_source_cloudstack_pod.go | 281 +++++++++++++++++++++++
cloudstack/data_source_cloudstack_pod_test.go | 50 ++++
cloudstack/provider.go | 2 +
cloudstack/resource_cloudstack_host.go | 316 ++++++++++++++++++++++++++
cloudstack/resource_cloudstack_host_test.go | 143 ++++++++++++
5 files changed, 792 insertions(+)
diff --git a/cloudstack/data_source_cloudstack_pod.go b/cloudstack/data_source_cloudstack_pod.go
new file mode 100644
index 0000000..d6d211e
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_pod.go
@@ -0,0 +1,281 @@
+//
+// 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/helper/schema"
+)
+
+func dataSourceCloudstackPod() *schema.Resource {
+ return &schema.Resource{
+ Read: datasourceCloudStackPodRead,
+ Schema: map[string]*schema.Schema{
+ "filter": dataSourceFiltersSchema(),
+
+ //Computed values
+ "pod_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "zone_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "end_ip": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "gateway": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "netmask": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "start_ip": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "allocation_state": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "zone_name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "vlan_id": {
+ 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,
+ },
+ },
+ },
+ },
+ "ip_ranges": {
+ Type: schema.TypeList,
+ Computed: true,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "end_ip": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "gateway": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "for_system_vms": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "start_ip": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "vlan_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ },
+ },
+ },
+ }
+}
+
+func dsFlattenPodCapacity(capacity []cloudstack.PodCapacity) []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 dsFlattenPodIpRanges(ip_ranges []cloudstack.PodIpranges) []map[string]interface{} {
+ ranges := make([]map[string]interface{}, len(ip_ranges))
+ for i, ip_range := range ip_ranges {
+ ranges[i] = map[string]interface{}{
+ "end_ip": ip_range.Endip,
+ "for_system_vms": ip_range.Forsystemvms,
+ "start_ip": ip_range.Startip,
+ "vlan_id": ip_range.Vlanid,
+ }
+ }
+ return ranges
+}
+
+func datasourceCloudStackPodRead(d *schema.ResourceData, meta interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+ p := cs.Pod.NewListPodsParams()
+
+ csPods, err := cs.Pod.ListPods(p)
+ if err != nil {
+ return fmt.Errorf("failed to list pods: %s", err)
+ }
+
+ filters := d.Get("filter")
+
+ for _, pod := range csPods.Pods {
+ match, err := applyPodFilters(pod, filters.(*schema.Set))
+ if err != nil {
+ return err
+ }
+ if match {
+ return podDescriptionAttributes(d, pod)
+ }
+ }
+
+ return fmt.Errorf("no pods found")
+}
+
+func podDescriptionAttributes(d *schema.ResourceData, pod *cloudstack.Pod) error {
+ d.SetId(pod.Id)
+ var end_ip string
+ if len(pod.Endip) > 0 {
+ end_ip = pod.Endip[0]
+ }
+
+ fields := map[string]interface{}{
+ "pod_id": pod.Id,
+ "name": pod.Name,
+ "allocation_state": pod.Allocationstate,
+ "gateway": pod.Gateway,
+ "netmask": pod.Netmask,
+ "start_ip": pod.Startip[0],
+ "vlan_id": pod.Vlanid[0],
+ "zone_id": pod.Zoneid,
+ "zone_name": pod.Zonename,
+ "end_ip": end_ip,
+ "ip_ranges": dsFlattenPodIpRanges(pod.Ipranges),
+ "capacity": dsFlattenPodCapacity(pod.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 applyPodFilters(pod *cloudstack.Pod, filters *schema.Set) (bool, error) {
+ val := reflect.ValueOf(pod).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), "_", "")
+ podField := val.FieldByNameFunc(func(fieldName string) bool {
+ if strings.EqualFold(fieldName, updatedName) {
+ updatedName = fieldName
+ return true
+ }
+ return false
+ }).String()
+
+ if r.MatchString(podField) {
+ return true, nil
+ }
+ }
+
+ return false, nil
+}
diff --git a/cloudstack/data_source_cloudstack_pod_test.go b/cloudstack/data_source_cloudstack_pod_test.go
new file mode 100644
index 0000000..815e57e
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_pod_test.go
@@ -0,0 +1,50 @@
+//
+// 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/helper/resource"
+)
+
+func TestAccPodDataSource_basic(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ Steps: []resource.TestStep{
+ {
+ Config: testPodDataSourceConfig_basic,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr("data.cloudstack_pod.test", "name", "POD0"),
+ ),
+ },
+ },
+ })
+}
+
+const testPodDataSourceConfig_basic = `
+ data "cloudstack_pod" "test" {
+ filter {
+ name = "name"
+ value = "POD0"
+ }
+ }
+ `
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index 3def7c9..c35a263 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -89,6 +89,7 @@ func Provider() terraform.ResourceProvider {
"cloudstack_ipaddress": dataSourceCloudstackIPAddress(),
"cloudstack_user": dataSourceCloudstackUser(),
"cloudstack_vpn_connection": dataSourceCloudstackVPNConnection(),
+ "cloudstack_pod": dataSourceCloudstackPod(),
},
ResourcesMap: map[string]*schema.Resource{
@@ -98,6 +99,7 @@ func Provider() terraform.ResourceProvider {
"cloudstack_disk": resourceCloudStackDisk(),
"cloudstack_egress_firewall": resourceCloudStackEgressFirewall(),
"cloudstack_firewall": resourceCloudStackFirewall(),
+ "cloudstack_host": resourceCloudStackHost(),
"cloudstack_instance": resourceCloudStackInstance(),
"cloudstack_ipaddress": resourceCloudStackIPAddress(),
"cloudstack_kubernetes_cluster": resourceCloudStackKubernetesCluster(),
diff --git a/cloudstack/resource_cloudstack_host.go b/cloudstack/resource_cloudstack_host.go
new file mode 100644
index 0000000..46fc8e2
--- /dev/null
+++ b/cloudstack/resource_cloudstack_host.go
@@ -0,0 +1,316 @@
+//
+// 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 (
+ "errors"
+ "fmt"
+ "log"
+ "sort"
+ "strings"
+ "time"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform/helper/schema"
+)
+
+func resourceCloudStackHost() *schema.Resource {
+ return &schema.Resource{
+ Read: resourceCloudStackHostRead,
+ Update: resourceCloudStackHostUpdate,
+ Create: resourceCloudStackHostCreate,
+ Delete: resourceCloudStackHostDelete,
+ Schema: map[string]*schema.Schema{
+ "hypervisor": {
+ Type: schema.TypeString,
+ Required: true,
+ ValidateFunc: func(v interface{}, k string) (ws []string, errors []error) {
+ validHypervisors := []string{"xenserver", "kvm", "vmware", "baremetal", "simulator"}
+
+ sort.Strings(validHypervisors)
+
+ if sort.SearchStrings(validHypervisors, v.(string)) >= len(validHypervisors) {
+ errors = append(errors, fmt.Errorf("%q must be one of %v", k, validHypervisors))
+ }
+ return
+ },
+ ForceNew: true,
+ },
+ "pod_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "url": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "zone_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ },
+ "cluster_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ ConflictsWith: []string{
+ "cluster_name",
+ },
+ },
+ "cluster_name": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ ConflictsWith: []string{
+ "cluster_id",
+ },
+ },
+ "host_tags": {
+ Type: schema.TypeList,
+ Optional: true,
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+ "username": {
+ Type: schema.TypeString,
+ Optional: true,
+ },
+ "password": {
+ Type: schema.TypeString,
+ Optional: true,
+ Sensitive: true,
+ },
+ "prevent_destroy": {
+ Type: schema.TypeBool,
+ Description: "Prevent the host from being destroyed. This is useful when you want to avoid destroy the host in any change.",
+ Optional: true,
+ Default: false,
+ },
+ "force_destroy": {
+ Type: schema.TypeBool,
+ Description: "Force the host to be destroyed.",
+ Optional: true,
+ Default: false,
+ },
+ "allocation_state": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "Enabled",
+ },
+ "state": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "resource_state": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ // Creating a host can fail if the instance is still being created and Cloudsack
+ // user is created during cloud-init. This timeout is used to wait for the host
+ // to be created and Cloudstack user to be available.
+ "create_timeout": {
+ Type: schema.TypeInt,
+ Description: "Timeout in seconds to wait for the host to be created.",
+ Optional: true,
+ Default: 300,
+ },
+ // Destroying a host will put it in Maintenance mode first. If the VMs are still
+ // being migrated, the host will be in state PrepareForMaintenance. This timeout
+ // is used to wait for the host to be in Maintenance state.
+ "destroy_timeout": {
+ Type: schema.TypeInt,
+ Description: "Timeout in seconds to wait for the host to be destroyed.",
+ Optional: true,
+ Default: 300,
+ },
+ },
+ }
+}
+
+func resourceCloudStackHostCreate(d *schema.ResourceData, meta interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+ hypervisor := d.Get("hypervisor").(string)
+ pod_id := d.Get("pod_id").(string)
+ url := d.Get("url").(string)
+ zone_id := d.Get("zone_id").(string)
+
+ p := cs.Host.NewAddHostParams(hypervisor, pod_id, url, zone_id)
+
+ if cluster_id, ok := d.GetOk("cluster_id"); ok {
+ p.SetClusterid(cluster_id.(string))
+ }
+
+ if cluster_name, ok := d.GetOk("cluster_name"); ok {
+ p.SetClustername(cluster_name.(string))
+ }
+
+ if host_tags, ok := d.GetOk("host_tags"); ok {
+ p.SetHosttags(host_tags.([]string))
+ }
+
+ if username, ok := d.GetOk("username"); ok {
+ p.SetUsername(username.(string))
+ }
+
+ if password, ok := d.GetOk("password"); ok {
+ p.SetPassword(password.(string))
+ }
+
+ timeout := time.After(time.Duration(d.Get("create_timeout").(int)) * time.Second)
+ tick := time.NewTicker(5 * time.Second)
+ var err error
+ var host *cloudstack.AddHostResponse
+
+ for {
+ select {
+ case <-timeout:
+ return fmt.Errorf("timeout waiting for Host to be created, with error: %s", err)
+ case <-tick.C:
+ log.Printf("[DEBUG] Trying to create host %s", d.Get("url").(string))
+ host, err = cs.Host.AddHost(p)
+ if err != nil {
+ log.Printf("[ERROR] Error creating host %s: %s. Will try again...", d.Get("url").(string), err)
+ continue
+ }
+
+ if host.Id != "" {
+ log.Printf("[DEBUG] Host %s successfully created", url)
+ d.SetId(host.Id)
+ return resourceCloudStackHostRead(d, meta)
+ }
+ }
+ }
+}
+
+func resourceCloudStackHostRead(d *schema.ResourceData, meta interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+ log.Printf("[DEBUG] Retrieving Host %s", d.Get("url").(string))
+
+ h, count, err := cs.Host.GetHostByID(d.Id())
+
+ if err != nil {
+ if count == 0 {
+ log.Printf("[WARN] Host %s does no longer exist", d.Get("url").(string))
+ d.SetId("")
+ return nil
+ }
+ return err
+ }
+
+ d.SetId(h.Id)
+
+ fields := map[string]interface{}{
+ "hypervisor": h.Hypervisor,
+ "pod_id": h.Podid,
+ "zone_id": h.Zoneid,
+ "state": h.State,
+ "resource_state": h.Resourcestate,
+ "name": h.Name,
+ }
+
+ for k, v := range fields {
+ if err := d.Set(k, v); err != nil {
+ return err
+ }
+ }
+
+ if cluster_id := d.Get("cluster_id"); cluster_id != "" {
+ d.Set("cluster_id", h.Clusterid)
+ } else {
+ d.Set("cluster_name", h.Clustername)
+ }
+
+ if h.Hosttags != "" {
+ d.Set("host_tags", strings.Split(h.Hosttags, ","))
+ }
+
+ return nil
+}
+
+func resourceCloudStackHostUpdate(d *schema.ResourceData, meta interface{}) error {
+ log.Printf("[DEBUG] Updating Host: %s", d.Id())
+
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.Host.NewUpdateHostParams(d.Id())
+
+ if d.HasChange("allocation_state") {
+ log.Printf("[DEBUG] Updating Host allocation state: %s", d.Id())
+ p.SetAllocationstate(d.Get("allocation_state").(string))
+ }
+
+ if d.HasChange("host_tags") {
+ log.Printf("[DEBUG] Updating Host tags: %s", d.Id())
+ p.SetHosttags(d.Get("host_tags").([]string))
+ }
+
+ return resourceCloudStackHostRead(d, meta)
+}
+
+func resourceCloudStackHostDelete(d *schema.ResourceData, meta interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ if d.Get("prevent_destroy").(bool) {
+ log.Printf("[INFO] Skipping Host deletion: %s", d.Id())
+ return fmt.Errorf("host %s is marked to be protected from deletion", d.Id())
+ }
+
+ log.Printf("[INFO] Removing Host: %s", d.Id())
+ mm := cs.Host.NewPrepareHostForMaintenanceParams(d.Id())
+ _, err := cs.Host.PrepareHostForMaintenance(mm)
+
+ if err != nil {
+ return fmt.Errorf("error preparing Host for maintenance: %s", err)
+ }
+
+ timeout := time.After(time.Duration(d.Get("destroy_timeout").(int)) * time.Second)
+ tick := time.NewTicker(3 * time.Second)
+
+ for {
+ select {
+ case <-timeout:
+ return errors.New("timeout waiting for Host to enter Maintenance state")
+ case <-tick.C:
+ log.Printf("[DEBUG] Checking Host state: %s", d.Id())
+ err = resourceCloudStackHostRead(d, meta)
+ if err != nil {
+ return fmt.Errorf("error reading Host: %s", err)
+ }
+
+ if d.Get("resource_state").(string) == "Maintenance" || d.Get("resource_state").(string) == "Disconnected" {
+ log.Printf("[INFO] Deleting Host: %s", d.Id())
+ h := cs.Host.NewDeleteHostParams(d.Id())
+ _, err = cs.Host.DeleteHost(h)
+
+ if err != nil {
+ return fmt.Errorf("error deleting Host: %s", err)
+ }
+ return nil
+ }
+ }
+ }
+}
diff --git a/cloudstack/resource_cloudstack_host_test.go b/cloudstack/resource_cloudstack_host_test.go
new file mode 100644
index 0000000..8da6f58
--- /dev/null
+++ b/cloudstack/resource_cloudstack_host_test.go
@@ -0,0 +1,143 @@
+//
+// 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/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform/helper/resource"
+ "github.com/hashicorp/terraform/terraform"
+)
+
+func TestAccCloudStackHost_basic(t *testing.T) {
+ var h cloudstack.Host
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccCloudStackHost_basic,
+ Check: resource.ComposeTestCheckFunc(
+ testAccCheckCloudStackHostExists("cloudstack_host.test", &h),
+ resource.TestCheckResourceAttr("cloudstack_host.test", "hypervisor", "Simulator"),
+ resource.TestCheckResourceAttr("cloudstack_host.test", "cluster_name", "C1"),
+ resource.TestCheckResourceAttrSet("cloudstack_host.test", "state"),
+ resource.TestCheckResourceAttrSet("cloudstack_host.test", "name"),
+ ),
+ },
+ },
+ })
+}
+
+const testAccCloudStackHost_basic = `
+data "cloudstack_zone" "zone" {
+ filter {
+ name = "name"
+ value = "Sandbox-simulator"
+ }
+}
+
+data "cloudstack_pod" "pod" {
+ filter {
+ name = "name"
+ value = "POD0"
+ }
+}
+
+resource "cloudstack_host" "test" {
+ hypervisor = "Simulator"
+ pod_id = data.cloudstack_pod.pod.id
+ url = "http://sim/c1/h"
+ zone_id = data.cloudstack_zone.zone.id
+ cluster_name = "C1"
+ username = "root"
+ password = "password"
+}
+`
+
+func testAccCheckCloudStackHostExists(n string, h *cloudstack.Host) 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 host ID is set")
+ }
+
+ cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
+ resp, _, err := cs.Host.GetHostByID(rs.Primary.ID)
+ if err != nil {
+ return err
+ }
+
+ if resp.Id != rs.Primary.ID {
+ return fmt.Errorf("Host not found")
+ }
+
+ *h = *resp
+
+ return nil
+ }
+}
+
+func TestAccCloudStackHost_fail(t *testing.T) {
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { testAccPreCheck(t) },
+ Providers: testAccProviders,
+ Steps: []resource.TestStep{
+ {
+ Config: testAccCloudStackHost_fail,
+ ExpectError: regexp.MustCompile("timeout waiting for Host to be created, with error: .*Username and Password need to be provided.*"),
+ },
+ },
+ })
+}
+
+const testAccCloudStackHost_fail = `
+data "cloudstack_zone" "zone_fail" {
+ filter {
+ name = "name"
+ value = "Sandbox-simulator"
+ }
+}
+
+data "cloudstack_pod" "pod_fail" {
+ filter {
+ name = "name"
+ value = "POD0"
+ }
+}
+
+resource "cloudstack_host" "test_fail" {
+ hypervisor = "Simulator"
+ pod_id = data.cloudstack_pod.pod_fail.id
+ url = "http://sim/c1/h"
+ zone_id = data.cloudstack_zone.zone_fail.id
+ cluster_name = "C1"
+ username = "root"
+ password = ""
+ create_timeout = 10
+}
+`