Category Archives: Terraform

Different Tools; Same Result – vSphere Tags

Following the last blog post on create vSphere Port Groups, let’s take a look at creating Tags and Tag Categories.

Let’s first look at the process via the GUI, in this case, the vSphere Client. (Based on vSphere 7.0.3c)

vSphere Client

I wont go into to much detail here as this information is readily available, but here is a brief run through.

After logging into the vSphere Client, select the menu followed by Tags & Custom Attributes.

You the have the option to select either Tags or Categories, followed by the ‘New’ option.

For Categories you need to provide the Category name, optional description, the cardinality (single or multiple) and select the objects that can have this tag associated with it.

Then with Tags, you need to provide the name, optional description and the category the tag will be part of.

Now this may be ok for one or two, but if you need to create in bulk, this will take a while! Lets look as some alternatives.

PowerShell

Firstly, PowerShell, specifically the VMware PowerCLI PowerShell module. Here are examples of the using the cmdlets New-TagCategory and New-Tag to create the same thing we did in the vSphere Client.

#Tag Categories
New-TagCategory -Name "costcentre" -Description "Created with PowerCLI" -Cardinality "MULTIPLE" -EntityType "VirtualMachine", "Datastore"
#Tags
New-Tag -Name "0001" -Category "costcentre" -Description "Created with PowerCLI"

Below is the output from PowerShell after running the script above:

Name                                     Cardinality Description
----                                     ----------- -----------
costcentre                               Multiple    Created with PowerCLI

Name                           Category                       Description
----                           --------                       -----------
0001                           costcentre                     Created with PowerCLI

Now this isn’t much quicker than doing it in the vSphere Client so here is one way to create in bulk.

Here is a custom array with multiple categories and the additional values needed to create a Category.

$TagCategories = @(
    [pscustomobject]@{Name = "costcentre"; Cardinality = "MULTIPLE"; EntityType = "VirtualMachine", "Datastore" }
    [pscustomobject]@{Name = "environment"; Cardinality = "SINGLE"; EntityType = "VirtualMachine", "Datastore" }
    [pscustomobject]@{Name = "nsx-tier"; Cardinality = "MULTIPLE"; EntityType = "VirtualMachine" }
)
foreach ($Category in $TagCategories) {
    New-TagCategory -Name $Category.Name -Cardinality $Category.Cardinality -EntityType $Category.EntityType -Description "Created with PowerCLI"
}

Here is the output:

Name                                     Cardinality Description
----                                     ----------- -----------
costcentre                               Multiple    Created with PowerCLI
environment                              Single      Created with PowerCLI
nsx-tier                                 Multiple    Created with PowerCLI

And now the same principal but with Tags.

$Tags = @(
    [pscustomobject]@{Name = "0001"; Category = "costcentre" }
    [pscustomobject]@{Name = "0002"; Category = "costcentre" }
    [pscustomobject]@{Name = "0003"; Category = "costcentre" }
    [pscustomobject]@{Name = "0004"; Category = "costcentre" }
    [pscustomobject]@{Name = "environment"; Category = "environment" }
    [pscustomobject]@{Name = "production"; Category = "environment" }
    [pscustomobject]@{Name = "pre-production"; Category = "environment" }
    [pscustomobject]@{Name = "test"; Category = "environment" }
    [pscustomobject]@{Name = "development"; Category = "environment" }
    [pscustomobject]@{Name = "web"; Category = "nsx-tier" }
    [pscustomobject]@{Name = "app"; Category = "nsx-tier" }
    [pscustomobject]@{Name = "data"; Category = "nsx-tier" }
)
foreach ($Tag in $Tags) {
    New-Tag -Name $Tag.Name -Category $Tag.Category -Description "Created with PowerCLI"
}

Output:

Name                           Category                       Description
----                           --------                       -----------
0001                           costcentre                     Created with PowerCLI
0002                           costcentre                     Created with PowerCLI
0003                           costcentre                     Created with PowerCLI
0004                           costcentre                     Created with PowerCLI
environment                    environment                    Created with PowerCLI
production                     environment                    Created with PowerCLI
pre-production                 environment                    Created with PowerCLI
test                           environment                    Created with PowerCLI
development                    environment                    Created with PowerCLI
web                            nsx-tier                       Created with PowerCLI
app                            nsx-tier                       Created with PowerCLI
data                           nsx-tier                       Created with PowerCLI

