diff --git a/docs/data-sources/host.md b/docs/data-sources/host.md new file mode 100644 index 0000000..8d05693 --- /dev/null +++ b/docs/data-sources/host.md @@ -0,0 +1,113 @@ +--- +# 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 unique identifier of the ESXi host. +- `status` (String) - The status of the ESXi host. +- `version` (String) - The version of the ESXi running on the host. +- `compatible_storage_type` (String) - The compatible storage type of the ESXi host. + +### Nested Schema for `hardware` + +Read-Only: + +- `vendor` (String) - The hardware vendor of the ESXi host. +- `model` (String) - The hardware model of the ESXi host. +- `hybrid` (Boolean) - The hybrid status of the hardware. + +### 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 `domain` + +Read-Only: + +- `id` (String) - The unique identifier of the domain. +- `name` (String) - The name of the domain. + +### Nested Schema for `cluster` + +Read-Only: + +- `id` (String) - The unique identifier of the cluster. + +### Nested Schema for `ip_addresses` + +Read-Only: + +- `ip_address` (String) - The IP address of the ESXi host. +- `type` (String) - The service type of the IP address. + +### 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 `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 physical NIC. +- `mac_address` (String) - The MAC address of the physical NIC. +- `speed` (Number) - The speed of the physical NIC. +- `unit` (String) - The unit of the physical 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..df69904 --- /dev/null +++ b/internal/provider/data_host.go @@ -0,0 +1,434 @@ +// © 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" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + "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" +) + +type HostModel struct { + Fqdn types.String `tfsdk:"fqdn"` + Id types.String `tfsdk:"id"` + Status types.String `tfsdk:"status"` + CompatibleStorageType types.String `tfsdk:"compatible_storage_type"` + Domain []DomainModel `tfsdk:"domain"` + Cluster []ClusterModel `tfsdk:"cluster"` + NetworkPool []NetworkPoolModel `tfsdk:"network_pool"` + Version types.String `tfsdk:"version"` + Hardware []HardwareModel `tfsdk:"hardware"` + IpAddresses []IpAddressModel `tfsdk:"ip_addresses"` + Cpu CpuModel `tfsdk:"cpu"` + Memory MemoryModel `tfsdk:"memory"` + Storage []StorageModel `tfsdk:"storage"` + PhysicalNics []PhysicalNicModel `tfsdk:"physical_nics"` +} + +type DomainModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` +} + +type ClusterModel struct { + Id types.String `tfsdk:"id"` +} + +type NetworkPoolModel struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` +} + +type HardwareModel struct { + Model types.String `tfsdk:"model"` + Vendor types.String `tfsdk:"vendor"` + Hybrid types.Bool `tfsdk:"hybrid"` +} + +type IpAddressModel struct { + IpAddress types.String `tfsdk:"ip_address"` + Type types.String `tfsdk:"type"` +} + +type CpuModel struct { + Cores types.Int64 `tfsdk:"cores"` + CpuCoreDetails []CpuCoreModel `tfsdk:"cpu_core_details"` + FrequencyMHz types.Float64 `tfsdk:"frequency_mhz"` + UsedFrequencyMHz types.Float64 `tfsdk:"used_frequency_mhz"` +} + +type CpuCoreModel struct { + FrequencyMHz types.Float64 `tfsdk:"frequency_mhz"` + Manufacturer types.String `tfsdk:"manufacturer"` + Model types.String `tfsdk:"model"` +} + +type MemoryModel struct { + TotalCapacityMB types.Float64 `tfsdk:"total_capacity_mb"` + UsedCapacityMB types.Float64 `tfsdk:"used_capacity_mb"` +} + +type StorageModel struct { + TotalCapacityMB types.Float64 `tfsdk:"total_capacity_mb"` + UsedCapacityMB types.Float64 `tfsdk:"used_capacity_mb"` + Disks []DiskModel `tfsdk:"disks"` +} + +type DiskModel struct { + CapacityMB types.Float64 `tfsdk:"capacity_mb"` + DiskType types.String `tfsdk:"disk_type"` + Manufacturer types.String `tfsdk:"manufacturer"` + Model types.String `tfsdk:"model"` +} + +type PhysicalNicModel struct { + DeviceName types.String `tfsdk:"device_name"` + MacAddress types.String `tfsdk:"mac_address"` + Speed types.Int64 `tfsdk:"speed"` + Unit types.String `tfsdk:"unit"` +} + +type DataSourceHost struct { + client *client.VcfClient +} + +func (d *DataSourceHost) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "vcf_host" +} + +func (d *DataSourceHost) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + d.client = req.ProviderData.(*api_client.SddcManagerClient).ApiClient +} + +func (d *DataSourceHost) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "fqdn": schema.StringAttribute{ + Required: true, + Description: "The fully qualified domain name of the ESXi host.", + }, + "id": schema.StringAttribute{ + Computed: true, + Description: "The unique identifier of the ESXi host.", + }, + "status": schema.StringAttribute{ + Computed: true, + Description: "The status of the ESXi host.", + }, + "domain": schema.ListNestedAttribute{ + Computed: true, + Description: "The domain details of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + "cluster": schema.ListNestedAttribute{ + Computed: true, + Description: "The clusters of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + "network_pool": schema.ListNestedAttribute{ + Computed: true, + Description: "The network pool of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + "version": schema.StringAttribute{ + Computed: true, + Description: "The version of the ESXi host.", + }, + "compatible_storage_type": schema.StringAttribute{ + Computed: true, + Description: "The compatible storage type of the ESXi host.", + }, + "hardware": schema.ListNestedAttribute{ + Computed: true, + Description: "The hardware details of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "model": schema.StringAttribute{ + Computed: true, + }, + "vendor": schema.StringAttribute{ + Computed: true, + }, + "hybrid": schema.BoolAttribute{ + Computed: true, + }, + }, + }, + }, + "ip_addresses": schema.ListNestedAttribute{ + Computed: true, + Description: "The IP addresses of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "ip_address": schema.StringAttribute{ + Computed: true, + }, + "type": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + "cpu": schema.SingleNestedAttribute{ + Computed: true, + Description: "The CPU details of the ESXi host.", + Attributes: map[string]schema.Attribute{ + "cores": schema.Int64Attribute{ + Computed: true, + }, + "cpu_cores": schema.ListNestedAttribute{ + Computed: true, + Description: "The CPU core details of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "frequency_mhz": schema.Float64Attribute{ + Computed: true, + }, + "manufacturer": schema.StringAttribute{ + Computed: true, + }, + "model": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + "frequency_mhz": schema.Float64Attribute{ + Computed: true, + }, + "used_frequency_mhz": schema.Float64Attribute{ + Computed: true, + }, + }, + }, + "memory": schema.SingleNestedAttribute{ + Computed: true, + Description: "The memory details of the ESXi host.", + Attributes: map[string]schema.Attribute{ + "total_capacity_mb": schema.Float64Attribute{ + Computed: true, + }, + "used_capacity_mb": schema.Float64Attribute{ + Computed: true, + }, + }, + }, + "storage": schema.ListNestedAttribute{ + Computed: true, + Description: "The storage details of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "total_capacity_mb": schema.Float64Attribute{ + Computed: true, + }, + "used_capacity_mb": schema.Float64Attribute{ + Computed: true, + }, + "disks": schema.ListNestedAttribute{ + Computed: true, + Description: "The disks of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "capacity_mb": schema.Float64Attribute{ + Computed: true, + }, + "disk_type": schema.StringAttribute{ + Computed: true, + }, + "manufacturer": schema.StringAttribute{ + Computed: true, + }, + "model": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + }, + }, + "physical_nics": schema.ListNestedAttribute{ + Computed: true, + Description: "The physical NICs of the ESXi host.", + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "device_name": schema.StringAttribute{ + Computed: true, + }, + "mac_address": schema.StringAttribute{ + Computed: true, + }, + "speed": schema.Int64Attribute{ + Computed: true, + }, + "unit": schema.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + } +} + +func (d *DataSourceHost) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var data []HostModel + + params := hosts.NewGetHostsParamsWithContext(ctx). + WithTimeout(constants.DefaultVcfApiCallTimeout) + + hostPayload, err := d.client.Hosts.GetHosts(params) + if err != nil { + resp.Diagnostics.Append(diag.NewErrorDiagnostic("Failed to retrieve hosts", err.Error())) + return + } + + for _, element := range hostPayload.Payload.Elements { + // Debugging logs to print the values of id and status + tflog.Debug(ctx, "Mapping host", map[string]interface{}{ + "fqdn": element.Fqdn, + "id": element.ID, + "status": element.Status, + }) + + host := mapHostElementToModel(element) + data = append(data, host) + } + + // Debugging log to trace the mapped data + tflog.Debug(ctx, "Mapped data", map[string]interface{}{ + "hosts": data, + }) + + resp.State.Set(ctx, &data) +} + +func mapHostElementToModel(element *models.Host) HostModel { + // Inline dereferencing logic + domainID := derefString(element.Domain.ID) + clusterID := derefString(element.Cluster.ID) + networkPoolID := derefString(element.Networkpool.ID) + + return HostModel{ + Fqdn: types.StringValue(element.Fqdn), + Id: types.StringValue(element.ID), + Status: types.StringValue(element.Status), + Domain: []DomainModel{ + { + Id: types.StringValue(domainID), + Name: types.StringValue(element.Domain.Name), + }, + }, + Cluster: []ClusterModel{ + { + Id: types.StringValue(clusterID), + }, + }, + NetworkPool: []NetworkPoolModel{ + { + Id: types.StringValue(networkPoolID), + Name: types.StringValue(element.Networkpool.Name), + }, + }, + Version: types.StringValue(element.EsxiVersion), + CompatibleStorageType: types.StringValue(element.CompatibleStorageType), + Hardware: []HardwareModel{ + { + Model: types.StringValue(element.HardwareModel), + Vendor: types.StringValue(element.HardwareVendor), + Hybrid: types.BoolValue(element.Hybrid), + }, + }, + IpAddresses: []IpAddressModel{ + { + IpAddress: types.StringValue(element.IPAddresses[0].IPAddress), + Type: types.StringValue(element.IPAddresses[0].Type), + }, + }, + Cpu: CpuModel{ + Cores: types.Int64Value(int64(element.CPU.Cores)), + CpuCoreDetails: []CpuCoreModel{ + { + FrequencyMHz: types.Float64Value(element.CPU.CPUCores[0].FrequencyMHz), + Manufacturer: types.StringValue(element.CPU.CPUCores[0].Manufacturer), + Model: types.StringValue(element.CPU.CPUCores[0].Model), + }, + }, + FrequencyMHz: types.Float64Value(element.CPU.FrequencyMHz), + UsedFrequencyMHz: types.Float64Value(element.CPU.UsedFrequencyMHz), + }, + Memory: MemoryModel{ + TotalCapacityMB: types.Float64Value(element.Memory.TotalCapacityMB), + UsedCapacityMB: types.Float64Value(element.Memory.UsedCapacityMB), + }, + Storage: []StorageModel{ + { + TotalCapacityMB: types.Float64Value(element.Storage.TotalCapacityMB), + UsedCapacityMB: types.Float64Value(element.Storage.UsedCapacityMB), + Disks: []DiskModel{ + { + CapacityMB: types.Float64Value(element.Storage.Disks[0].CapacityMB), + DiskType: types.StringValue(element.Storage.Disks[0].DiskType), + Manufacturer: types.StringValue(element.Storage.Disks[0].Manufacturer), + Model: types.StringValue(element.Storage.Disks[0].Model), + }, + }, + }, + }, + PhysicalNics: []PhysicalNicModel{ + { + DeviceName: types.StringValue(element.PhysicalNics[0].DeviceName), + MacAddress: types.StringValue(element.PhysicalNics[0].MacAddress), + Speed: types.Int64Value(element.PhysicalNics[0].Speed), + Unit: types.StringValue(element.PhysicalNics[0].Unit), + }, + }, + } +} + +func derefString(s *string) string { + if s == nil { + return "" + } + return *s +} diff --git a/internal/provider/data_host_test.go b/internal/provider/data_host_test.go new file mode 100644 index 0000000..97bf334 --- /dev/null +++ b/internal/provider/data_host_test.go @@ -0,0 +1,56 @@ +// © 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 + username = "root" + password = "password" + network_pool_id = "test_network_pool_id" + storage_type = "VSAN" + } + + data "vcf_host" "test_host" { + fqdn = vcf_host.test_host.fqdn + } + `, hostFqdn) +} diff --git a/internal/provider/framework_provider.go b/internal/provider/framework_provider.go index a96800e..4367728 100644 --- a/internal/provider/framework_provider.go +++ b/internal/provider/framework_provider.go @@ -143,7 +143,11 @@ func (frameworkProvider *FrameworkProvider) Resources(ctx context.Context) []fun } func (frameworkProvider *FrameworkProvider) DataSources(ctx context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{} + return []func() datasource.DataSource{ + func() datasource.DataSource { + return &DataSourceHost{} + }, + } } func (frameworkProvider *FrameworkProvider) Configure(ctx context.Context, req provider.ConfigureRequest, res *provider.ConfigureResponse) { @@ -168,6 +172,7 @@ func (frameworkProvider *FrameworkProvider) Configure(ctx context.Context, req p frameworkProvider.SddcManagerClient = client res.ResourceData = client + res.DataSourceData = client } else { // Connect to Cloud Builder client := api_client.NewCloudBuilderClient( @@ -179,6 +184,7 @@ func (frameworkProvider *FrameworkProvider) Configure(ctx context.Context, req p frameworkProvider.CloudBuilderClient = client res.ResourceData = client + res.DataSourceData = client } }