Packer hcl2 configuration migration

Maybe before I diverge into an old story I should barf up a recent one.

Packer HCL2 configuration migration

But first I should set the scene - I went full retard into using packer to build all the things. Some work very well (Fedora and Centos), some work pretty good (Windows2019 and OmniOS), some you wouldn’t even expect to work can actually be coaxed into behaving quite well (Netapp Ontap), and others are just a basket case needing a nice dose of hack (openwrt). I’ve even kicked the tyres on using multiple builders (targets) in the same file - totally supported mind you. I used Virtualbox initially, then added KVM/QEMU and eventually I needed to target Vmware (which has a host builder esxi and a vsphere builder just to keep you on your toes - incompatible with each other and all the variables are named differently).

So you could say I’ve tried a lot of things in there, and for whatever reasons ended up with some out there configuration in the files. When I started with Packer, the files were json only with no comments. :( json is fine, but not having a comment tag in the schema meant even adding a name:value pair for a comment resulted in a validation error and sad pandas all around. Once I got over that (comments in a file next to the json, and a variable for comments at the top of the file) I added more and more standardisation to my files, to make it easier to up rev something, say Centos 7 to 8. Each packer config had it’s own directory (and comments file), so a new rev was a copy/paste and some renaming and change a few lines in the file. Variables at the top made this easy for all the common things (like iso URL and iso checksum, and guest name) which the multiple builders in the file used - no duplication was important. This worked well for a while, and I built up quite the catalog of a variety of OS’s and versions. Each spitting out a packer box, locally named spaced and versioned so they can all exist and be consumable concurrently and vagrant allows the same box name with different providers too.

copy pasta

I got quite advanced with the Netapp Ontap one, as I wanted to produce a vagrant environment of a multiple node simulator. Step one was taking the simulator that targets vmware, and converting it to run in VirtualBox. Step two was to packer build it to a point that ansible could talk to it (add an IP and password). Then repeat step two for multiple nodes (different IP’s and serial numbers). I was quite pleased with the result and was able to iterate very rapidly on some zero day building playbooks. It eventually got to the point I outgrew what virtualbox could do (I wanted more than 8 nics) and it’s networking was limiting me, so I started moving that to QEMU. However that’s not what this post /rant is about. BACK TO HCL2.

So fast forward some packer versions (I started with 1.1 and 1.7.10 is current now). This new HCL2 thing comes along in beta, and I mostly ignore it. I’m using jq to query my packer json files in the tooling that I’ve built up around it (to better handle the vagrant and later libvirt parts). But by the time 1.7 comes out the writing is on the wall - HCL2 is the future of the packer config format and I’ll need to migrate eventually. FORTUNATELY, it’s been made easy with a migration tool LUCKY!

But in usual hashicorp fashion it seems - 80/20 rule is alive and well. The 80% that everyone has used will work fine, but the 20% that not so many use might not. As is my usual tradition - around this tool I build up some scraps (shell script in this case) to handle the rest. See I want my output files to be orderly and tidy. Not a code generated mess, after all the input files were curated with json fields in a sensible order (say all the disk things grouped together, and the network things together). But as you’d expect the migration tool is the result of a config parse and then export. So the unimportant details like field order get lost and they come out alphabetical.

Other deficiencies in the migration also show up as the templating/variable handling has changed. HCL2 allows for typed variables (good), and has locals which are like a constant (handy). It also lets you name a source (formally builder), and gives you an autogenerated one by default. But the way it handled some of the magic hasn’t made this conversion yet. So now it’s a mixture of HCL2 variables and templating, with a reasonable chunk of the old skool go templating still there. Some specific examples might help.

Builder json "cpus": "{{user `cpus`}}", becomes source hcl2 cpus = "${var.cpus}" though I prefer the cleaner cpus = var.cpus. That’s fixable with sed.

Builder json "iso_checksum": "{{user `iso_checksum`}}", becomes source hcl2 iso_checksum = "${vars.iso_checksum}" though lets make it a local since it’s constant iso_checksum = local.iso_checksum. That’s fixable with sed (and the above one would run first to simplify). The actual edit to the header of the file to turn the variable into a local also needs doing, but thats copy/paste in so skip over that one. Also repeat this for a few vars.

So far so good. Cooking with gas.

Builder json "guest_additions_path": "VBoxGuestAdditions_{{.Version}}.iso", becomes source hcl2 guest_additions_path = "VBoxGuestAdditions_{{ .Version }}.iso". Now that’s surprising, maybe the internal variables that start with dots get left alone.

Builder json "boot_command": ["<up><tab> inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/kickstart.cfg<enter>"], becomes source hcl2 boot_command = ["<up><tab> inst.ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/kickstart.cfg<enter>"]. Yeah maybe that’s it.

Post processor json "output": "../boxes/{{user `codename`}}_{{ .Provider }}.box", becomes post processor hcl2 output = "../boxes/${var.codename}_<no value>.box". grumbles back to the documentation to try and find the answer, and that link saves you the trouble. But the trick is to know it’s not the build.type you want but the source.type. So what I wanted in hcl2 was output = "../boxes/${local.codename}_${source.type}.box". Fortunately sed can save the day here too. Strictly speaking these are not the same though. .Provider returned virtualbox or libvirt where source.type returns virtualbox-iso and qemu. Ta da!

Also while on the topic of the builder/source differences there, it also changes how you call from the cli. I was calling packer with the desired builder on the cli, say packer build -timestamp-ui -only=virtualbox-iso blah.json for virtualbox but now I have to use the name of the source and it’s identifer (or wildcard), so packer build -timestamp-ui -only=virtualbox-iso.centos7 blah.pkr.hcl for example, or generic using a wildcard which then means quoting -only="virtualbox-iso.*" blah.pkr.hcl. Oh dear. These other changes were not mentioned anywhere in the migration article. The 80% out there must only use a single builder anyway, so it wouldn’t ever come up.

So then you end up with a migration script that looks a bit like this (and still has some manual bits that aren’t scripted, like the vars to locals at the top)

if [ "$#" -ne 1 ]; then
  echo parameter is json file to convert
echo About to convert ${FILENAME} into ${FILENAMEHCL}

packer hcl2_upgrade ${FILENAME}

sed -i -e "s/var.brand/local.brand/" ${FILENAMEHCL}
sed -i -e "s/var.codename/local.codename/" ${FILENAMEHCL}
sed -i -e "s/var.iso_checksum/local.iso_checksum/" ${FILENAMEHCL}
sed -i -e "s/var.iso_url/local.iso_url/" ${FILENAMEHCL}
sed -i -e 's/_<no value>/_\${source.type}/' ${FILENAMEHCL}

#and turn "${var.disk_size}" into var.disk_size
sed -i -r 's/"\$\{([^}]*)\}"$/\1/' ${FILENAMEHCL}

#replace autogenerated tag with buildname
sed -i -e "s/autogenerated_1/${BUILDNAME}/" ${FILENAMEHCL}
sed -i -e "s/autogenerated_2/${BUILDNAME}/" ${FILENAMEHCL}

But really this is just scratching the surface. Now to rewrite my wrapper scripts to handle the vagrant box add/remove bits, and the libvirt storage pool delete bits. It feels like make work.

It’s just part of the feeding and watering maintenance of the automation journey. Who knows what the next flavour of the month tool will be?

Powered by hampsters on a wheel.
Built with Hugo
Theme Stack designed by Jimmy