That is just one way to create multiple Categories and Tags. You could take this information from a CSV file using the ‘Get-Content’ cmdlet as an alternative to creating the array manually.

Terraform

Now let’s take a look at using Terraform to achieve the same result. Terraform is an infrastructure and code tool used to manage infrastructure in the form of configuration files and state:

#Providers
provider "vsphere" {
  vsphere_server       = "vcsa-fqdn"
  user                 = "domain\\user"
  password             = "password"
  allow_unverified_ssl = false
}
#Tag categories
resource "vsphere_tag_category" "costcentre" {
  name        = "costcentre"
  description = "Managed by Terraform"
  cardinality = "MULTIPLE"
  associable_types = [
    "VirtualMachine",
    "Datastore",
  ]
}
resource "vsphere_tag_category" "environment" {
  name        = "environment"
  description = "Managed by Terraform"
  cardinality = "SINGLE"
  associable_types = [
    "VirtualMachine",
    "Datastore",
  ]
}
resource "vsphere_tag_category" "nsx-tier" {
  name        = "nsx-tier"
  description = "Managed by Terraform"
  cardinality = "MULTIPLE"
  associable_types = [
    "VirtualMachine"
  ]
}
#Tags
#Local values
locals {
  costcentre_tags  = ["0001", "0002", "0003", "0004"]
  environment_tags = ["production", "pre-production", "test", "development"]
  nsx_tier_tags    = ["web", "app", "data"]
}
#Resources
resource "vsphere_tag" "costcentre-tags" {
  for_each    = toset(local.costcentre_tags)
  name        = each.key
  category_id = vsphere_tag_category.costcentre.id
  description = "Managed by Terraform"
}
resource "vsphere_tag" "environment-tags" {
  for_each    = toset(local.environment_tags)
  name        = each.key
  category_id = vsphere_tag_category.environment.id
  description = "Managed by Terraform"
}
resource "vsphere_tag" "nsx-tier-tags" {
  for_each    = toset(local.nsx_tier_tags)
  name        = each.key
  category_id = vsphere_tag_category.nsx-tier.id
  description = "Managed by Terraform"
}

Lets break this down.

First we are specifying which terraform provider we want to use, this will be the vSphere provider in this case. We are then providing some parameters for the provider to connect to your vCenter instance; VCSA FQDN and credentials. You would want make use of variables for this data, but for this blog I am keeping it simple.

provider "vsphere" {
  vsphere_server       = "vcsa-fqdn"
  user                 = "domain\\user"
  password             = "password"
  allow_unverified_ssl = false
}

We then have three vsphere_tag_category resource blocks, one for each of the categories we want to create. This again provides values for cardinality and associable types like we did in PowerShell.

resource "vsphere_tag_category" "costcentre" {
  name        = "costcentre"
  description = "Managed by Terraform"
  cardinality = "MULTIPLE"
  associable_types = [
    "VirtualMachine",
    "Datastore",
  ]
}
resource "vsphere_tag_category" "environment" {
  name        = "environment"
  description = "Managed by Terraform"
  cardinality = "SINGLE"
  associable_types = [
    "VirtualMachine",
    "Datastore",
  ]
}
resource "vsphere_tag_category" "nsx-tier" {
  name        = "nsx-tier"
  description = "Managed by Terraform"
  cardinality = "MULTIPLE"
  associable_types = [
    "VirtualMachine"
  ]
}

Next we are going to create the tags, but I am going to use a set of local variables to then pass into the three vsphere_tag resource blocks to reduce the amount of repeating code.

Here are the local variables. This is similar to creating the array we did in PowerShell.

locals {
  costcentre_tags  = ["0001", "0002", "0003", "0004"]
  environment_tags = ["production", "pre-production", "test", "development"]
  nsx_tier_tags    = ["web", "app", "data"]
}

And then the resource blocks, notice the for_each parameter. For each Tag Category, it will cycle through each value in the locals array for each category. This is just like we did in PowerShell foreach function earlier.

