Getting Started With Packer to Create vSphere Templates – Part 4 – Blocks

Posted by Stephan McTighe on 14 Jul 2021

Welcome to Part 4 of the Packer Series! In this part we will look at putting together all the block and files we need to deploy a template!

As we have touched upon in earlier parts, we have multiple blocks and files available to us that can be used to make up a complete configuration. We will walk through a complete Source and Build Block here using user defined variables to complete the build. In the final part of this series, I will use a combination of user and environment variables to give you an idea of how you may use this outside of a lab.

Lets start by breaking down a Source Block for a Windows 2019 Core template.

 1source "vsphere-iso" "win-2019-std-core" {
 2  CPUs            = var.CPUs
 3  RAM             = var.RAM
 4  RAM_reserve_all = var.ram_reserve_all
 5  boot_command    = var.boot_command
 6  boot_order      = var.boot_order
 7  boot_wait       = var.boot_wait
 8  cluster         = var.vsphere_compute_cluster
 9  content_library_destination {
10    destroy = var.library_vm_destroy
11    library = var.content_library_destination
12    name    = var.template_library_Name
13    ovf     = var.ovf
14  }
15  datacenter           = var.vsphere_datacenter
16  datastore            = var.vsphere_datastore
17  disk_controller_type = var.disk_controller_type
18  firmware             = var.firmware
19  floppy_files         = var.config_files
20  folder               = var.vsphere_folder
21  guest_os_type        = var.guest_os_type
22  insecure_connection  = var.insecure_connection
23  iso_paths = [var.os_iso_path,var.vmtools_iso_path]
24  network_adapters {
25    network      = var.vsphere_portgroup_name
26    network_card = var.network_card
27  }
28  notes        = var.notes
29  password     = var.vsphere_password
30  communicator = var.communicator
31  winrm_password = var.winrm_password
32  winrm_timeout  = var.winrm_timeout
33  winrm_username = var.winrm_user
34  storage {
35    disk_size             = var.disk_size
36    disk_thin_provisioned = var.disk_thin_provisioned
37  }
38  username       = var.vsphere_user
39  vcenter_server = var.vsphere_server
40  vm_name        = var.vm_name
41  vm_version     = var.vm_version

All values are passed in via variables in this example. You can see this by the ‘var.<variable_name>’ entry against every configuration line. All variables in this example are user defined variables in a pkrvar.hcl file.

We have configuration for CPU, Memory and disk sizes for instance, then we also have the WinRM username, password and timeout values used for connecting to the operating system after it’s been installed, for use with provisioners.

You can deploy your template as just a ’normal’ VM Template in the VM and Templates Inventory by using this line:

1convert_to_template        = true

Or a using a variable:

1convert_to_template             = var.convert_to_template

Or you can deploy to Content Libraries by either removing the “convert_to_template” option or setting it to false, and replacing it with this:

1  content_library_destination {
3    library = var.content_library_destination
4    name    = var.template_library_Name
5  }

If you already use Content Libraries, then you are likely going to want to continue to do so.  Or, if you have multiple vCenter’s, you may want to make use of subscribed libraries so you only have to deploy the template once.

To go further you can automatically destroy the original VM after its been uploaded to the Content Library by adding:

1destroy = var.library_vm_destroy

And to take it even further, you can add the following to convert the template to an OVF.  OVF’s can be updated in the content library and therefore will be overwritten when you deploy your template again.  This can’t be done with a standard VM template.

1ovf     = var.ovf

To bring that all together it looks like this:

1  content_library_destination {
2    destroy = var.library_vm_destroy
3    library = var.content_library_destination
4    name    = var.template_library_Name
5    ovf     = var.ovf
6  }

A key line to point out in this windows example configuration above, is the ‘floppy_files’ option. This option is used to mount a floppy disk with any configuration files or media that you need to reference during the operating system installation. This includes your unattended.xml file, any scripts and any media or drivers such as VMware Paravirtual drivers for the SCSI controller. Checkout Part 2 for more info.

If we were looking at a Linux build, we would see the WinRM options replaced by SSH, like so:

1  ssh_password = var.ssh_password
2  ssh_timeout  = var.ssh_timeout
3  ssh_username = var.ssh_username

A full list of the different configuration options available can be found here.

Now we have defined our source, we now want to deploy it using a build block.

 1build {
 2  name    = "win-2019-std-core"
 3  sources = [""]
 5  provisioner "powershell" {
 6    scripts           = var.script_files
 7  }
 8  provisioner "windows-update" {
 9            search_criteria = "IsInstalled=0"
10            filters = [
11                      "exclude:$_.Title -like '*Preview*'",
12                      "include:$true"
13            ]
14            update_limit = 25
15  }
16  post-processor "manifest" {
17    output = "output/out-win-2019-std-core.json"
18    strip_path = false
19  }

What’s happening in this block, is that we are referencing the source block that contains our configuration based on the name of the source block that we defined earlier, in this case ’’.

In this example we also have two provisioners being used once the operating system has been installed. Firstly, the Windows-Update-Provisioner which installs the latest Windows updates based on any filters you include. In this example, its configured to exclude any updates with ‘Preview’ in the title and also to limit it to install up to 25 updates.

Secondly, we are making use of the Manifest post-processor. This produces an output that includes information such as build time each time it is run.

 1      "name": "win-2019-std-core",
 2      "builder_type": "vsphere-iso",
 3      "build_time": 1617185954,
 4      "files": null,
 5      "artifact_id": "windows-2019-std-core",
 6      "packer_run_uuid": "865be1fd-0dec-1688-8c89-9252e48d0818",
 7      "custom_data": null
 8    }
 9  ],
10  "last_run_uuid": "865be1fd-0dec-1688-8c89-9252e48d0818"

All of the above makes up a complete build file that can be deployed with any media or variables you have referenced. The full set of files for this example can be found here.

To give you an example of a non-windows Provisioner, here is a Shell Provisioner for a Linux template:

1provisioner "shell" {
2    execute_command = "echo '${"var.ssh_password"}' | sudo -S -E bash '{{ .Path }}'"
3    scripts         = var.script_files
4  }

This executes all scripts that are referenced in the script.files variables.

Now using environment variables, nothing really changes. Your build file will look the same, the only differences will be you won’t provide a value for your declared variable in your pkrvar.hcl file, instead adding the variable to your terminal session. Check out Part 3 for more info. In the final part of this series, I will show an example of using both user defined and environment variables.

That concludes a short run through of the different files in the examples you can find on my GitHub. By no means have I covered everything in those examples or everything you can do with Packer, but this series along with the examples should help you on your way with discovering Packer! There is so much more that can be done using this product to create templates on vSphere as well as multiple other platforms so do head over to to discover more.

In the final part of this series, I am going to try a different content type, video’s! In these, we will run through two end to end template deployments using default values for variables, user defined and environment variables to show how you could use this as part of a workflow.

If you have gotten this far, thanks for sticking with me and I hope you have enjoyed it and found it useful!