From 3aab2c88c4db8e1595ea078b907febbd2f96be39 Mon Sep 17 00:00:00 2001 From: Ryan Johnson Date: Mon, 26 Aug 2024 23:15:28 -0400 Subject: [PATCH] feat: add `d/host` Adds a data source for host: `data/vcf_host`. Signed-off-by: Ryan Johnson --- docs/data-sources/host.md | 115 ++++++ examples/data-sources/host/variables.tf | 22 ++ examples/data-sources/host/vcf_host.tf | 21 ++ internal/provider/data_host.go | 473 ++++++++++++++++++++++++ internal/provider/data_host_test.go | 55 +++ internal/provider/provider.go | 8 +- 6 files changed, 690 insertions(+), 4 deletions(-) create mode 100644 docs/data-sources/host.md create mode 100644 examples/data-sources/host/variables.tf create mode 100644 examples/data-sources/host/vcf_host.tf create mode 100644 internal/provider/data_host.go create mode 100644 internal/provider/data_host_test.go diff --git a/docs/data-sources/host.md b/docs/data-sources/host.md new file mode 100644 index 0000000..82e1675 --- /dev/null +++ b/docs/data-sources/host.md @@ -0,0 +1,115 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "vcf_host Data Source - terraform-provider-vcf" +subcategory: "" +description: |- + +--- + +# Data Source: vcf_host + +The `vcf_host` data source provides information about an ESXi host in a VMware Cloud Foundation environment. + +## Schema + +### Required + +- `fqdn` (String) - The fully qualified domain name of the ESXi host. + +### Read-Only + +- `id` (String) - The ID of the ESXi host. +- `status` (String) - The status of the ESXi host. +- `version` (String) - The version of the ESXi running on the host. +- `storage_type` (String) - The storage type of the ESXi host. + +### Nested Schema for `network_pool` + +Read-Only: + +- `name` (String) - The name of the network pool. +- `id` (String) - The ID of the network pool. + +### Nested Schema for `cluster` + +Read-Only: + +- `id` (String) - The ID of the cluster. + +### Nested Schema for `cpu` + +Read-Only: + +- `cores` (Number) - Number of CPU cores. +- `frequency_mhz` (Number) - Total CPU frequency in MHz. +- `used_frequency_mhz` (Number) - Used CPU frequency in MHz. +- `cpu_cores` (List of Object) - Information about each of the [CPU cores](#nestedobjatt--cpu--cpu_cores). + + + +#### Nested Schema for `cpu.cpu_cores` + +Read-Only: + +- `frequency` (Number) - The frequency of the CPU core in MHz. +- `manufacturer` (String) - The manufacturer of the CPU. +- `model` (String) - The model of the CPU. + + + +### Nested Schema for `domain` + +Read-Only: + +- `id` (String) - The ID of the workload domain. +- `name` (String) - The name of the workload domain. + +### Nested Schema for `hardware` + +Read-Only: + +- `model` (String) - The hardware model of the ESXi host. +- `vendor` (String) - The hardware vendor of the ESXi host. +- `hybrid` (Boolean) - Indicates if the ESXi host is hybrid. + +### Nested Schema for `ip_addresses` + +Read-Only: + +- `ip_address` (String) - The IP address. +- `type` (String) - The type of the IP address. + +### Nested Schema for `memory` + +Read-Only: + +- `total_capacity_mb` (Number) - The total memory capacity in MB. +- `used_capacity_mb` (Number) - The used memory capacity in MB. + +### Nested Schema for `storage` + +Read-Only: + +- `total_capacity_mb` (Number) - The total storage capacity in MB. +- `used_capacity_mb` (Number) - The used storage capacity in MB. +- `disks` (List of Object) - The disks information of the ESXi host (see [below for nested schema](#nestedobjatt--storage--disks)). + + + +### Nested Schema for `storage.disks` + +Read-Only: + +- `capacity_mb` (Number) - The capacity of the disk in MB. +- `disk_type` (String) - The type of the disk. +- `manufacturer` (String) - The manufacturer of the disk. +- `model` (String) - The model of the disk. + +### Nested Schema for `physical_nics` + +Read-Only: + +- `device_name` (String) - The device name of the NIC. +- `mac_address` (String) - The MAC address of the NIC. +- `speed` (Number) - The speed of the NIC. +- `unit` (String) - The unit of the NIC speed. diff --git a/examples/data-sources/host/variables.tf b/examples/data-sources/host/variables.tf new file mode 100644 index 0000000..9e69411 --- /dev/null +++ b/examples/data-sources/host/variables.tf @@ -0,0 +1,22 @@ +variable "sddc_manager_host" { + type = string + description = "The fully qualified domain name of the SDDC Manager instance." +} + +variable "sddc_manager_username" { + type = string + description = "The username to authenticate to the SDDC Manager instance." + sensitive = true +} + +variable "sddc_manager_password" { + type = string + description = "The password to authenticate to the SDDC Manager instance." + sensitive = true +} + +variable "host_fqdn" { + type = string + description = "The fully qualified domain name of the ESXi host." + default = "sfo-w01-esx01.sfo.rainpole.io" +} diff --git a/examples/data-sources/host/vcf_host.tf b/examples/data-sources/host/vcf_host.tf new file mode 100644 index 0000000..f635ace --- /dev/null +++ b/examples/data-sources/host/vcf_host.tf @@ -0,0 +1,21 @@ +terraform { + required_providers { + vcf = { + source = "vmware/vcf" + } + } +} + +provider "vcf" { + sddc_manager_username = var.sddc_manager_username + sddc_manager_password = var.sddc_manager_password + sddc_manager_host = var.sddc_manager_host +} + +data "vcf_host" "example" { + fqdn = var.host_fqdn +} + +output "host_id" { + value = data.vcf_host.example.id +} diff --git a/internal/provider/data_host.go b/internal/provider/data_host.go new file mode 100644 index 0000000..e497435 --- /dev/null +++ b/internal/provider/data_host.go @@ -0,0 +1,473 @@ +// © Broadcom. All Rights Reserved. +// The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "context" + "errors" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/vmware/vcf-sdk-go/client" + "github.com/vmware/vcf-sdk-go/client/hosts" + "github.com/vmware/vcf-sdk-go/models" + + "github.com/vmware/terraform-provider-vcf/internal/api_client" + "github.com/vmware/terraform-provider-vcf/internal/constants" +) + +func DataSourceHost() *schema.Resource { + return &schema.Resource{ + ReadContext: dataSourceHostRead, + Schema: map[string]*schema.Schema{ + "fqdn": { + Type: schema.TypeString, + Required: true, + Description: "The fully qualified domain name of ESXi host.", + }, + "network_pool": { + Type: schema.TypeList, + Computed: true, + Description: "The network pool associated with the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the network pool.", + }, + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the network pool.", + }, + }, + }, + }, + "storage_type": { + Type: schema.TypeString, + Computed: true, + Description: "The storage type of the ESXi host.", + }, + "status": { + Type: schema.TypeString, + Computed: true, + Description: "The status of the ESXi host.", + }, + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the ESXi host.", + }, + "cluster": { + Type: schema.TypeList, + Computed: true, + Description: "The cluster information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the cluster.", + }, + }, + }, + }, + "cpu_cores": { + Type: schema.TypeList, + Computed: true, + Description: "The CPU cores information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "frequency": { + Type: schema.TypeFloat, + Computed: true, + Description: "The frequency of the CPU core (in MHz).", + }, + "manufacturer": { + Type: schema.TypeString, + Computed: true, + Description: "The manufacturer of the CPU.", + }, + "model": { + Type: schema.TypeString, + Computed: true, + Description: "The model of the CPU.", + }, + }, + }, + }, "cpu": { + Type: schema.TypeList, + Computed: true, + Description: "The CPU information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cores": { + Type: schema.TypeInt, + Computed: true, + Description: "Number of CPU cores.", + }, + "cpu_cores": { + Type: schema.TypeList, + Computed: true, + Description: "Information about each of the CPU cores.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "frequency_mhz": { + Type: schema.TypeFloat, + Computed: true, + Description: "The frequency of the CPU core in MHz.", + }, + "manufacturer": { + Type: schema.TypeString, + Computed: true, + Description: "The manufacturer of the CPU.", + }, + "model": { + Type: schema.TypeString, + Computed: true, + Description: "The model of the CPU.", + }, + }, + }, + }, + "frequency_mhz": { + Type: schema.TypeFloat, + Computed: true, + Description: "Total CPU frequency in MHz.", + }, + "used_frequency_mhz": { + Type: schema.TypeFloat, + Computed: true, + Description: "Used CPU frequency in MHz.", + }, + }, + }, + }, + "domain": { + Type: schema.TypeList, + Computed: true, + Description: "The workload domain information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Computed: true, + Description: "The ID of the workload domain.", + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: "The name of the workload domain.", + }, + }, + }, + }, + "version": { + Type: schema.TypeString, + Computed: true, + Description: "The version of the ESXi running on the host.", + }, + "hardware": { + Type: schema.TypeList, + Computed: true, + Description: "The hardware information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "model": { + Type: schema.TypeString, + Computed: true, + Description: "The hardware model of the ESXi host.", + }, + "vendor": { + Type: schema.TypeString, + Computed: true, + Description: "The hardware vendor of the ESXi host.", + }, + "hybrid": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates if the ESXi host is hybrid.", + }, + }, + }, + }, + "ip_addresses": { + Type: schema.TypeList, + Computed: true, + Description: "The IP addresses information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "ip_address": { + Type: schema.TypeString, + Computed: true, + Description: "The IP address.", + }, + "type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of the IP address.", + }, + }, + }, + }, + "memory": { + Type: schema.TypeList, + Computed: true, + Description: "The memory information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "total_capacity_mb": { + Type: schema.TypeFloat, + Computed: true, + Description: "The total memory capacity in MB.", + }, + "used_capacity_mb": { + Type: schema.TypeFloat, + Computed: true, + Description: "The used memory capacity in MB.", + }, + }, + }, + }, + "storage": { + Type: schema.TypeList, + Computed: true, + Description: "The storage information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "total_capacity_mb": { + Type: schema.TypeFloat, + Computed: true, + Description: "The total storage capacity in MB.", + }, + "used_capacity_mb": { + Type: schema.TypeFloat, + Computed: true, + Description: "The used storage capacity in MB.", + }, + "disks": { + Type: schema.TypeList, + Computed: true, + Description: "The disks information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "capacity_mb": { + Type: schema.TypeFloat, + Computed: true, + Description: "The capacity of the disk in MB.", + }, + "disk_type": { + Type: schema.TypeString, + Computed: true, + Description: "The type of the disk.", + }, + "manufacturer": { + Type: schema.TypeString, + Computed: true, + Description: "The manufacturer of the disk.", + }, + "model": { + Type: schema.TypeString, + Computed: true, + Description: "The model of the disk.", + }, + }, + }, + }, + }, + }, + }, + "physical_nics": { + Type: schema.TypeList, + Computed: true, + Description: "The physical NICs information of the ESXi host.", + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "device_name": { + Type: schema.TypeString, + Computed: true, + Description: "The device name of the NIC.", + }, + "mac_address": { + Type: schema.TypeString, + Computed: true, + Description: "The MAC address of the NIC.", + }, + "speed": { + Type: schema.TypeFloat, + Computed: true, + Description: "The speed of the NIC.", + }, + "unit": { + Type: schema.TypeString, + Computed: true, + Description: "The unit of the NIC speed.", + }, + }, + }, + }, + }, + } +} + +func dataSourceHostRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(*api_client.SddcManagerClient).ApiClient + + fqdn := d.Get("fqdn").(string) + host, err := getHostByFqdn(ctx, apiClient, fqdn) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(host.ID) + + // Cluster information. + cluster := []map[string]interface{}{ + { + "id": host.Cluster.ID, + }, + } + _ = d.Set("cluster", cluster) + + // Compatible storage type information. + _ = d.Set("compatible_storage_type", host.CompatibleStorageType) + + // CPU information. + _ = d.Set("cpu_cores", host.CPU.Cores) + + cpuCores := []map[string]interface{}{} + for _, core := range host.CPU.CPUCores { + cpuCore := map[string]interface{}{ + "frequency": core.FrequencyMHz, + "manufacturer": core.Manufacturer, + "model": core.Model, + } + cpuCores = append(cpuCores, cpuCore) + } + + cpu := map[string]interface{}{ + "cores": host.CPU.Cores, + "cpu_cores": cpuCores, + "frequency_mhz": host.CPU.FrequencyMHz, + "used_frequency_mhz": host.CPU.UsedFrequencyMHz, + } + + _ = d.Set("cpu", cpu) + + // Domain information. + domain := []map[string]interface{}{ + { + "id": host.Domain.ID, + "name": host.Domain.Name, + }, + } + _ = d.Set("domain", domain) + + // ESXi version information. + _ = d.Set("version", host.EsxiVersion) + + // Fully qualified domain name information. + _ = d.Set("fqdn", host.Fqdn) + + // Hardware information. + hardware := []map[string]interface{}{ + { + "model": host.HardwareModel, + "vendor": host.HardwareVendor, + "hybrid": host.Hybrid, + }, + } + + _ = d.Set("hardware", hardware) + + // IP addresses information. + ipAddresses := []map[string]interface{}{} + for _, ip := range host.IPAddresses { + ipAddress := map[string]interface{}{ + "ip_address": ip.IPAddress, + "type": ip.Type, + } + ipAddresses = append(ipAddresses, ipAddress) + } + _ = d.Set("ip_addresses", ipAddresses) + + // Memory information. + memory := []map[string]interface{}{ + { + "total_capacity_mb": host.Memory.TotalCapacityMB, + "used_capacity_mb": host.Memory.UsedCapacityMB, + }, + } + _ = d.Set("memory", memory) + + // Network pool information. + networkPool := []map[string]interface{}{ + { + "id": host.Networkpool.ID, + "name": host.Networkpool.Name, + }, + } + _ = d.Set("network_pool", networkPool) + + // Physical NICs information. + physicalNics := []map[string]interface{}{} + for _, nic := range host.PhysicalNics { + physicalNic := map[string]interface{}{ + "device_name": nic.DeviceName, + "mac_address": nic.MacAddress, + "speed": nic.Speed, + "unit": nic.Unit, + } + physicalNics = append(physicalNics, physicalNic) + } + _ = d.Set("physical_nics", physicalNics) + + // Status information. + _ = d.Set("status", host.Status) + + // Storage information. + disks := []map[string]interface{}{} + for _, disk := range host.Storage.Disks { + diskInfo := map[string]interface{}{ + "capacity_mb": disk.CapacityMB, + "disk_type": disk.DiskType, + "manufacturer": disk.Manufacturer, + "model": disk.Model, + } + disks = append(disks, diskInfo) + } + + storage := []map[string]interface{}{ + { + "total_capacity_mb": host.Storage.TotalCapacityMB, + "used_capacity_mb": host.Storage.UsedCapacityMB, + "disks": disks, + }, + } + _ = d.Set("storage", storage) + + return nil +} + +func getHostByFqdn(ctx context.Context, apiClient *client.VcfClient, fqdn string) (*models.Host, error) { + params := hosts.NewGetHostParamsWithContext(ctx). + WithTimeout(constants.DefaultVcfApiCallTimeout) + + hostResponse, err := apiClient.Hosts.GetHost(params) + if err != nil { + return nil, err + } + + if hostResponse.Payload == nil { + return nil, errors.New("host not found") + } + + if hostResponse.Payload.Fqdn == fqdn { + return hostResponse.Payload, nil + } + + return nil, errors.New("host not found") +} diff --git a/internal/provider/data_host_test.go b/internal/provider/data_host_test.go new file mode 100644 index 0000000..52161d7 --- /dev/null +++ b/internal/provider/data_host_test.go @@ -0,0 +1,55 @@ +// © Broadcom. All Rights Reserved. +// The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. +// SPDX-License-Identifier: MPL-2.0 + +package provider + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/vmware/terraform-provider-vcf/internal/constants" +) + +func TestAccDataSourceVcfHost(t *testing.T) { + hosts := []string{ + constants.VcfTestHost1Fqdn, + constants.VcfTestHost2Fqdn, + constants.VcfTestHost3Fqdn, + constants.VcfTestHost4Fqdn, + } + + var steps []resource.TestStep + for _, fqdn := range hosts { + steps = append(steps, resource.TestStep{ + Config: testAccDataSourceVcfHostConfig(fqdn), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("data.vcf_host.test_host", "id"), + resource.TestCheckResourceAttr("data.vcf_host.test_host", "fqdn", fqdn), + ), + }) + } + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: muxedFactories(), + Steps: steps, + }) +} + +func testAccDataSourceVcfHostConfig(hostFqdn string) string { + return fmt.Sprintf(` + resource "vcf_host" "test_host" { + fqdn = %q + network_pool_id = "test_network_pool_id" + network_pool_name = "test_network_pool_name" + status = "active" + } + + data "vcf_host" "test_host" { + fqdn = vcf_host.test_host.fqdn + } + `, hostFqdn) +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 9fd2f21..e8f5508 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -75,10 +75,10 @@ func Provider() *schema.Provider { }, DataSourcesMap: map[string]*schema.Resource{ - "vcf_cluster": DataSourceCluster(), - "vcf_domain": DataSourceDomain(), - "vcf_credentials": DataSourceCredentials(), - "vcf_network_pool": DataSourceNetworkPool(), + "vcf_cluster": DataSourceCluster(), + "vcf_domain": DataSourceDomain(), + "vcf_host": DataSourceHost(), + "vcf_credentials": DataSourceCredentials(), }, ResourcesMap: map[string]*schema.Resource{