Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: datasource for virtual machines #498

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .web-docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ packer plugins install github.com/hashicorp/vsphere
This builder deploys and publishes new virtual machine to a vSphere Supervisor cluster using VM
Service.

#### Data Sources

- [vsphere-virtualmachine](/packer/integrations/hashicorp/vsphere/latest/components/data-source/vsphere-virtualmachine) -
This data source returns the name of a virtual machine that matches all defined filters.

#### Post-Processors

- [vsphere](/packer/integrations/hashicorp/vsphere/latest/components/post-processor/vsphere) -
Expand Down
160 changes: 160 additions & 0 deletions .web-docs/components/data-source/virtualmachine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
Type: `vsphere-virtualmachine`
Artifact BuilderId: `vsphere.virtualmachine`

This data source retrieves information about existing virtual machines from vSphere
and return name of one virtual machine that matches all specified filters. This virtual
machine can be used in the vSphere Clone builder to select a template.

## Configuration Reference

### Filters Configuration

**Optional:**

<!-- Code generated from the comments of the Config struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->

- `name` (string) - Basic filter with glob support (e.g. `ubuntu_basic*`). Defaults to `*`.
Using strict globs will not reduce execution time because vSphere API
returns the full inventory. But can be used for better readability over
regular expressions.

- `name_regex` (string) - Extended name filter with regular expressions support
(e.g. `ubuntu[-_]basic[0-9]*`). Default is empty. The match of the
regular expression is checked by substring. Use `^` and `$` to define a
full string. For example, the `^[^_]+$` filter will search names
without any underscores. The expression must use
[Go Regex Syntax](https://pkg.go.dev/regexp/syntax).

- `template` (bool) - Filter to return only objects that are virtual machine templates.
Defaults to `false` and returns all virtual machines.

- `host` (string) - Filter to search virtual machines only on the specified ESX host.

- `tag` ([]Tag) - Filter to return only that virtual machines that have attached all
specifies tags. Specify one or more `tag` blocks to define list of tags
for the filter. Multiple blocks can be created programmatically in HCL2 with the [`dynamic_block`](/packer/docs/templates/hcl_templates/expressions#dynamic-blocks).

- `latest` (bool) - This filter determines how to handle multiple machines that were
matched with all previous filters. Machine creation time is being used
to find latest. By default, multiple matching machines results in an
error.

<!-- End of code generated from the comments of the Config struct in datasource/virtualmachine/data.go; -->


### Tags Filter Configuration

<!-- Code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->

HCL Example:

```hcl

tag {
category = "team"
name = "operations"
}
tag {
category = "sla"
name = "gold"
}

```

<!-- End of code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; -->


**Required:**

<!-- Code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->

- `name` (string) - Name of the tag added to virtual machine which must pass the `tag`
filter.

- `category` (string) - Name of the tag category that contains the tag.

-> **Note:** Both `name` and `category` must be specified in the `tag`
filter.

<!-- End of code generated from the comments of the Tag struct in datasource/virtualmachine/data.go; -->


### Connection Configuration

**Optional:**

<!-- Code generated from the comments of the ConnectConfig struct in builder/vsphere/common/step_connect.go; DO NOT EDIT MANUALLY -->

- `vcenter_server` (string) - The fully qualified domain name or IP address of the vCenter Server
instance.

- `username` (string) - The username to authenticate with the vCenter Server instance.

- `password` (string) - The password to authenticate with the vCenter Server instance.

- `insecure_connection` (bool) - Do not validate the certificate of the vCenter Server instance.
Defaults to `false`.

-> **Note:** This option is beneficial in scenarios where the certificate
is self-signed or does not meet standard validation criteria.

- `datacenter` (string) - The name of the datacenter object in the vSphere inventory.

-> **Note:** Required if more than one datacenter object exists in the
vSphere inventory.

<!-- End of code generated from the comments of the ConnectConfig struct in builder/vsphere/common/step_connect.go; -->


## Output

<!-- Code generated from the comments of the DatasourceOutput struct in datasource/virtualmachine/data.go; DO NOT EDIT MANUALLY -->

- `vm_name` (string) - Name of the found virtual machine.

<!-- End of code generated from the comments of the DatasourceOutput struct in datasource/virtualmachine/data.go; -->


## Example Usage

This example demonstrates how to connect to vSphere cluster and search for the latest virtual machine
that matches the filters. The name of the machine is then output to the console as an output variable.
```hcl
data "vsphere-virtualmachine" "default" {
vcenter_server = "vcenter.example.com"
insecure_connection = true
username = "[email protected]"
password = "VMware1!"
datacenter = "dc-01"
latest = true
tags {
category = "team"
name = "operations"
}
tags {
category = "sla"
name = "gold"
}

}

locals {
vm_name = data.vsphere-virtualmachine.default.vm_name
}

source "null" "example" {
communicator = "none"
}

build {
sources = [
"source.null.example"
]

provisioner "shell-local" {
inline = [
"echo vm_name: ${local.vm_name}",
]
}
}
```
5 changes: 5 additions & 0 deletions .web-docs/metadata.hcl
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ integration {
name = "vSphere Template"
slug = "vsphere-template"
}
component {
type = "data-source"
name = "vSphere Virtual Machine"
slug = "vsphere-virtualmachine"
}
}
1 change: 0 additions & 1 deletion builder/vsphere/driver/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -449,7 +449,6 @@ func (vm *VirtualMachineDriver) Clone(ctx context.Context, config *CloneConfig)
Device: adapter.(types.BaseVirtualDevice),
Operation: types.VirtualDeviceConfigSpecOperationEdit,
}

