diff --git a/vsphere/data_source_vsphere_host.go b/vsphere/data_source_vsphere_host.go new file mode 100644 index 000000000..52ee01245 --- /dev/null +++ b/vsphere/data_source_vsphere_host.go @@ -0,0 +1,46 @@ +package vsphere + +import ( + "fmt" + + "github.com/hashicorp/terraform/helper/schema" + "github.com/vmware/govmomi" +) + +func dataSourceVSphereHost() *schema.Resource { + return &schema.Resource{ + Read: dataSourceVSphereHostRead, + + Schema: map[string]*schema.Schema{ + "name": &schema.Schema{ + Type: schema.TypeString, + Description: "The name of the host. This can be a name or path. If not provided, the default host is used.", + Optional: true, + }, + "datacenter_id": &schema.Schema{ + Type: schema.TypeString, + Description: "The managed object ID of the datacenter to look for the host in.", + Required: true, + }, + }, + } +} + +func dataSourceVSphereHostRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(*govmomi.Client) + name := d.Get("name").(string) + dcID := d.Get("datacenter_id").(string) + dc, err := datacenterFromID(client, dcID) + if err != nil { + return fmt.Errorf("error fetching datacenter: %s", err) + } + hs, err := hostSystemOrDefault(client, name, dc) + if err != nil { + return fmt.Errorf("error fetching host: %s", err) + } + + id := hs.Reference().Value + d.SetId(id) + + return nil +} diff --git a/vsphere/data_source_vsphere_host_test.go b/vsphere/data_source_vsphere_host_test.go new file mode 100644 index 000000000..6a2b0ac25 --- /dev/null +++ b/vsphere/data_source_vsphere_host_test.go @@ -0,0 +1,108 @@ +package vsphere + +import ( + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform/helper/resource" +) + +func TestAccDataSourceVSphereHost(t *testing.T) { + var tp *testing.T + testAccDataSourceVSphereHostCases := []struct { + name string + testCase resource.TestCase + }{ + { + "basic", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccDataSourceVSphereHostPreCheck(tp) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVSphereHostConfig(), + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr( + "data.vsphere_host.host", + "id", + testAccDataSourceVSphereHostExpectedRegexp(), + ), + ), + }, + }, + }, + }, + { + "default", + resource.TestCase{ + PreCheck: func() { + testAccPreCheck(tp) + testAccDataSourceVSphereHostPreCheck(tp) + testAccSkipIfNotEsxi(tp) + }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceVSphereHostConfigDefault, + Check: resource.ComposeTestCheckFunc( + resource.TestMatchResourceAttr( + "data.vsphere_host.host", + "id", + testAccDataSourceVSphereHostExpectedRegexp(), + ), + ), + }, + }, + }, + }, + } + + for _, tc := range testAccDataSourceVSphereHostCases { + t.Run(tc.name, func(t *testing.T) { + tp = t + resource.Test(t, tc.testCase) + }) + } +} + +func testAccDataSourceVSphereHostPreCheck(t *testing.T) { + if os.Getenv("VSPHERE_DATACENTER") == "" { + t.Skip("set VSPHERE_DATACENTER to run vsphere_host acceptance tests") + } + if os.Getenv("VSPHERE_ESXI_HOST") == "" { + t.Skip("set VSPHERE_ESXI_HOST to run vsphere_host acceptance tests") + } +} + +func testAccDataSourceVSphereHostExpectedRegexp() *regexp.Regexp { + if os.Getenv("VSPHERE_TEST_ESXI") != "" { + return regexp.MustCompile("^ha-host$") + } + return regexp.MustCompile("^host-") +} + +func testAccDataSourceVSphereHostConfig() string { + return fmt.Sprintf(` +data "vsphere_datacenter" "dc" { + name = "%s" +} + +data "vsphere_host" "host" { + name = "%s" + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} +`, os.Getenv("VSPHERE_DATACENTER"), os.Getenv("VSPHERE_ESXI_HOST")) +} + +const testAccDataSourceVSphereHostConfigDefault = ` +data "vsphere_datacenter" "dc" {} + +data "vsphere_host" "host" { + datacenter_id = "${data.vsphere_datacenter.dc.id}" +} +` diff --git a/vsphere/datacenter_helper.go b/vsphere/datacenter_helper.go index 31c198ceb..4c90ca0ee 100644 --- a/vsphere/datacenter_helper.go +++ b/vsphere/datacenter_helper.go @@ -6,6 +6,7 @@ import ( "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/vim25/types" "golang.org/x/net/context" ) @@ -28,3 +29,21 @@ func getDatacenter(c *govmomi.Client, dc string) (*object.Datacenter, error) { } return nil, fmt.Errorf("unsupported ApiType: %s", t) } + +// datacenterFromID locates a Datacenter by its managed object reference ID. +func datacenterFromID(client *govmomi.Client, id string) (*object.Datacenter, error) { + finder := find.NewFinder(client.Client, false) + + ref := types.ManagedObjectReference{ + Type: "Datacenter", + Value: id, + } + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + ds, err := finder.ObjectReference(ctx, ref) + if err != nil { + return nil, fmt.Errorf("could not find datacenter with id: %s: %s", id, err) + } + return ds.(*object.Datacenter), nil +} diff --git a/vsphere/helper_test.go b/vsphere/helper_test.go new file mode 100644 index 000000000..f08da80cc --- /dev/null +++ b/vsphere/helper_test.go @@ -0,0 +1,13 @@ +package vsphere + +import ( + "os" + "testing" +) + +// testAccSkipIfNotEsxi skips a test if VSPHERE_TEST_ESXI is not set. +func testAccSkipIfNotEsxi(t *testing.T) { + if os.Getenv("VSPHERE_TEST_ESXI") == "" { + t.Skip("set VSPHERE_TEST_ESXI to run ESXi-specific acceptance tests") + } +} diff --git a/vsphere/host_system_helper.go b/vsphere/host_system_helper.go new file mode 100644 index 000000000..357602f4c --- /dev/null +++ b/vsphere/host_system_helper.go @@ -0,0 +1,32 @@ +package vsphere + +import ( + "context" + "fmt" + + "github.com/vmware/govmomi" + "github.com/vmware/govmomi/find" + "github.com/vmware/govmomi/object" +) + +// hostSystemOrDefault returns a HostSystem from a specific host name and +// datacenter. If the user is connecting over ESXi, the default host system is +// used. +func hostSystemOrDefault(client *govmomi.Client, name string, dc *object.Datacenter) (*object.HostSystem, error) { + finder := find.NewFinder(client.Client, false) + finder.SetDatacenter(dc) + + ctx, cancel := context.WithTimeout(context.Background(), defaultAPITimeout) + defer cancel() + t := client.ServiceContent.About.ApiType + switch t { + case "HostAgent": + return finder.DefaultHostSystem(ctx) + case "VirtualCenter": + if name != "" { + return finder.HostSystem(ctx, name) + } + return finder.DefaultHostSystem(ctx) + } + return nil, fmt.Errorf("unsupported ApiType: %s", t) +} diff --git a/vsphere/provider.go b/vsphere/provider.go index 2aafa226e..350944d69 100644 --- a/vsphere/provider.go +++ b/vsphere/provider.go @@ -2,11 +2,16 @@ package vsphere import ( "fmt" + "time" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) +// defaultAPITimeout is a default timeout value that is passed to functions +// requiring contexts, and other various waiters. +var defaultAPITimeout = time.Minute * 5 + // Provider returns a terraform.ResourceProvider. func Provider() terraform.ResourceProvider { return &schema.Provider{ @@ -74,6 +79,7 @@ func Provider() terraform.ResourceProvider { DataSourcesMap: map[string]*schema.Resource{ "vsphere_datacenter": dataSourceVSphereDatacenter(), + "vsphere_host": dataSourceVSphereHost(), }, ConfigureFunc: providerConfigure, diff --git a/vsphere/resource_vsphere_license_test.go b/vsphere/resource_vsphere_license_test.go index 733e316d4..71af5bf2c 100644 --- a/vsphere/resource_vsphere_license_test.go +++ b/vsphere/resource_vsphere_license_test.go @@ -89,7 +89,7 @@ func TestAccVSphereLicenseWithLabelsOnESXiServer(t *testing.T) { PreCheck: func() { testAccPreCheck(t) testAccVSpherePreLicenseBasicCheck(t) - testAccVspherePreLicenseESXiServerIsSetCheck(t) + testAccSkipIfNotEsxi(t) }, Providers: testAccProviders, Steps: []resource.TestStep{ @@ -109,12 +109,6 @@ func testAccVspherePreLicenseESXiServerIsNotSetCheck(t *testing.T) { t.Skip("VSPHERE_TEST_ESXI must not be set for this acceptance test") } } -func testAccVspherePreLicenseESXiServerIsSetCheck(t *testing.T) { - key, err := strconv.ParseBool(os.Getenv("VSPHERE_TEST_ESXI")) - if err != nil || !key { - t.Skip("VSPHERE_TEST_ESXI must be set to true for this acceptance test") - } -} func testAccVSphereLicenseWithLabelConfig() string { return fmt.Sprintf(` diff --git a/website/docs/d/host.html.markdown b/website/docs/d/host.html.markdown new file mode 100644 index 000000000..b9cf977c8 --- /dev/null +++ b/website/docs/d/host.html.markdown @@ -0,0 +1,43 @@ +--- +layout: "vsphere" +page_title: "VMware vSphere: vsphere_host" +sidebar_current: "docs-vsphere-data-source-host" +description: |- + A data source that can be used to get the ID of a host. +--- + +# vsphere\_host + +The `vsphere_host` data source can be used to discover the ID of a vSphere +host. This can then be used with resources or data sources that require a host +managed object reference ID. + +## Example Usage + +```hcl +data "vsphere_datacenter" "datacenter" { + name = "dc1" +} + +data "vsphere_host" "host" { + name = "esxi1" + datacenter_id = "${data.vsphere_datacenter.datacenter.id}" +} +``` + +## Argument Reference + +The following arguments are supported: + +* `name` - (String) The name of the host. This can be a name or path. Can be + omitted if there is only one host in your inventory. +* `datacenter_id` - (String, required) The managed object reference ID of a + datacenter. + +~> **NOTE:** When used against an ESXi host directly, this data source _always_ +fetches the server's host object ID, regardless of what is entered into `name`. + +## Attribute Reference + +The only exported attribute is `id`, which is the managed object ID of this +host. diff --git a/website/vsphere.erb b/website/vsphere.erb index 15228e46b..6a9ae2a1c 100644 --- a/website/vsphere.erb +++ b/website/vsphere.erb @@ -16,6 +16,9 @@