Using Template Files with HashiCorp Packer
Learn about the new HCL2-only templatefile function and how to use it in an http_content to preseed a virtual machine.
In HashiCorp Packer 1.7, we tagged HCL2 as stable and implemented HCL2-only functions. You can use one such function, the templatefile
function to build multiple operating systems with less duplication of configuration.
Currently, you need to use the boot_command
argument to configure an OS before you connect to the machine. You can use it with many builders, including the vmware-iso or virtualbox-iso builders. A boot_command
mimicks manual keystrokes and sends them at a regular cadence. You aggregate these keystrokes for installing and configuring packages in a preseed file. Packer enables sharing preseed files by making them available statically through an HTTP server. You can also access static files using CD files or a floppy.
In this post, we’ll use the http_content
and the templatefile
functions together to build preseed file templates for two Ubuntu images, one with HashiCorp Nomad and one with HashiCorp Consul.
» Templating a Preseed File
Say the file preseed.pkrtpl
is your preseed template file, and you would like to be able to set a user’s name, ID, and password and also the packages installed with it :
d-i apt-setup/universe boolean true
d-i pkgsel/include %{ for install in installs ~}${install} %{ endfor }string
openssh-server cryptsetup build-essential libssl-dev libreadline-dev zlib1g-dev
linux-source dkms nfs-common linux-headers-$(uname -r) perl cifs-utils
software-properties-common rsync ifupdown
d-i passwd/user-fullname string ${user.name}
d-i passwd/user-uid string ${user.id}
d-i passwd/user-password password ${user.password}
d-i passwd/user-password-again password ${user.password}
d-i passwd/username string ${user.name}
choose-mirror-bin mirror/http/proxy string
d-i base-installer/kernel/override-image string linux-server
d-i clock-setup/utc boolean true
d-i clock-setup/utc-auto boolean true
d-i finish-install/reboot_in_progress note
d-i grub-installer/only_debian boolean true
d-i grub-installer/with_other_os boolean true
d-i mirror/country string manual
d-i mirror/http/directory string /ubuntu/
d-i mirror/http/hostname string archive.ubuntu.com
d-i mirror/http/proxy string
d-i partman-auto-lvm/guided_size string max
d-i partman-auto/choose_recipe select atomic
d-i partman-auto/method string lvm
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm boolean true
d-i partman-lvm/confirm_nooverwrite boolean true
d-i partman-lvm/device_remove_lvm boolean true
d-i partman/choose_partition select finish
d-i partman/confirm boolean true
d-i partman/confirm_nooverwrite boolean true
d-i partman/confirm_write_new_label boolean true
d-i pkgsel/install-language-support boolean false
d-i pkgsel/update-policy select none
d-i pkgsel/upgrade select full-upgrade
d-i time/zone string UTC
d-i user-setup/allow-password-weak boolean true
d-i user-setup/encrypt-home boolean false
tasksel tasksel/first multiselect standard, server
This template file can be used to install binaries from a variable. In this example, we made it possible to configure the user settings and pass an arbitrary list of packages to install. This simplifies the build while making it more powerful.
Note: The
.pkrtpl
extension is a recommendation and not a requirement. It allows editors to recognize a Packer template file written in HCL2.
The following configuration file can then be defined:
variables {
headless = true
}
source "virtualbox-iso" "base-ubuntu-amd64" {
headless = var.headless
iso_url = local.ubuntu_2010_iso_url
iso_checksum = "file:${local.ubuntu_2010_iso_checksum_url}"
guest_os_type = "Ubuntu_64"
hard_drive_interface = "sata"
ssh_wait_timeout = "15m"
boot_wait = "5s"
}
locals {
ubuntu_2010_dl_folder = "http://cdimage.ubuntu.com/ubuntu/releases/18.04/release/"
ubuntu_2010_iso_url = "${local.ubuntu_2010_dl_folder}ubuntu-18.04.5-server-amd64.iso"
ubuntu_2010_iso_checksum_url = "${local.ubuntu_2010_dl_folder}SHA256SUMS"
builds = {
consul = {
user = {
id = 1000
name = "bob"
password = "s3cr2t"
}
installs = ["consul"]
}
nomad = {
user = {
id = 1000
name = "bob"
password = "s3cr2t"
}
installs = ["nomad"]
}
}
}
build {
name = "ubuntu"
description = <<EOF
This build creates images for :
* Ubuntu 18.04
For the following builders :
* virtualbox-iso
It will create base images with:
* Nomad
* Consul
EOF
dynamic "source" {
for_each = local.builds
labels = ["source.virtualbox-iso.base-ubuntu-amd64"]
content {
name = source.key
ssh_username = source.value.user.name
ssh_password = source.value.user.password
shutdown_command = "echo '${source.value.user.password}' | sudo -S shutdown -P now"
http_content = {
"/preseed.cfg" = templatefile("${path.root}/preseed.pkrtpl", source.value)
}
boot_command = [
"<esc><wait>",
"<esc><wait>",
"<enter><wait><wait>",
"/install/vmlinuz<wait>",
" auto<wait>",
" console-setup/ask_detect=false<wait>",
" console-setup/layoutcode=us<wait>",
" console-setup/modelcode=pc105<wait>",
" debconf/frontend=noninteractive<wait>",
" debian-installer=en_US.UTF-8<wait>",
" fb=false<wait>",
" initrd=/install/initrd.gz<wait>",
" kbd-chooser/method=us<wait>",
" keyboard-configuration/layout=USA<wait>",
" keyboard-configuration/variant=USA<wait>",
" locale=en_US.UTF-8<wait>",
" netcfg/get_domain=vm<wait>",
" netcfg/get_hostname=vagrant<wait>",
" grub-installer/bootdev=/dev/sda<wait>",
" noapic<wait>",
" preseed/url=http://{{ .HTTPIP }}:{{ .HTTPPort }}/preseed.cfg<wait>",
" -- <wait>",
"<enter><wait>"
]
output_directory = "virtualbox_iso_ubuntu_2010_amd64_${source.key}"
}
}
provisioner "shell" {
environment_vars = [ "HOME_DIR=/home/vagrant" ]
execute_command = "echo '${local.builds[source.name].user.password}' | {{.Vars}} sudo -S -E sh -eux '{{.Path}}'"
expect_disconnect = true
inline = [
"echo hello from the ${source.name} image",
"${source.name} version"
]
}
}
Now we can see that this has made it easier to maintain a preseed project. Pretty cool, right? Effectively, the templatefile
function can be used in other scenarios, same for the http_content option. We could extend this by skipping the template file and setting everything from the map:
[...]
http_content = {
"/preseed.cfg" = <<<EOF
d-i apt-setup/universe boolean true
d-i pkgsel/include string openssh-server cryptsetup build-essential libssl-dev libreadline-dev zlib1g-dev linux-source dkms nfs-common linux-headers-$(uname -r) perl cifs-utils software-properties-common rsync ifupdown consul
...
EOF
}
» Conclusion
For more information on Packer’s recent additions, review Packer's changelog. Lastly, if you have any issues, do not hesitate to open an issue in the Packer repository on GitHub.
Sign up for the latest HashiCorp news
More blog posts like this one
New SLM offerings for Vault, Boundary, and Consul at HashiConf 2024 make security easier
The latest Security Lifecycle Management (SLM) features from HashiCorp Vault, Boundary, and Consul help organizations offer a smoother path to better security practices for developers.
Terraform, Packer, Nomad, and Waypoint updates help scale ILM at HashiConf 2024
New Infrastructure Lifecycle Management (ILM) offerings from HashiCorp Terraform, Packer, Nomad, and Waypoint help organizations manage their infrastructure at scale with reduced complexity.
HCP Packer now tracks CI/CD pipeline metadata
You can now see the CI/CD pipeline metadata associated with each image build in HCP Packer.