configSpec.DeviceChange = append(configSpec.DeviceChange, config)
}

Expand Down
60 changes: 60 additions & 0 deletions datasource/common/driver/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package driver

import (
"context"
"fmt"
"net/url"

"github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vapi/rest"
)

type VCenterDriver struct {
Ctx context.Context
Client *govmomi.Client
RestClient *rest.Client
Finder *find.Finder
Datacenter *object.Datacenter
}

func NewDriver(config common.ConnectConfig) (*VCenterDriver, error) {
ctx := context.Background()

vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer))
if err != nil {
return nil, fmt.Errorf("failed to parse URL: %w", err)
}
vcenterUrl.User = url.UserPassword(config.Username, config.Password)

client, err := govmomi.NewClient(ctx, vcenterUrl, true)
if err != nil {
return nil, fmt.Errorf("failed to create govmomi Client: %w", err)
}

restClient := rest.NewClient(client.Client)
err = restClient.Login(ctx, vcenterUrl.User)
if err != nil {
return nil, fmt.Errorf("failed to login to REST API endpoint: %w", err)
}

finder := find.NewFinder(client.Client, true)
datacenter, err := finder.DatacenterOrDefault(ctx, config.Datacenter)
if err != nil {
return nil, fmt.Errorf("failed to find datacenter: %w", err)
}
finder.SetDatacenter(datacenter)

return &VCenterDriver{
Ctx: ctx,
Client: client,
RestClient: restClient,
Finder: finder,
Datacenter: datacenter,
}, nil
}
67 changes: 67 additions & 0 deletions datasource/common/testing/call_restapi.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package testing

import (
"context"
"fmt"

"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vapi/tags"
)

// MarkSimulatedVmAsTemplate powers off the virtual machine before converting it to a template (because the simulator
// creates all virtual machines in an online state).
func MarkSimulatedVmAsTemplate(ctx context.Context, vm *object.VirtualMachine) error {
task, err := vm.PowerOff(ctx)
if err != nil {
return fmt.Errorf("failed to issue powering off command to the machine: %w", err)
}
err = task.Wait(ctx)
if err != nil {
return fmt.Errorf("failed to power off the machine: %w", err)
}
err = vm.MarkAsTemplate(ctx)
if err != nil {
return fmt.Errorf("failed to mark virtual machine as a template: %w", err)
}
return nil
}

// FindOrCreateCategory tries to find category passed by name, creates category if not found and returns category ID.
// Category will be created with "MULTIPLE" constraint.
func FindOrCreateCategory(ctx context.Context, man *tags.Manager, catName string) (string, error) {
categoryList, err := man.GetCategories(ctx)
if err != nil {
return "", fmt.Errorf("cannot return categories from cluster: %w", err)
}
for _, category := range categoryList {
if category.Name == catName {
return category.ID, nil
}
}
newCategoryID, err := man.CreateCategory(ctx, &tags.Category{Name: catName, Cardinality: "MULTIPLE"})
if err != nil {
return "", fmt.Errorf("cannot create category: %w", err)
}
return newCategoryID, nil
}

// FindOrCreateTag tries to find the tagName in category with catID, creates if not found and returns tag ID.
func FindOrCreateTag(ctx context.Context, man *tags.Manager, catID string, tagName string) (string, error) {
tagsInCategory, err := man.GetTagsForCategory(ctx, catID)
if err != nil {
return "", fmt.Errorf("cannot return tags for category: %w", err)
}
for _, tag := range tagsInCategory {
if tag.Name == tagName {
return tag.ID, nil
}
}
newTagID, err := man.CreateTag(ctx, &tags.Tag{Name: tagName, CategoryID: catID})
if err != nil {
return "", fmt.Errorf("cannot create tag: %w", err)
}
return newTagID, nil
}
Loading