resource "vsphere_tag" "costcentre-tags" {
  for_each    = toset(local.costcentre_tags)
  name        = each.key
  category_id = vsphere_tag_category.costcentre.id
  description = "Managed by Terraform"
}
resource "vsphere_tag" "environment-tags" {
  for_each    = toset(local.environment_tags)
  name        = each.key
  category_id = vsphere_tag_category.environment.id
  description = "Managed by Terraform"
}
resource "vsphere_tag" "nsx-tier-tags" {
  for_each    = toset(local.nsx_tier_tags)
  name        = each.key
  category_id = vsphere_tag_category.nsx-tier.id
  description = "Managed by Terraform"
}

Now when we run ‘terraform apply’ from the command line to apply for code, this is the output:

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create
    
Terraform will perform the following actions:

  # vsphere_tag.costcentre-tags["0001"] will be created
  + resource "vsphere_tag" "costcentre-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "0001"
    }

  # vsphere_tag.costcentre-tags["0002"] will be created
  + resource "vsphere_tag" "costcentre-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "0002"
    }

  # vsphere_tag.costcentre-tags["0003"] will be created
  + resource "vsphere_tag" "costcentre-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "0003"
    }

  # vsphere_tag.costcentre-tags["0004"] will be created
  + resource "vsphere_tag" "costcentre-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "0004"
    }

  # vsphere_tag.environment-tags["development"] will be created
  + resource "vsphere_tag" "environment-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "development"
    }

  # vsphere_tag.environment-tags["pre-production"] will be created
  + resource "vsphere_tag" "environment-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "pre-production"
    }

  # vsphere_tag.environment-tags["production"] will be created
  + resource "vsphere_tag" "environment-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "production"
    }

  # vsphere_tag.environment-tags["test"] will be created
  + resource "vsphere_tag" "environment-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "test"
    }

  # vsphere_tag.nsx-tier-tags["app"] will be created
  + resource "vsphere_tag" "nsx-tier-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "app"
    }

  # vsphere_tag.nsx-tier-tags["data"] will be created
  + resource "vsphere_tag" "nsx-tier-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "data"
    }

  # vsphere_tag.nsx-tier-tags["web"] will be created
  + resource "vsphere_tag" "nsx-tier-tags" {
      + category_id = (known after apply)
      + description = "Managed by Terraform"
      + id          = (known after apply)
      + name        = "web"
    }

  # vsphere_tag_category.costcentre will be created
  + resource "vsphere_tag_category" "costcentre" {
      + associable_types = [
          + "Datastore",
          + "VirtualMachine",
        ]
      + cardinality      = "MULTIPLE"
      + description      = "Managed by Terraform"
      + id               = (known after apply)
      + name             = "costcentre"
    }

  # vsphere_tag_category.environment will be created
  + resource "vsphere_tag_category" "environment" {
      + associable_types = [
          + "Datastore",
          + "VirtualMachine",
        ]
vsphere_tag.environment-tags["production"]: Creating...
vsphere_tag.environment-tags["pre-production"]: Creating...
vsphere_tag_category.nsx-tier: Creation complete after 0s [id=urn:vmomi:InventoryServiceCategory:20a2167a-b0f8-4a60-9d29-6c7ca57711ef:GLOBAL]
vsphere_tag.nsx-tier-tags["data"]: Creating...
vsphere_tag.nsx-tier-tags["app"]: Creating...
vsphere_tag.nsx-tier-tags["web"]: Creating...
vsphere_tag_category.costcentre: Creation complete after 0s [id=urn:vmomi:InventoryServiceCategory:28a909f5-ee41-4d94-b228-b5e96e09284e:GLOBAL]
vsphere_tag.costcentre-tags["0004"]: Creating...
vsphere_tag.costcentre-tags["0002"]: Creating...
vsphere_tag.costcentre-tags["0003"]: Creating...
vsphere_tag.environment-tags["development"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:5b63e350-ef6e-4bbc-a633-09c9047b327b:GLOBAL]
vsphere_tag.costcentre-tags["0001"]: Creating...
vsphere_tag.environment-tags["pre-production"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:e2a8737c-e42a-4c6f-b9a8-716a1681d0c0:GLOBAL]
vsphere_tag.nsx-tier-tags["data"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:b9d3394d-388c-4018-b7b2-9e4d3da8287b:GLOBAL]
vsphere_tag.costcentre-tags["0002"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:8a482528-5d67-40e9-86cb-4dbf566f85ac:GLOBAL]
vsphere_tag.nsx-tier-tags["web"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:5a325904-4dfd-46ac-b0db-37fd6fda1533:GLOBAL]
vsphere_tag.environment-tags["production"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:89c609b9-7f90-457d-9f71-0bd0b7cc667d:GLOBAL]
vsphere_tag.nsx-tier-tags["app"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:45c2dd0e-533a-4917-82be-987d3245137a:GLOBAL]
vsphere_tag.costcentre-tags["0004"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:230db56e-7352-4e14-ba63-0ad4b4c0ba18:GLOBAL]
vsphere_tag.environment-tags["test"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:ebcf1809-8cae-4cb2-a5fa-82a492e54227:GLOBAL]
vsphere_tag.costcentre-tags["0001"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:e4649ad2-08d2-4dcd-aabf-4e2d74f93a36:GLOBAL]
vsphere_tag.costcentre-tags["0003"]: Creation complete after 0s [id=urn:vmomi:InventoryServiceTag:18de9eca-456c-4539-ad6c-19d625ac5be7:GLOBAL]

Apply complete! Resources: 14 added, 0 changed, 0 destroyed.

For more information on the vSphere provider from Terraform, check out this link.

I hope this has given you some idea’s on how you can perhaps leverage other options beside the GUI, especially when looking to build or configure in bulk. All the code in this blog can be found on my GitHub here.

Thanks for reading!

Different Tools; Same Result – vSphere Distributed Port Groups

As technology moves forward, more and more ways to achieve your goal become available. Many people still rely on the good old trusty GUI to achieve their goal, I know I do at times. Is this because it’s quicker, more comfortable or familiar? Or perhaps because they don’t realise there are other options out there!?

This blog post will be one of many, where I highlight some of the options available for completing various technical tasks or configurations, in the hope it can provide additional options or tools for consideration.

To kick off, let’s take a look at a common example for a vSphere Administrator, creating Port Groups on a Distributed Switch.

vSphere Client

So let’s first look at the process via the GUI, in this case, the vSphere Client. I wont go into too much detail on the steps involved, as it is a well documented process, but the screenshots are below:

Repeat for the remaining Port Groups and you will be left with the finished article.

And there we have it, three Port Groups on a distributed Switch. Now, imagine doing this for 10’s or 100’s of Port Groups? It’s going to be slow and painful, so let’s look at some other options.

PowerShell

Firstly, PowerShell, specifically the VMware PowerCLI PowerShell module. Here is an example script that will create the same three Port Groups that we did using the GUI:

$vDSName = "vDS-Workload-Networks"
$Ports = "8"
$LoadBalancing = "LoadBalanceLoadBased" 
$ActiveUP = "Uplink 1", "Uplink 2"

$vdpgs = @(
    [pscustomobject]@{PG = 'dvPG-Guest-VM-1'; VLANID = '20'}
    [pscustomobject]@{PG = 'dvPG-Guest-VM-2'; VLANID = '21'}
    [pscustomobject]@{PG = 'dvPG-Secure-VM-1'; VLANID = '25'}
)

#Create Distributed Virtual Port Group.
ForEach ($vdpg in $vdpgs) {
    Get-VDSwitch -Name $vDSName | New-VDPortGroup -Name $VDPG.PG -VLanId $VDPG.VLANID -NumPorts $Ports
    #Set Load Balancing option
    Get-VDswitch -Name $vDSName | Get-VDPortgroup $VDPG.PG | Get-VDUplinkTeamingPolicy | Set-VDUplinkTeamingPolicy -LoadBalancingPolicy $LoadBalancing -ActiveUplinkPort $ActiveUP
}

So lets break down this code. Firstly we are defining some variables;

  • $vDSName – This is the name of an existing virtual distributed switch in which you will be creating your Port Groups.
  • $Ports – This defines the number of ports the Port Group will be initially configured with. (By default 128 ports are created, there is nothing wrong with using the default, see the note further down as to why I have specified 8.)
  • $LoadBalancing – This is the load balancing policy I wish to set for the Port Group. Available options are:LoadBalanceLoadBased, LoadBalanceIP, LoadBalanceSrcMac, LoadBalanceSrcId, ExplicitFailover. This can be adjusted as required.
  • $ActiveUP – This variable defines the uplinks you wish to set as active for the Port Group. (If you want to add standby uplinks, you could add this parameter in too)
  • $VDPGS – Finally, this is an array containing both the name and VLAN ID for each Port Group.

Now we have our input information in variables, we move onto the next two lines of code. These are within a ‘ForEach Loop’. This will take each entry within an array and run a block of code against it. In this case, each Port Group we wish to create.

So for each entry in the array, ‘Get-VDswitch -Name $vDSName‘ gets the existing Virtual Distributed Switch based on the variable and then pipes (‘|’) this into the command (New-VDPortGroup -Name $VDPG.PG -VLanId $VDPG.VLANID -NumPorts $Ports) to create the Port Group on the Distributed Switch, using the properties set for each line of the array.

Secondly, we get the Port Group we just created (Get-VDswitch -Name $vDSName | Get-VDPortgroup $VDPG.PG) and then ‘Get & Set’ the Teaming and Loadbalancing options (Get-VDUplinkTeamingPolicy | Set-VDUplinkTeamingPolicy -LoadBalancingPolicy $LoadBalancing -ActiveUplinkPort $ActiveUP), again ‘piping’ the results into the next command.

Below is the output from PowerShell after running the script above:

Name                           NumPorts PortBinding
----                           -------- -----------
dvPG-Guest-VM-1                8        Static

VDPortgroup                      : dvPG-Guest-VM-1
NotifySwitches                   : True
NotifySwitchesInherited          : True
LoadBalancingPolicy              : LoadBalanceLoadBased
LoadBalancingPolicyInherited     : False
FailoverDetectionPolicy          : LinkStatus
ActiveUplinkPort                 : {Uplink 1, Uplink 2}
StandbyUplinkPort                : {}
UplinkPortOrderInherited         : False
Failback                         : False
EnableFailback                   : True
FailbackInherited                : True
UnusedUplinkPort                 : {}
FailoverDetectionPolicyInherited : True
Uid                              : /VIServer=vsphere.local\administrator@vm-vcsa-01.smt-lab.local:443/VDPortgroupUplinkTeamingPolicy=cec49f0b7f124d0c9f37814392494a31/

dvPG-Guest-VM-2                8        Static

VDPortgroup                      : dvPG-Guest-VM-2
NotifySwitches                   : True
NotifySwitchesInherited          : True
LoadBalancingPolicy              : LoadBalanceLoadBased
LoadBalancingPolicyInherited     : False
FailoverDetectionPolicy          : LinkStatus
ActiveUplinkPort                 : {Uplink 1, Uplink 2}
StandbyUplinkPort                : {}
UplinkPortOrderInherited         : False
Failback                         : False
EnableFailback                   : True
FailbackInherited                : True
UnusedUplinkPort                 : {}
FailoverDetectionPolicyInherited : True
Uid                              : /VIServer=vsphere.local\administrator@vm-vcsa-01.smt-lab.local:443/VDPortgroupUplinkTeamingPolicy=e126093ed67f45a3b7c42874c5affc20/

dvPG-Secure-VM-1               8        Static

VDPortgroup                      : dvPG-Secure-VM-1
NotifySwitches                   : True
NotifySwitchesInherited          : True
LoadBalancingPolicy              : LoadBalanceLoadBased
LoadBalancingPolicyInherited     : False
FailoverDetectionPolicy          : LinkStatus
ActiveUplinkPort                 : {Uplink 1, Uplink 2}
StandbyUplinkPort                : {}
UplinkPortOrderInherited         : False
Failback                         : False
EnableFailback                   : True
FailbackInherited                : True
UnusedUplinkPort                 : {}
FailoverDetectionPolicyInherited : True
Uid                              : /VIServer=vsphere.local\administrator@vm-vcsa-01.smt-lab.local:443/VDPortgroupUplinkTeamingPolicy=b5f1889461584b1daf314379cd935f50/

Terraform

Now let’s take a look at using Terraform to achieve the same result. Terraform is an infrastructure and code tool used to manage infrastructure in the form of configuration files and state:

provider "vsphere" {
  vsphere_server = "vCenter Server FQDN"
  user           = "Domain\\Username"
  password       = "Password"
}
data "vsphere_datacenter" "datacenter" {
  name = "dc-smt-01"
}
data "vsphere_distributed_virtual_switch" "vds" {
  name          = "vDS-Workload-Networks"
  datacenter_id = data.vsphere_datacenter.datacenter.id
}
resource "vsphere_distributed_port_group" "pg20" {
  name                            = "dvPG-Guest-VM-1"
  distributed_virtual_switch_uuid = data.vsphere_distributed_virtual_switch.vds.id
  number_of_ports                 = 8
  vlan_id                         = 20
}
resource "vsphere_distributed_port_group" "pg21" {
  name                            = "dvPG-Guest-VM-2"
  distributed_virtual_switch_uuid = data.vsphere_distributed_virtual_switch.vds.id
  number_of_ports                 = 8
  vlan_id                         = 21
}
resource "vsphere_distributed_port_group" "pg25" {
  name                            = "dvPG-Secure-VM-1"
  distributed_virtual_switch_uuid = data.vsphere_distributed_virtual_switch.vds.id
  number_of_ports                 = 8
  vlan_id                         = 25
}

Lets break this down.

First we are specifying which terraform provider we want to use, this will be the vSphere provider in this case. We are then providing some parameters for Terraform to connect to your vCenter instance; VCSA FQDN and credentials.

We then have two ‘data’ blocks. These are used to get information about an existing resource, such as the Distributed Switch and the Datacenter it resides in. You could loosely consider this similar to populating variables in the PowerShell example.

Next we have three ‘resource’ blocks. Each block represents one of the three Port Groups we want to configure. It provides parameters for Name, number of ports and vlan ID for each, along with a reference to the Distributed Switch from the ‘data’ block.

Now when you run ‘terraform apply’ to apply for code, here is the output:

terraform apply  


Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # vsphere_distributed_port_group.pg20 will be created
  + resource "vsphere_distributed_port_group" "pg20" {
      + active_uplinks                    = (known after apply)
      + allow_forged_transmits            = (known after apply)
      + allow_mac_changes                 = (known after apply)
      + allow_promiscuous                 = (known after apply)
      + auto_expand                       = true
      + block_all_ports                   = (known after apply)
      + check_beacon                      = (known after apply)
      + config_version                    = (known after apply)
      + directpath_gen2_allowed           = (known after apply)
      + distributed_virtual_switch_uuid   = "50 33 5e 01 05 1e 32 66-ea f7 7c 42 ce fa f1 96"
      + egress_shaping_average_bandwidth  = (known after apply)
      + egress_shaping_burst_size         = (known after apply)
      + egress_shaping_enabled            = (known after apply)
      + egress_shaping_peak_bandwidth     = (known after apply)
      + failback                          = (known after apply)
      + id                                = (known after apply)
      + ingress_shaping_average_bandwidth = (known after apply)
      + ingress_shaping_burst_size        = (known after apply)
      + ingress_shaping_enabled           = (known after apply)
      + ingress_shaping_peak_bandwidth    = (known after apply)
      + key                               = (known after apply)
      + lacp_enabled                      = (known after apply)
      + lacp_mode                         = (known after apply)
      + name                              = "dvPG-Guest-VM-1"
      + netflow_enabled                   = (known after apply)
      + network_resource_pool_key         = "-1"
      + notify_switches                   = (known after apply)
      + number_of_ports                   = 8
      + port_private_secondary_vlan_id    = (known after apply)
      + standby_uplinks                   = (known after apply)
      + teaming_policy                    = (known after apply)
      + tx_uplink                         = (known after apply)
      + type                              = "earlyBinding"
      + vlan_id                           = 20

      + vlan_range {
          + max_vlan = (known after apply)
          + min_vlan = (known after apply)
        }
    }

  # vsphere_distributed_port_group.pg21 will be created
  + resource "vsphere_distributed_port_group" "pg21" {
      + active_uplinks                    = (known after apply)
      + allow_forged_transmits            = (known after apply)
      + allow_mac_changes                 = (known after apply)
      + allow_promiscuous                 = (known after apply)
      + auto_expand                       = true
      + block_all_ports                   = (known after apply)
      + check_beacon                      = (known after apply)
      + config_version                    = (known after apply)
      + directpath_gen2_allowed           = (known after apply)
      + distributed_virtual_switch_uuid   = "50 33 5e 01 05 1e 32 66-ea f7 7c 42 ce fa f1 96"
      + egress_shaping_average_bandwidth  = (known after apply)
      + egress_shaping_burst_size         = (known after apply)
      + egress_shaping_enabled            = (known after apply)
      + egress_shaping_peak_bandwidth     = (known after apply)
      + failback                          = (known after apply)
      + id                                = (known after apply)
      + ingress_shaping_average_bandwidth = (known after apply)
      + ingress_shaping_burst_size        = (known after apply)
      + ingress_shaping_enabled           = (known after apply)
      + ingress_shaping_peak_bandwidth    = (known after apply)
      + key                               = (known after apply)
      + lacp_enabled                      = (known after apply)
      + lacp_mode                         = (known after apply)
      + name                              = "dvPG-Guest-VM-2"
      + netflow_enabled                   = (known after apply)
      + network_resource_pool_key         = "-1"
      + notify_switches                   = (known after apply)
      + number_of_ports                   = 8
      + port_private_secondary_vlan_id    = (known after apply)
      + standby_uplinks                   = (known after apply)
      + teaming_policy                    = (known after apply)
      + tx_uplink                         = (known after apply)
      + type                              = "earlyBinding"
      + vlan_id                           = 21

      + vlan_range {
          + max_vlan = (known after apply)
          + min_vlan = (known after apply)
        }
    }

  # vsphere_distributed_port_group.pg25 will be created
  + resource "vsphere_distributed_port_group" "pg25" {
      + active_uplinks                    = (known after apply)
      + allow_forged_transmits            = (known after apply)
      + allow_mac_changes                 = (known after apply)
      + allow_promiscuous                 = (known after apply)
      + auto_expand                       = true
      + block_all_ports                   = (known after apply)
      + check_beacon                      = (known after apply)
      + config_version                    = (known after apply)
      + directpath_gen2_allowed           = (known after apply)
      + distributed_virtual_switch_uuid   = "50 33 5e 01 05 1e 32 66-ea f7 7c 42 ce fa f1 96"
      + egress_shaping_average_bandwidth  = (known after apply)
      + egress_shaping_burst_size         = (known after apply)
      + egress_shaping_enabled            = (known after apply)
      + egress_shaping_peak_bandwidth     = (known after apply)
      + failback                          = (known after apply)
      + id                                = (known after apply)
      + ingress_shaping_average_bandwidth = (known after apply)
      + ingress_shaping_burst_size        = (known after apply)
      + ingress_shaping_enabled           = (known after apply)
      + ingress_shaping_peak_bandwidth    = (known after apply)
      + key                               = (known after apply)
      + lacp_enabled                      = (known after apply)
      + lacp_mode                         = (known after apply)
      + name                              = "dvPG-Secure-VM-1"
      + netflow_enabled                   = (known after apply)
      + network_resource_pool_key         = "-1"
      + notify_switches                   = (known after apply)
      + number_of_ports                   = 8
      + port_private_secondary_vlan_id    = (known after apply)
      + standby_uplinks                   = (known after apply)
      + teaming_policy                    = (known after apply)
      + tx_uplink                         = (known after apply)
      + type                              = "earlyBinding"
      + vlan_id                           = 25

      + vlan_range {
          + max_vlan = (known after apply)
          + min_vlan = (known after apply)
        }
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

vsphere_distributed_port_group.pg20: Creating...
vsphere_distributed_port_group.pg21: Creating...
vsphere_distributed_port_group.pg25: Creating...
vsphere_distributed_port_group.pg25: Creation complete after 0s [id=dvportgroup-2669728]
vsphere_distributed_port_group.pg21: Creation complete after 0s [id=dvportgroup-2669730]
vsphere_distributed_port_group.pg20: Creation complete after 0s [id=dvportgroup-2669729]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

For more information on the vSphere provider from Terraform, check out this link.

You will have noticed that I have explicitly defined the number of ports in both the PowerShell and Terraform examples. This is purely to match up with the default value that is set when using the vSphere Client; 8. By default the port allocation automatically expands as required, so this is for consistency rather than anything else.

If you are someone who relies heavily on a GUI as part of your work, I hope this have given you some idea’s on how you can perhaps leverage other options, especially when looking to build or configure in bulk.

Thanks for reading!