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.