Skip to content
Closed
Show file tree
Hide file tree
Changes from 4 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
3 changes: 2 additions & 1 deletion docs/data-sources/network.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ data "vsphere_network" "my_port_group" {

The following arguments are supported:

* `name` - (Required) The name of the network. This can be a name or path.
* `name` - (Optional) The name of the network. This can be a name or path. Exactly one of `name` or `vlan_id` must be set.
* `vlan_id` - (Optional) The VLAN ID of the network. Exactly one of `name` or `vlan_id` must be set. This is used to look up a Distributed Virtual Port Group by its VLAN ID.
Comment thread
akli-ime marked this conversation as resolved.
Outdated
Comment thread
akli-ime marked this conversation as resolved.
Outdated
* `datacenter_id` - (Optional) The
[managed object reference ID][docs-about-morefs] of the datacenter the network
is located in. This can be omitted if the search path used in `name` is an
Expand Down
90 changes: 86 additions & 4 deletions vsphere/data_source_vsphere_network.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
package vsphere

import (
"context"
"errors"
"fmt"
"time"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"github.com/vmware/terraform-provider-vsphere/vsphere/internal/helper/network"
)

Expand All @@ -28,9 +32,16 @@ func dataSourceVSphereNetwork() *schema.Resource {

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "The name or path of the network.",
Required: true,
Type: schema.TypeString,
Description: "The name or path of the network.",
Optional: true,
ExactlyOneOf: []string{"name", "vlan_id"},
},
"vlan_id": {
Type: schema.TypeInt,
Optional: true,
Description: "The vlan id of the network.",
Comment thread
akli-ime marked this conversation as resolved.
Outdated
Comment thread
akli-ime marked this conversation as resolved.
Outdated
ExactlyOneOf: []string{"name", "vlan_id"},
},
"datacenter_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -88,9 +99,23 @@ func dataSourceVSphereNetwork() *schema.Resource {
}
}

type distributedPortGroupStructure struct {
VLANID int
Comment thread
akli-ime marked this conversation as resolved.
}

func expandDistributedPortGroupVlan(d *schema.ResourceData) *distributedPortGroupStructure {
if v, ok := d.GetOk("vlan_id"); ok {
return &distributedPortGroupStructure{
VLANID: v.(int),
}
}
return nil
}
Comment thread
akli-ime marked this conversation as resolved.

func dataSourceVSphereNetworkRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*Client).vimClient

vlan := expandDistributedPortGroupVlan(d)
name := d.Get("name").(string)
dvSwitchUUID := d.Get("distributed_virtual_switch_uuid").(string)
vpcID := d.Get("vpc_id").(string)
Expand Down Expand Up @@ -145,6 +170,59 @@ func dataSourceVSphereNetworkRead(d *schema.ResourceData, meta interface{}) erro
}
return net, waitForNetworkCompleted, nil
}
// Handle VLAN-based lookup (Distributed Virtual Port Groups only)
if vlan != nil {
ctx := context.Background()
Comment thread
akli-ime marked this conversation as resolved.
finder := find.NewFinder(vimClient, false)
if dc != nil {
finder.SetDatacenter(dc)
}

nets, err := finder.NetworkList(ctx, "*")
if err != nil {
return struct{}{}, waitForNetworkError, err
}

var matches []object.NetworkReference

for _, n := range nets {
dvpg, ok := n.(*object.DistributedVirtualPortgroup)
if !ok {
continue
}
Comment on lines +189 to +192

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Standard networks can be assigned with a VLAN ID too

@akli-ime akli-ime Feb 9, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The whole point of this PR is to allow lookup by VLAN ID without requiring a name. Names may not always be known or consistent — for example, a portgroup might have one name in vSphere and a completely different one in Infoblox (IP address management, IPAM). Using only the VLAN ID provides a single identifier that can be used to bind resources across both technologies, so making the name mandatory would defeat the purpose. This provides an alternative for identifying distributed portgroups by VLAN.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad, ExactlyOneOf will mean that name is still necessary as long as vlan_id is not set.

I'm assuming that your infrastructure has a one-to-one mapping of distributed portgroups and VLAN IDs.
This is not how it works in vSphere though. Every managed resource has a single unique identifier and that is its ID.
Names can be treated as unique too because the VIM API enforces this rule and we use it for our datasources.
Trying to identify a network by its VLAN ID will begin to fail as soon as a second portgroup is assigned to the same VLAN.

Let me give some examples how network names are unique

  1. Standard networks

Every host has a "VM Network" created by default. All of these actually point to the same managed object.
Even if you create a new standard network on several hosts using the same name, these networks would be backed by a single managed object.

  1. Opaque networks

These are externally created by NSX. The name is unique in both systems and is synchronized between NSX-T and vCenter.

  1. Distributed portgroups

Every distributed portgroup is a managed object and is stored in the same network folder as its distributed switch.
To the VIM API a VDS and a distributed portgroup are equal entities (the UI renders them as nested but that's only visual)

vCenter does not allow more than 1 distributed portgroup to exist under the same name even across different datacenters. What it does allow is an arbitrary number of distributed portgroups to be assigned to the same VLAN

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From our perspective, it is important to give users the flexibility to decide how they want to identify and select their target resources when using the data source.

At the moment, the provider already offers mechanisms to help narrow down the selection, including support for wildcard (*) name patterns. When multiple networks match, a clear error message is returned to prompt the user to refine their criteria.

Here is a concrete example I just tested with Terraform, using a wildcard name pattern:

data "vsphere_network" "network" {
  name          = "V3*"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}

Returned error:

Error: multiple networks found with the name 'V3*'. Please specify a filter to narrow down the results

This approach helps avoid ambiguity and encourages users to be explicit about their intent, which is especially important in environments where multiple networks may share similar characteristics.

In this context, leaving the responsibility of precise resource identification to the user provides greater flexibility and aligns well with the variety of infrastructure designs found in real-world deployments.


var pg mo.DistributedVirtualPortgroup
if err := dvpg.Properties(ctx, dvpg.Reference(), []string{"config.defaultPortConfig"}, &pg); err != nil {
return struct{}{}, waitForNetworkError, err
}
Comment thread
akli-ime marked this conversation as resolved.

cfg, ok := pg.Config.DefaultPortConfig.(*types.VMwareDVSPortSetting)
if !ok || cfg.Vlan == nil {
continue
}

vlanSpec, ok := cfg.Vlan.(*types.VmwareDistributedVirtualSwitchVlanIdSpec)
if !ok {
continue
}

if int(vlanSpec.VlanId) == vlan.VLANID {
matches = append(matches, dvpg)
}
}

if len(matches) == 0 {
return struct{}{}, waitForNetworkPending, nil
}

if len(matches) > 1 {
return struct{}{}, waitForNetworkError,
fmt.Errorf("multiple distributed port groups found with vlan_id %d", vlan.VLANID)
}

return matches[0], waitForNetworkCompleted, nil
}
Comment thread
akli-ime marked this conversation as resolved.

// Handle standard switch port group
net, err = network.FromName(vimClient, name, dc, filters) // Pass the *vim25.Client
if err != nil {
Expand Down Expand Up @@ -183,7 +261,11 @@ func dataSourceVSphereNetworkRead(d *schema.ResourceData, meta interface{}) erro
}

if state == waitForNetworkPending {
err = fmt.Errorf("network %s not found", name)
if vlan != nil {
err = fmt.Errorf("network with vlan_id %d not found", vlan.VLANID)
} else {
err = fmt.Errorf("network %s not found", name)
}
}

if err != nil {
Expand Down
Loading