diff --git a/.web-docs/README.md b/.web-docs/README.md index d6da08e1..20127e6f 100644 --- a/.web-docs/README.md +++ b/.web-docs/README.md @@ -51,9 +51,8 @@ packer plugins install github.com/hashicorp/vsphere #### Data Sources -- [vsphere-virtual_machine](/packer/integrations/hashicorp/vsphere/latest/components/data-source/vsphere-virtual_machine) - - This datasource returns name of existing virtual machine that matches all defined filters to use - it as a builder source for `vsphere-clone`. +- [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 diff --git a/.web-docs/components/data-source/virtual_machine/README.md b/.web-docs/components/data-source/virtual_machine/README.md deleted file mode 100644 index a24f7bca..00000000 --- a/.web-docs/components/data-source/virtual_machine/README.md +++ /dev/null @@ -1,155 +0,0 @@ -Type: `vsphere-virtual_machine` -Artifact BuilderId: `vsphere.virtual_machine` - -This datasource is able to get information about existing virtual machines from vSphere -and return name of one virtual machine that matches all specified filters. This virtual -machine can later be used in the vSphere Clone builder to select template. - -## Configuration Reference - -### Filters Configuration - -**Optional:** - - - -- `name` (string) - Basic filter with glob support (e.g. `nginx_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. `nginx[-_]basic[0-9]*`). Default is empty. - The match of the regular expression is checked by substring. Use `^` and `$` to define a full string. - E.g. 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 VMs. - -- `node` (string) - Filter to search virtual machines only on the specified node. - -- `vm_tags` ([]Tag) - Filter to return only that virtual machines that have attached all specifies tags. - Specify one or more `vm_tags` blocks to define list of tags that will make up the filter. - Should work since vCenter 6.7. To avoid incompatibility, REST client is being - initialized only when at least one tag has been defined in the config. - -- `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. - - - - -### Tags Filter Configuration - - - -Example of multiple vm_tags blocks in HCL format: -``` - - vm_tags { - category = "team" - name = "operations" - } - vm_tags { - category = "SLA" - name = "gold" - } - -``` - - - - -**Required:** - - - -- `name` (string) - Tag with this name must be attached to virtual machine which should pass the Tags Filter. - -- `category` (string) - Name of the category that contains this tag. Both tag and category must be specified. - - - - -### Connection Configuration - -**Optional:** - - - -- `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. - - - - -## Output - - - -- `vm_name` (string) - Name of the found virtual machine. - - - - -## Example Usage - -This is a very basic example that connects to vSphere cluster and tries to search -the latest virtual machine that matches all filters. The machine name is then printed -to console as output variable. -```hcl -data "vsphere-virtual_machine" "default" { - vcenter_server = "vcenter.example.org" - insecure_connection = true - username = "administrator@example.org" - password = "St4ongPa$$w0rd" - datacenter = "AZ1" - latest = true - vm_tags { - category = "team" - name = "operations" - } - vm_tags { - category = "SLA" - name = "gold" - } - -} - -locals { - vm_name = data.vsphere-virtual_machine.default.vm_name -} - -source "null" "basic-example" { - communicator = "none" -} - -build { - sources = [ - "source.null.basic-example" - ] - - provisioner "shell-local" { - inline = [ - "echo vm_name: ${local.vm_name}", - ] - } -} - - -``` diff --git a/.web-docs/components/data-source/virtualmachine/README.md b/.web-docs/components/data-source/virtualmachine/README.md new file mode 100644 index 00000000..aaea7aaa --- /dev/null +++ b/.web-docs/components/data-source/virtualmachine/README.md @@ -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:** + + + +- `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. + +- `tags` ([]Tag) - Filter to return only that virtual machines that have attached all + specifies tags. Specify one or more `tags` blocks to define list of tags + for the filter. + +- `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. + + + + +### Tags Filter Configuration + + + +HCL Example: + +```hcl + + tags { + category = "team" + name = "operations" + } + tags { + category = "sla" + name = "gold" + } + +``` + + + + +**Required:** + + + +- `name` (string) - Name of the tag added to virtual machine which must pass the `tags` + filter. + +- `category` (string) - Name of the tag category that contains the tag. + + -> **Note:** Both `name` and `category` must be specified in the `tags` + filter. + + + + +### Connection Configuration + +**Optional:** + + + +- `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. + + + + +## Output + + + +- `vm_name` (string) - Name of the found virtual machine. + + + + +## 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 = "administrator@vsphere.local" + 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}", + ] + } +} +``` diff --git a/.web-docs/metadata.hcl b/.web-docs/metadata.hcl index 7e7e5536..1a7e5589 100644 --- a/.web-docs/metadata.hcl +++ b/.web-docs/metadata.hcl @@ -36,6 +36,6 @@ integration { component { type = "data-source" name = "vSphere Virtual Machine" - slug = "vsphere-virtual_machine" + slug = "vsphere-virtualmachine" } } diff --git a/builder/vsphere/driver/vm.go b/builder/vsphere/driver/vm.go index e3a6c413..20b69751 100644 --- a/builder/vsphere/driver/vm.go +++ b/builder/vsphere/driver/vm.go @@ -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) } diff --git a/datasource/common/driver/driver.go b/datasource/common/driver/driver.go new file mode 100644 index 00000000..f4e342df --- /dev/null +++ b/datasource/common/driver/driver.go @@ -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 +} diff --git a/datasource/virtual_machine/testing/call_restapi.go b/datasource/common/testing/call_restapi.go similarity index 56% rename from datasource/virtual_machine/testing/call_restapi.go rename to datasource/common/testing/call_restapi.go index b5ad5800..57d84011 100644 --- a/datasource/virtual_machine/testing/call_restapi.go +++ b/datasource/common/testing/call_restapi.go @@ -1,35 +1,40 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package testing import ( "context" + "fmt" - "github.com/pkg/errors" "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 errors.Wrap(err, "failed to issue powering off command to the machine") + return fmt.Errorf("failed to issue powering off command to the machine: %w", err) } err = task.Wait(ctx) if err != nil { - return errors.Wrap(err, "failed to power off the machine") + return fmt.Errorf("failed to power off the machine: %w", err) } err = vm.MarkAsTemplate(ctx) if err != nil { - return errors.Wrap(err, "failed to mark VM as a template") + return fmt.Errorf("failed to mark virtual machine as a template: %w", err) } return nil } -// Try to find category passed by name, create category if not found and return category ID. +// 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 "", errors.Wrap(err, "cannot get categories from cluster") + return "", fmt.Errorf("cannot return categories from cluster: %w", err) } for _, category := range categoryList { if category.Name == catName { @@ -38,16 +43,16 @@ func FindOrCreateCategory(ctx context.Context, man *tags.Manager, catName string } newCategoryID, err := man.CreateCategory(ctx, &tags.Category{Name: catName, Cardinality: "MULTIPLE"}) if err != nil { - return "", errors.Wrap(err, "cannot create category") + return "", fmt.Errorf("cannot create category: %w", err) } return newCategoryID, nil } -// Try to find the tagName in category with catID, create if not found and return tag ID. +// 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 "", errors.Wrap(err, "cannot get tags for category") + return "", fmt.Errorf("cannot return tags for category: %w", err) } for _, tag := range tagsInCategory { if tag.Name == tagName { @@ -56,7 +61,7 @@ func FindOrCreateTag(ctx context.Context, man *tags.Manager, catID string, tagNa } newTagID, err := man.CreateTag(ctx, &tags.Tag{Name: tagName, CategoryID: catID}) if err != nil { - return "", errors.Wrap(err, "cannot create tag") + return "", fmt.Errorf("cannot create tag: %w", err) } return newTagID, nil } diff --git a/datasource/virtual_machine/testing/simulator.go b/datasource/common/testing/simulator.go similarity index 75% rename from datasource/virtual_machine/testing/simulator.go rename to datasource/common/testing/simulator.go index 988e347d..ba1b5a90 100644 --- a/datasource/virtual_machine/testing/simulator.go +++ b/datasource/common/testing/simulator.go @@ -1,12 +1,15 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + package testing import ( "context" "crypto/tls" + "fmt" "net/url" "time" - "github.com/pkg/errors" "github.com/vmware/govmomi" "github.com/vmware/govmomi/find" "github.com/vmware/govmomi/object" @@ -43,12 +46,12 @@ type VCenterSimulator struct { func NewVCenterSimulator(model *simulator.Model) (*VCenterSimulator, error) { ctx := context.Background() if model == nil { - return nil, errors.New("model has not been initialized") + return nil, fmt.Errorf("model has not been initialized") } err := model.Create() if err != nil { - return nil, errors.Wrap(err, "failed to create simulator model") + return nil, fmt.Errorf("failed to create simulator model: %w", err) } model.Service.RegisterEndpoints = true model.Service.TLS = new(tls.Config) @@ -57,29 +60,29 @@ func NewVCenterSimulator(model *simulator.Model) (*VCenterSimulator, error) { u, err := url.Parse(server.URL.String()) if err != nil { - return nil, errors.Wrap(err, "failed to parse simulator URL") + return nil, fmt.Errorf("failed to parse simulator URL: %w", err) } password, _ := simulator.DefaultLogin.Password() u.User = url.UserPassword(simulator.DefaultLogin.Username(), password) client, err := govmomi.NewClient(ctx, u, true) if err != nil { - return nil, errors.Wrap(err, "failed to connect to SOAP simulator") + return nil, fmt.Errorf("failed to connect to SOAP simulator: %w", err) } restClient := rest.NewClient(client.Client) err = restClient.Login(ctx, simulator.DefaultLogin) if err != nil { - return nil, errors.Wrap(err, "failed to login to REST simulator") + return nil, fmt.Errorf("failed to login to REST simulator: %w", err) } finder := find.NewFinder(client.Client, false) dcs, err := finder.DatacenterList(ctx, "*") if err != nil { - return nil, errors.Wrap(err, "failed to list datacenters") + return nil, fmt.Errorf("failed to list datacenters: %w", err) } if len(dcs) == 0 { - return nil, errors.Wrap(err, "datacenters were not found in the simulator") + return nil, fmt.Errorf("datacenters were not found in the simulator: %w", err) } finder.SetDatacenter(dcs[0]) @@ -110,7 +113,7 @@ func (sim *VCenterSimulator) CustomizeSimulator(vmsConfig []SimulatedVMConfig) e vms, err := sim.Finder.VirtualMachineList(sim.Ctx, "*") if err != nil { - return errors.Wrap(err, "failed to list VMs in cluster") + return fmt.Errorf("failed to list virtual machines in cluster: %w", err) } for i := 0; i < len(vmsConfig); i++ { @@ -125,17 +128,17 @@ func (sim *VCenterSimulator) CustomizeSimulator(vmsConfig []SimulatedVMConfig) e if vmsConfig[i].Name != "" { task, err := vms[i].Reconfigure(sim.Ctx, vmConfig) if err != nil { - return errors.Wrap(err, "failed to issue rename of VM command") + return fmt.Errorf("failed to issue rename of virtual machine command: %w", err) } if err = task.Wait(sim.Ctx); err != nil { - return errors.Wrap(err, "failed to rename VM") + return fmt.Errorf("failed to rename virtual machine: %w", err) } } if vmsConfig[i].Template { err = MarkSimulatedVmAsTemplate(sim.Ctx, vms[i]) if err != nil { - return errors.Wrap(err, "failed to convert VMs to templates") + return fmt.Errorf("failed to convert to templates: %w", err) } } @@ -143,15 +146,15 @@ func (sim *VCenterSimulator) CustomizeSimulator(vmsConfig []SimulatedVMConfig) e for _, tag := range vmsConfig[i].Tags { catID, err := FindOrCreateCategory(sim.Ctx, tagMan, tag.Category) if err != nil { - return errors.Wrap(err, "failed to find/create category") + return fmt.Errorf("failed to find/create category: %w", err) } tagID, err := FindOrCreateTag(sim.Ctx, tagMan, catID, tag.Name) if err != nil { - return errors.Wrap(err, "failed to find/create tag") + return fmt.Errorf("failed to find/create tag: %w", err) } err = tagMan.AttachTag(sim.Ctx, tagID, vms[i].Reference()) if err != nil { - return errors.Wrap(err, "failed to attach tag to VM") + return fmt.Errorf("failed to attach tag to virtual machine: %w", err) } } } diff --git a/datasource/virtual_machine/driver.go b/datasource/virtual_machine/driver.go deleted file mode 100644 index 82070cb3..00000000 --- a/datasource/virtual_machine/driver.go +++ /dev/null @@ -1,63 +0,0 @@ -package virtual_machine - -import ( - "context" - "fmt" - - "net/url" - - "github.com/pkg/errors" - "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 Config) (*VCenterDriver, error) { - ctx := context.Background() - - vcenterUrl, err := url.Parse(fmt.Sprintf("https://%v/sdk", config.VCenterServer)) - if err != nil { - return nil, errors.Wrap(err, "failed to parse URL") - } - vcenterUrl.User = url.UserPassword(config.Username, config.Password) - - client, err := govmomi.NewClient(ctx, vcenterUrl, true) - if err != nil { - return nil, errors.Wrap(err, "failed to create govmomi client") - } - - var restClient *rest.Client - if config.VmTags != nil { - // REST client is only needed when the plugin has to retrieve tags from VMs. - // Skip initialization if not needed (there is additional risk of fail on old vCenter versions). - restClient = rest.NewClient(client.Client) - err = restClient.Login(ctx, vcenterUrl.User) - if err != nil { - return nil, errors.Wrap(err, "failed to login to REST API endpoint") - } - } - - finder := find.NewFinder(client.Client, true) - datacenter, err := finder.DatacenterOrDefault(ctx, config.Datacenter) - if err != nil { - return nil, errors.Wrap(err, "failed to find datacenter") - } - finder.SetDatacenter(datacenter) - - return &VCenterDriver{ - ctx: ctx, - client: client, - restClient: restClient, - finder: finder, - datacenter: datacenter, - }, nil -} diff --git a/datasource/virtual_machine/filters.go b/datasource/virtual_machine/filters.go deleted file mode 100644 index 671fa6bb..00000000 --- a/datasource/virtual_machine/filters.go +++ /dev/null @@ -1,132 +0,0 @@ -package virtual_machine - -import ( - "regexp" - "time" - - "github.com/pkg/errors" - "github.com/vmware/govmomi/object" - "github.com/vmware/govmomi/property" - "github.com/vmware/govmomi/vapi/tags" - "github.com/vmware/govmomi/vim25/mo" -) - -// Filter machines by matching their names against defined regular expression. -func filterByNameRegex(vmList []*object.VirtualMachine, nameRegex string) []*object.VirtualMachine { - re, _ := regexp.Compile(nameRegex) - result := make([]*object.VirtualMachine, 0) - for _, i := range vmList { - if re.MatchString(i.Name()) { - result = append(result, i) - } - } - return result -} - -// Filter machines by template attribute. Only templates will pass the filter. -func filterByTemplate(driver *VCenterDriver, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { - result := make([]*object.VirtualMachine, 0) - for _, i := range vmList { - isTemplate, err := i.IsTemplate(driver.ctx) - if err != nil { - return nil, errors.Wrap(err, "error checking if VM is a tempalte") - } - - if isTemplate { - result = append(result, i) - } - } - return result, nil -} - -// Filter machines by node placement. Only machines that are stored on the defined node will pass the filter. -func filterByNode(driver *VCenterDriver, config Config, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { - pc := property.DefaultCollector(driver.client.Client) - obj, err := driver.finder.HostSystem(driver.ctx, config.Node) - if err != nil { - return nil, errors.Wrap(err, "error finding defined host system") - } - - var host mo.HostSystem - err = pc.RetrieveOne(driver.ctx, obj.Reference(), []string{"vm"}, &host) - if err != nil { - return nil, errors.Wrap(err, "error retrieving properties of host system") - } - - var nodeVms []mo.VirtualMachine - err = pc.Retrieve(driver.ctx, host.Vm, []string{"name"}, &nodeVms) - if err != nil { - return nil, errors.Wrap(err, "failed to get properties for VM") - } - - result := make([]*object.VirtualMachine, 0) - for _, filteredVm := range vmList { - vmName := filteredVm.Name() - for _, nodeVm := range nodeVms { - if vmName == nodeVm.Name { - result = append(result, filteredVm) - } - } - } - - return result, nil -} - -// Filter machines by tags. Tags are stored in the driver as list of flatTag elements. -// Only machines that has all the tags from list will pass the filter. -func filterByTags(driver *VCenterDriver, vmTags []Tag, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { - result := make([]*object.VirtualMachine, 0) - tagMan := tags.NewManager(driver.restClient) - for _, filteredVm := range vmList { - realTagsList, err := tagMan.GetAttachedTags(driver.ctx, filteredVm.Reference()) - if err != nil { - return nil, errors.Wrap(err, "failed to get attached tags for vm") - } - matchedTagsCount := 0 - for _, configTag := range vmTags { - configTagMatched := false - for _, realTag := range realTagsList { - if configTag.Name == realTag.Name { - category, err := tagMan.GetCategory(driver.ctx, realTag.CategoryID) - if err != nil { - return nil, errors.Wrap(err, "failed to get attached category for tag") - } - if configTag.Category == category.Name { - configTagMatched = true - break - } - } - } - if configTagMatched { - matchedTagsCount++ - } else { - // If a single requested tag from config not matched then no need to proceed. - // Fail early. - break - } - } - if matchedTagsCount == len(vmTags) { - result = append(result, filteredVm) - } - } - - return result, nil -} - -func filterByLatest(driver *VCenterDriver, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { - var latestVM *object.VirtualMachine - var latestTimestamp time.Time - for _, elementVM := range vmList { - var vmConfig mo.VirtualMachine - err := elementVM.Properties(driver.ctx, elementVM.Reference(), []string{"config"}, &vmConfig) - if err != nil { - return nil, errors.Wrap(err, "error retrieving config properties of VM") - } - if vmConfig.Config.CreateDate.After(latestTimestamp) { - latestVM = elementVM - latestTimestamp = *vmConfig.Config.CreateDate - } - } - result := []*object.VirtualMachine{latestVM} - return result, nil -} diff --git a/datasource/virtual_machine/data.go b/datasource/virtualmachine/data.go similarity index 52% rename from datasource/virtual_machine/data.go rename to datasource/virtualmachine/data.go index 30e457a9..3847b88a 100644 --- a/datasource/virtual_machine/data.go +++ b/datasource/virtualmachine/data.go @@ -3,65 +3,76 @@ //go:generate packer-sdc struct-markdown //go:generate packer-sdc mapstructure-to-hcl2 -type Config,Tag,DatasourceOutput -package virtual_machine +package virtualmachine import ( + "errors" + "fmt" + "github.com/hashicorp/hcl/v2/hcldec" "github.com/hashicorp/packer-plugin-sdk/common" "github.com/hashicorp/packer-plugin-sdk/hcl2helper" packersdk "github.com/hashicorp/packer-plugin-sdk/packer" "github.com/hashicorp/packer-plugin-sdk/template/config" - vsCommon "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common" - "github.com/pkg/errors" + vsphere "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common" + "github.com/hashicorp/packer-plugin-vsphere/datasource/common/driver" "github.com/zclconf/go-cty/cty" ) -// Example of multiple vm_tags blocks in HCL format: -// ``` +// HCL Example: +// +// ```hcl // -// vm_tags { +// tags { // category = "team" // name = "operations" // } -// vm_tags { -// category = "SLA" +// tags { +// category = "sla" // name = "gold" // } // // ``` type Tag struct { - // Tag with this name must be attached to virtual machine which should pass the Tags Filter. + // Name of the tag added to virtual machine which must pass the `tags` + // filter. Name string `mapstructure:"name" required:"true"` - // Name of the category that contains this tag. Both tag and category must be specified. + // Name of the tag category that contains the tag. + // + // -> **Note:** Both `name` and `category` must be specified in the `tags` + // filter. Category string `mapstructure:"category" required:"true"` } type Config struct { - common.PackerConfig `mapstructure:",squash"` - vsCommon.ConnectConfig `mapstructure:",squash"` + common.PackerConfig `mapstructure:",squash"` + vsphere.ConnectConfig `mapstructure:",squash"` - // Basic filter with glob support (e.g. `nginx_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. + // 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 string `mapstructure:"name"` - // Extended name filter with regular expressions support (e.g. `nginx[-_]basic[0-9]*`). Default is empty. - // The match of the regular expression is checked by substring. Use `^` and `$` to define a full string. - // E.g. the `^[^_]+$` filter will search names without any underscores. - // The expression must use [Go Regex Syntax](https://pkg.go.dev/regexp/syntax). + // 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). NameRegex string `mapstructure:"name_regex"` // Filter to return only objects that are virtual machine templates. - // Defaults to `false` and returns all VMs. + // Defaults to `false` and returns all virtual machines. Template bool `mapstructure:"template"` - // Filter to search virtual machines only on the specified node. - Node string `mapstructure:"node"` - // Filter to return only that virtual machines that have attached all specifies tags. - // Specify one or more `vm_tags` blocks to define list of tags that will make up the filter. - // Should work since vCenter 6.7. To avoid incompatibility, REST client is being - // initialized only when at least one tag has been defined in the config. - VmTags []Tag `mapstructure:"vm_tags"` - // 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. + // Filter to search virtual machines only on the specified ESX host. + Host string `mapstructure:"host"` + // Filter to return only that virtual machines that have attached all + // specifies tags. Specify one or more `tags` blocks to define list of tags + // for the filter. + Tags []Tag `mapstructure:"tags"` + // 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. Latest bool `mapstructure:"latest"` } @@ -98,8 +109,8 @@ func (d *Datasource) Configure(raws ...interface{}) error { if d.config.Password == "" { errs = packersdk.MultiErrorAppend(errs, errors.New("'password' is required")) } - if len(d.config.VmTags) > 0 { - for _, tag := range d.config.VmTags { + if len(d.config.Tags) > 0 { + for _, tag := range d.config.Tags { if tag.Name == "" || tag.Category == "" { errs = packersdk.MultiErrorAppend(errs, errors.New("both name and category are required for tag")) } @@ -118,16 +129,16 @@ func (d *Datasource) OutputSpec() hcldec.ObjectSpec { } func (d *Datasource) Execute() (cty.Value, error) { - driver, err := newDriver(d.config) + dr, err := driver.NewDriver(d.config.ConnectConfig) if err != nil { - return cty.NullVal(cty.EmptyObject), errors.Wrap(err, "failed to initialize driver") + return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to initialize driver: %w", err) } // This is the first level of filters // (the finder with glob will return filtered list or drop an error if found nothing). - filteredVms, err := driver.finder.VirtualMachineList(driver.ctx, d.config.Name) + filteredVms, err := dr.Finder.VirtualMachineList(dr.Ctx, d.config.Name) if err != nil { - return cty.NullVal(cty.EmptyObject), errors.Wrap(err, "failed to retrieve virtual machines list") + return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to retrieve virtual machines list: %w", err) } // Chain of other filters that will be executed only when defined @@ -137,40 +148,40 @@ func (d *Datasource) Execute() (cty.Value, error) { } if len(filteredVms) > 0 && d.config.Template { - filteredVms, err = filterByTemplate(driver, filteredVms) + filteredVms, err = filterByTemplate(dr, filteredVms) if err != nil { - return cty.NullVal(cty.EmptyObject), errors.Wrap(err, "failed to filter by template attribute") + return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to filter by template attribute: %w", err) } } - if len(filteredVms) > 0 && d.config.Node != "" { - filteredVms, err = filterByNode(driver, d.config, filteredVms) + if len(filteredVms) > 0 && d.config.Host != "" { + filteredVms, err = filterByHost(dr, d.config, filteredVms) if err != nil { - return cty.NullVal(cty.EmptyObject), errors.Wrap(err, "failed to filter by node attribute") + return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to filter by host attribute: %w", err) } } - if len(filteredVms) > 0 && d.config.VmTags != nil { - filteredVms, err = filterByTags(driver, d.config.VmTags, filteredVms) + if len(filteredVms) > 0 && d.config.Tags != nil { + filteredVms, err = filterByTags(dr, d.config.Tags, filteredVms) if err != nil { - return cty.NullVal(cty.EmptyObject), errors.Wrap(err, "failed to filter by tags") + return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to filter by tags: %w", err) } } // No VMs passed the filter chain. Nothing to return. if len(filteredVms) == 0 { - return cty.NullVal(cty.EmptyObject), errors.New("not a single VM matches the configured filters") + return cty.NullVal(cty.EmptyObject), errors.New("no virtual machine matches the filters") } if len(filteredVms) > 1 { if d.config.Latest { - filteredVms, err = filterByLatest(driver, filteredVms) + filteredVms, err = filterByLatest(dr, filteredVms) if err != nil { - return cty.NullVal(cty.EmptyObject), errors.Wrap(err, "failed to find the latest VM") + return cty.NullVal(cty.EmptyObject), fmt.Errorf("failed to find the latest virtual machine: %w", err) } } else { // Too many machines passed the filter chain. Cannot decide which machine to return. - return cty.NullVal(cty.EmptyObject), errors.New("multiple VMs match the configured filters") + return cty.NullVal(cty.EmptyObject), errors.New("more than one virtual machine matched the filters") } } diff --git a/datasource/virtual_machine/data.hcl2spec.go b/datasource/virtualmachine/data.hcl2spec.go similarity index 94% rename from datasource/virtual_machine/data.hcl2spec.go rename to datasource/virtualmachine/data.hcl2spec.go index abba4cc6..9ddbfb91 100644 --- a/datasource/virtual_machine/data.hcl2spec.go +++ b/datasource/virtualmachine/data.hcl2spec.go @@ -1,6 +1,6 @@ // Code generated by "packer-sdc mapstructure-to-hcl2"; DO NOT EDIT. -package virtual_machine +package virtualmachine import ( "github.com/hashicorp/hcl/v2/hcldec" @@ -26,8 +26,8 @@ type FlatConfig struct { Name *string `mapstructure:"name" cty:"name" hcl:"name"` NameRegex *string `mapstructure:"name_regex" cty:"name_regex" hcl:"name_regex"` Template *bool `mapstructure:"template" cty:"template" hcl:"template"` - Node *string `mapstructure:"node" cty:"node" hcl:"node"` - VmTags []FlatTag `mapstructure:"vm_tags" cty:"vm_tags" hcl:"vm_tags"` + Host *string `mapstructure:"host" cty:"host" hcl:"host"` + Tags []FlatTag `mapstructure:"tags" cty:"tags" hcl:"tags"` Latest *bool `mapstructure:"latest" cty:"latest" hcl:"latest"` } @@ -59,8 +59,8 @@ func (*FlatConfig) HCL2Spec() map[string]hcldec.Spec { "name": &hcldec.AttrSpec{Name: "name", Type: cty.String, Required: false}, "name_regex": &hcldec.AttrSpec{Name: "name_regex", Type: cty.String, Required: false}, "template": &hcldec.AttrSpec{Name: "template", Type: cty.Bool, Required: false}, - "node": &hcldec.AttrSpec{Name: "node", Type: cty.String, Required: false}, - "vm_tags": &hcldec.BlockListSpec{TypeName: "vm_tags", Nested: hcldec.ObjectSpec((*FlatTag)(nil).HCL2Spec())}, + "host": &hcldec.AttrSpec{Name: "host", Type: cty.String, Required: false}, + "tags": &hcldec.BlockListSpec{TypeName: "tags", Nested: hcldec.ObjectSpec((*FlatTag)(nil).HCL2Spec())}, "latest": &hcldec.AttrSpec{Name: "latest", Type: cty.Bool, Required: false}, } return s diff --git a/datasource/virtual_machine/data_test.go b/datasource/virtualmachine/data_test.go similarity index 85% rename from datasource/virtual_machine/data_test.go rename to datasource/virtualmachine/data_test.go index 97be44d4..8a5ccad7 100644 --- a/datasource/virtual_machine/data_test.go +++ b/datasource/virtualmachine/data_test.go @@ -1,20 +1,23 @@ -package virtual_machine +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package virtualmachine import ( "testing" "time" - vsCommon "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common" + "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/common" "github.com/vmware/govmomi/simulator" - dsTesting "github.com/hashicorp/packer-plugin-vsphere/datasource/virtual_machine/testing" + commonT "github.com/hashicorp/packer-plugin-vsphere/datasource/common/testing" ) func TestExecute(t *testing.T) { - machinesToPrepare := []dsTesting.SimulatedVMConfig{ + machinesToPrepare := []commonT.SimulatedVMConfig{ { Name: "first-vm", - Tags: []dsTesting.Tag{ + Tags: []commonT.Tag{ { Category: "operating-system-class", Name: "Linux", @@ -22,7 +25,7 @@ func TestExecute(t *testing.T) { }, }, { Name: "second-vm", - Tags: []dsTesting.Tag{ + Tags: []commonT.Tag{ { Category: "operating-system-class", Name: "Linux", @@ -39,7 +42,7 @@ func TestExecute(t *testing.T) { Template: true, }, { Name: "machine-three", - Tags: []dsTesting.Tag{ + Tags: []commonT.Tag{ { Category: "operating-system-class", Name: "Linux", @@ -57,7 +60,7 @@ func TestExecute(t *testing.T) { model.Datacenter = 2 model.Machine = 8 - vcSim, err := dsTesting.NewVCenterSimulator(model) + vcSim, err := commonT.NewVCenterSimulator(model) if err != nil { t.Fatalf("error creating vCenter simulator: %s", err) } @@ -69,7 +72,7 @@ func TestExecute(t *testing.T) { } simulatorPassword, _ := vcSim.Server.URL.User.Password() - connectConfig := vsCommon.ConnectConfig{ + connectConfig := common.ConnectConfig{ VCenterServer: vcSim.Server.URL.Host, Username: vcSim.Server.URL.User.Username(), Password: simulatorPassword, @@ -133,19 +136,19 @@ func TestExecute(t *testing.T) { }, }, { - name: "found multiple machines at the node, error", + name: "found multiple machines at the host, error", expectFailure: true, expectVmName: "", config: Config{ - Node: "DC0_H0", + Host: "DC0_H0", }, }, { - name: "cluster node not found, error", + name: "cluster host not found, error", expectFailure: true, expectVmName: "", config: Config{ - Node: "unexpected_node", + Host: "unexpected_host", }, }, { @@ -153,7 +156,7 @@ func TestExecute(t *testing.T) { expectFailure: false, expectVmName: "second-vm", config: Config{ - VmTags: []Tag{ + Tags: []Tag{ { Category: "security-team", Name: "blue", @@ -170,7 +173,7 @@ func TestExecute(t *testing.T) { expectFailure: true, expectVmName: "", config: Config{ - VmTags: []Tag{ + Tags: []Tag{ { Category: "operating-system-class", Name: "Linux", diff --git a/datasource/virtualmachine/filters.go b/datasource/virtualmachine/filters.go new file mode 100644 index 00000000..ec9c1c65 --- /dev/null +++ b/datasource/virtualmachine/filters.go @@ -0,0 +1,137 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package virtualmachine + +import ( + "fmt" + "regexp" + "time" + + "github.com/hashicorp/packer-plugin-vsphere/datasource/common/driver" + "github.com/vmware/govmomi/object" + "github.com/vmware/govmomi/property" + "github.com/vmware/govmomi/vapi/tags" + "github.com/vmware/govmomi/vim25/mo" +) + +// filterByNameRegex filters machines by matching their names against defined regular expression. +func filterByNameRegex(vmList []*object.VirtualMachine, nameRegex string) []*object.VirtualMachine { + re, _ := regexp.Compile(nameRegex) + result := make([]*object.VirtualMachine, 0) + for _, i := range vmList { + if re.MatchString(i.Name()) { + result = append(result, i) + } + } + return result +} + +// filterByTemplate filters machines by template attribute. Only templates will pass the filter. +func filterByTemplate(driver *driver.VCenterDriver, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { + result := make([]*object.VirtualMachine, 0) + for _, i := range vmList { + isTemplate, err := i.IsTemplate(driver.Ctx) + if err != nil { + return nil, fmt.Errorf("error checking if virtual machine is a template: %w", err) + } + + if isTemplate { + result = append(result, i) + } + } + return result, nil +} + +// filterByHost filters machines by ESX host placement. +// Only machines that are stored on the defined host will pass the filter. +func filterByHost(driver *driver.VCenterDriver, config Config, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { + pc := property.DefaultCollector(driver.Client.Client) + obj, err := driver.Finder.HostSystem(driver.Ctx, config.Host) + if err != nil { + return nil, fmt.Errorf("error finding defined host system: %w", err) + } + + var host mo.HostSystem + err = pc.RetrieveOne(driver.Ctx, obj.Reference(), []string{"vm"}, &host) + if err != nil { + return nil, fmt.Errorf("error retrieving properties of host system: %w", err) + } + + var hostVms []mo.VirtualMachine + err = pc.Retrieve(driver.Ctx, host.Vm, []string{"name"}, &hostVms) + if err != nil { + return nil, fmt.Errorf("failed to get properties for the virtual machine: %w", err) + } + + result := make([]*object.VirtualMachine, 0) + for _, filteredVm := range vmList { + vmName := filteredVm.Name() + for _, hostVm := range hostVms { + if vmName == hostVm.Name { + result = append(result, filteredVm) + } + } + } + + return result, nil +} + +// filterByTags filters machines by tags. Only machines that has all the tags from list will pass the filter. +func filterByTags(driver *driver.VCenterDriver, vmTags []Tag, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { + result := make([]*object.VirtualMachine, 0) + tagMan := tags.NewManager(driver.RestClient) + for _, filteredVm := range vmList { + realTagsList, err := tagMan.GetAttachedTags(driver.Ctx, filteredVm.Reference()) + if err != nil { + return nil, fmt.Errorf("failed return tags for the virtual machine: %w", err) + } + matchedTagsCount := 0 + for _, configTag := range vmTags { + configTagMatched := false + for _, realTag := range realTagsList { + if configTag.Name == realTag.Name { + category, err := tagMan.GetCategory(driver.Ctx, realTag.CategoryID) + if err != nil { + return nil, fmt.Errorf("failed to return tag category for tag: %w", err) + } + if configTag.Category == category.Name { + configTagMatched = true + break + } + } + } + if configTagMatched { + matchedTagsCount++ + } else { + // If a single requested tag from config not matched then no need to proceed. + // Fail early. + break + } + } + if matchedTagsCount == len(vmTags) { + result = append(result, filteredVm) + } + } + + return result, nil +} + +// filterByLatest filters machines by creation date. This filter returns list with one element. +func filterByLatest(driver *driver.VCenterDriver, vmList []*object.VirtualMachine) ([]*object.VirtualMachine, error) { + var latestVM *object.VirtualMachine + var latestTimestamp time.Time + for _, elementVM := range vmList { + var vmConfig mo.VirtualMachine + err := elementVM.Properties(driver.Ctx, elementVM.Reference(), []string{"config"}, &vmConfig) + if err != nil { + return nil, fmt.Errorf("error retrieving config properties for the virtual machine: %w", err) + } + if vmConfig.Config.CreateDate.After(latestTimestamp) { + latestVM = elementVM + latestTimestamp = *vmConfig.Config.CreateDate + } + } + result := []*object.VirtualMachine{latestVM} + return result, nil +} diff --git a/docs-partials/datasource/virtual_machine/Config-not-required.mdx b/docs-partials/datasource/virtual_machine/Config-not-required.mdx deleted file mode 100644 index 7c3973d1..00000000 --- a/docs-partials/datasource/virtual_machine/Config-not-required.mdx +++ /dev/null @@ -1,26 +0,0 @@ - - -- `name` (string) - Basic filter with glob support (e.g. `nginx_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. `nginx[-_]basic[0-9]*`). Default is empty. - The match of the regular expression is checked by substring. Use `^` and `$` to define a full string. - E.g. 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 VMs. - -- `node` (string) - Filter to search virtual machines only on the specified node. - -- `vm_tags` ([]Tag) - Filter to return only that virtual machines that have attached all specifies tags. - Specify one or more `vm_tags` blocks to define list of tags that will make up the filter. - Should work since vCenter 6.7. To avoid incompatibility, REST client is being - initialized only when at least one tag has been defined in the config. - -- `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. - - diff --git a/docs-partials/datasource/virtual_machine/Tag-required.mdx b/docs-partials/datasource/virtual_machine/Tag-required.mdx deleted file mode 100644 index 65f82b0e..00000000 --- a/docs-partials/datasource/virtual_machine/Tag-required.mdx +++ /dev/null @@ -1,7 +0,0 @@ - - -- `name` (string) - Tag with this name must be attached to virtual machine which should pass the Tags Filter. - -- `category` (string) - Name of the category that contains this tag. Both tag and category must be specified. - - diff --git a/docs-partials/datasource/virtualmachine/Config-not-required.mdx b/docs-partials/datasource/virtualmachine/Config-not-required.mdx new file mode 100644 index 00000000..9155cd05 --- /dev/null +++ b/docs-partials/datasource/virtualmachine/Config-not-required.mdx @@ -0,0 +1,29 @@ + + +- `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. + +- `tags` ([]Tag) - Filter to return only that virtual machines that have attached all + specifies tags. Specify one or more `tags` blocks to define list of tags + for the filter. + +- `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. + + diff --git a/docs-partials/datasource/virtual_machine/DatasourceOutput.mdx b/docs-partials/datasource/virtualmachine/DatasourceOutput.mdx similarity index 60% rename from docs-partials/datasource/virtual_machine/DatasourceOutput.mdx rename to docs-partials/datasource/virtualmachine/DatasourceOutput.mdx index 30c8a095..ff300244 100644 --- a/docs-partials/datasource/virtual_machine/DatasourceOutput.mdx +++ b/docs-partials/datasource/virtualmachine/DatasourceOutput.mdx @@ -1,5 +1,5 @@ - + - `vm_name` (string) - Name of the found virtual machine. - + diff --git a/docs-partials/datasource/virtualmachine/Tag-required.mdx b/docs-partials/datasource/virtualmachine/Tag-required.mdx new file mode 100644 index 00000000..551e1fa2 --- /dev/null +++ b/docs-partials/datasource/virtualmachine/Tag-required.mdx @@ -0,0 +1,11 @@ + + +- `name` (string) - Name of the tag added to virtual machine which must pass the `tags` + filter. + +- `category` (string) - Name of the tag category that contains the tag. + + -> **Note:** Both `name` and `category` must be specified in the `tags` + filter. + + diff --git a/docs-partials/datasource/virtual_machine/Tag.mdx b/docs-partials/datasource/virtualmachine/Tag.mdx similarity index 51% rename from docs-partials/datasource/virtual_machine/Tag.mdx rename to docs-partials/datasource/virtualmachine/Tag.mdx index cfe63d63..fd7a7756 100644 --- a/docs-partials/datasource/virtual_machine/Tag.mdx +++ b/docs-partials/datasource/virtualmachine/Tag.mdx @@ -1,17 +1,18 @@ - + -Example of multiple vm_tags blocks in HCL format: -``` +HCL Example: + +```hcl - vm_tags { + tags { category = "team" name = "operations" } - vm_tags { - category = "SLA" + tags { + category = "sla" name = "gold" } ``` - + diff --git a/docs/README.md b/docs/README.md index d6da08e1..20127e6f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -51,9 +51,8 @@ packer plugins install github.com/hashicorp/vsphere #### Data Sources -- [vsphere-virtual_machine](/packer/integrations/hashicorp/vsphere/latest/components/data-source/vsphere-virtual_machine) - - This datasource returns name of existing virtual machine that matches all defined filters to use - it as a builder source for `vsphere-clone`. +- [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 diff --git a/docs/datasources/virtual_machine.mdx b/docs/datasources/virtual_machine.mdx deleted file mode 100644 index d493c4df..00000000 --- a/docs/datasources/virtual_machine.mdx +++ /dev/null @@ -1,92 +0,0 @@ ---- -modeline: | - vim: set ft=pandoc: -description: | - This datasource is able to get information about existing virtual machines from vSphere - and return name of one virtual machine that matches all specified filters. This virtual - machine can later be used in the vSphere Clone builder to select template. -page_title: vSphere VM - Datasources -sidebar_title: Virtual Machine ---- - -# VMware vSphere Virtual Machine Datasource - -Type: `vsphere-virtual_machine` -Artifact BuilderId: `vsphere.virtual_machine` - -This datasource is able to get information about existing virtual machines from vSphere -and return name of one virtual machine that matches all specified filters. This virtual -machine can later be used in the vSphere Clone builder to select template. - -## Configuration Reference - -### Filters Configuration - -**Optional:** - -@include 'datasource/virtual_machine/Config-not-required.mdx' - -### Tags Filter Configuration - -@include 'datasource/virtual_machine/Tag.mdx' - -**Required:** - -@include 'datasource/virtual_machine/Tag-required.mdx' - -### Connection Configuration - -**Optional:** - -@include 'builder/vsphere/common/ConnectConfig-not-required.mdx' - -## Output - -@include 'datasource/virtual_machine/DatasourceOutput.mdx' - -## Example Usage - -This is a very basic example that connects to vSphere cluster and tries to search -the latest virtual machine that matches all filters. The machine name is then printed -to console as output variable. -```hcl -data "vsphere-virtual_machine" "default" { - vcenter_server = "vcenter.example.org" - insecure_connection = true - username = "administrator@example.org" - password = "St4ongPa$$w0rd" - datacenter = "AZ1" - latest = true - vm_tags { - category = "team" - name = "operations" - } - vm_tags { - category = "SLA" - name = "gold" - } - -} - -locals { - vm_name = data.vsphere-virtual_machine.default.vm_name -} - -source "null" "basic-example" { - communicator = "none" -} - -build { - sources = [ - "source.null.basic-example" - ] - - provisioner "shell-local" { - inline = [ - "echo vm_name: ${local.vm_name}", - ] - } -} - - -``` diff --git a/docs/datasources/virtualmachine.mdx b/docs/datasources/virtualmachine.mdx new file mode 100644 index 00000000..b616f7a4 --- /dev/null +++ b/docs/datasources/virtualmachine.mdx @@ -0,0 +1,89 @@ +--- +modeline: | + vim: set ft=pandoc: +description: | + 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. +page_title: vSphere Virtual Machine - Data Source +sidebar_title: vSphere Virtual Machine +--- + +# Virtual Machine Data Source + +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:** + +@include 'datasource/virtualmachine/Config-not-required.mdx' + +### Tags Filter Configuration + +@include 'datasource/virtualmachine/Tag.mdx' + +**Required:** + +@include 'datasource/virtualmachine/Tag-required.mdx' + +### Connection Configuration + +**Optional:** + +@include 'builder/vsphere/common/ConnectConfig-not-required.mdx' + +## Output + +@include 'datasource/virtualmachine/DatasourceOutput.mdx' + +## 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 = "administrator@vsphere.local" + 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}", + ] + } +} +``` diff --git a/main.go b/main.go index 121f768e..ddfbe2d6 100644 --- a/main.go +++ b/main.go @@ -12,7 +12,7 @@ import ( "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/clone" "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/iso" "github.com/hashicorp/packer-plugin-vsphere/builder/vsphere/supervisor" - "github.com/hashicorp/packer-plugin-vsphere/datasource/virtual_machine" + "github.com/hashicorp/packer-plugin-vsphere/datasource/virtualmachine" "github.com/hashicorp/packer-plugin-vsphere/post-processor/vsphere" vsphereTemplate "github.com/hashicorp/packer-plugin-vsphere/post-processor/vsphere-template" "github.com/hashicorp/packer-plugin-vsphere/version" @@ -23,7 +23,7 @@ func main() { pps.RegisterBuilder("iso", new(iso.Builder)) pps.RegisterBuilder("clone", new(clone.Builder)) pps.RegisterBuilder("supervisor", new(supervisor.Builder)) - pps.RegisterDatasource("virtual_machine", new(virtual_machine.Datasource)) + pps.RegisterDatasource("virtualmachine", new(virtualmachine.Datasource)) pps.RegisterPostProcessor(plugin.DEFAULT_NAME, new(vsphere.PostProcessor)) pps.RegisterPostProcessor("template", new(vsphereTemplate.PostProcessor)) pps.SetVersion(version.PluginVersion)