Glazier Release: 0.1.5
Today, I minted and tagged Glazier v0.1.5. Immediately after, realised I never announced v0.1.4, the release with all the actual features in it. So, Happy Birthday, you get a twofer.
Let’s get v0.1.5 out of the way first.
There’s not much to see here, honestly. This is a routine housekeeping and dependency bump release. The only change worthy of a mention is is a bump of golang.org/x/text to address GO-2026-5970. This is the sort of thing you patch promptly even when the practical exposure for a tmux wrapper is nil ( ha-ha-ha ). Alongside that, the Go toolchain moved up to 1.26.4 and testify, urfave/cli and a stack of GitHub Actions all got their routine nudges courtesy of Dependabot. Hey, thanks little buddy!
Download the latest release, replace your current binary and you should be good to go.
v0.1.4 is where the features live.
Back in July I wrote about adding typed variable blocks to Glazier. That work shipped on July 17th as v0.1.4.
The short recap, if you didn’t read that post is that profiles can now declare the inputs they accept as first-class variable blocks. Just like, you guessed it, Terraform. Each block can carry a primitive type (string, number or bool), a default and a description. No default means the variable is required. Pass a --var the profile never declared and you get a proper located error.
Values are read through the var. namespace and only that namespace:
variable "district" {
description = "the district the gig is themed after"
type = string
default = "watson"
}
variable "fixer" {
type = string
}
session {
name = "gig-${var.district}"
window {
name = "${var.fixer}-ops"
pane {
commands = ["echo ${var.fixer} has the next job"]
}
}
}$ glaze up --var fixer=wakako
$ glaze up --var district=arasaka --var fixer=wakako
That post covers the design reasoning, the type coercion and the breaking namespace migration in detail. So, no need to reshash it all here. What it doesn’t cover is everything else that snuck into the release after I hit publish button.
Support for locals.
Variables are inputs. They cross the boundary from the outside world into your profile via --var flags, var files or defaults. But the moment I had proper variables I ran into their natural companion problem; derived values. The thing you compute from an input once and then want to reuse in five places without copy-pasting the same expression five times.
Terraform solves this with locals and, because Glazier remains an unapologetic love letter to Terraform’s parser, it does as well:
variable "district" {
default = "night city"
}
locals {
slug = lower(replace(var.district, " ", "-"))
session = "gig-${local.slug}"
logfile = "${path.pwd}/logs/${local.slug}.log"
}
session {
name = local.session
window {
name = "${local.slug}-ops"
pane {
commands = ["tail -f ${local.logfile}"]
}
}
window {
name = "editor"
pane {
commands = ["nvim"]
}
}
}You declare named values in a locals block at the top level and read them back through local.<name>. Plural block and a singular reference is exactly how Terraform does it.
A local can reference the var.*, env.*, path.* namespaces, the whole function library and, crucially, other locals. In any order. This works fine:
locals {
session = "gig-${local.slug}" # references slug...
slug = lower(replace(var.district, " ", "-")) # ...declared after it
}Under the hood resolution just keeps iterating, evaluating whatever it can each pass until the end. Whatever’s left over then reports its real evaluation diagnostics, so a genuine fat-fingered mistake surfaces as the actual error rather than being masked by ordering machinery. If you accidentally declare the same local twice you’ll get told exactly where both declarations live.
The one thing you can’t do with a local is set it from the command line and this is by design. Variables are the contract with the outside world; locals are private. If a value should be overridable, it’s a variable. If it’s just an expression you’re tired of repeating, it’s a local. Having the language enforce that distinction is one of those small things that keeps a profile clean as it grows.
They also pair nicely with the new random function and HCL’s inline comprehensions, both of which landed in this release too:
locals {
editors = ["nvim", "hx", "vim"]
greeting = random([for g in ["hello", "choom", "wake up samurai"] : title(g)])
}random(list) returns a uniformly random element of a list as a string. Did anyone ask for this? Absolutely not, but you deserve nice things.
lol… lmfao.
The July post ended with me musing about adding *.tfvars-style functionality and asking, and I quote, “Surely, I won’t fall for this again?”
He, indeed, fell for this again.
Yeah, so v0.1.4 also ships with --var-file support:
# gig.vars
district = "japantown"
fixer = "wakako"$ glaze up --var-file gig.vars
$ glaze up --var-file gig.vars --var fixer=dino
A var file is plain HCL; a name = value attributes written once per line. I originally supported JSON files too and then ripped that out before release, because maintaining a second parse path for a format nobody was going to use failed the smell test. In other words, “YAGNI”.
Precedence works the way you’d expect: defaults go first, a var file overrides defaults and an explicit --var flag always has the final say. In the second command above fixer comes out as dino no matter what the file thinks.
Strictness from the flag side applies here too. An entry in a var file that names a variable the profile never declared is an error. Values are coerced to their declared types and every problem in the file is reported all at once for convenience.
Our first contributor has entered the chat!
This release contains the first fix from an actual outside contributor. ThreeToes tracked down an annoying initialisation bug where the first window in a profile would be ignored and cleaned up on startup. Leaving you with either a stray default terminal or only the subsequent windows.
Someone who isn’t me not only ran the thing, they read the source and fixed it. That’s a genuinely lovely milestone for a hobby project. Thanks, man!
Oh, and the repo finally has a proper SPEC.md. A full reference for the profile format describing every block, namespace and function.
In closing …
Release notes are like backups. Everyone agrees they’re important and nobody does them until it’s embarrassing.
As always, it’s up on GitHub under MIT. If you’ve got Go installed:
$ go install github.com/wilhelm-murdoch/glazier/cmd/glaze@latest
Or, grab a prebuilt binary from the releases page, checksums and provenance attestations are included. Small note on this, I do plan on adding support for all your favourite package managers to make this process a bit more intuitive. Homebrew first as a test run, then all the other you’d expect.
If you’ve opinions on locals, var files or what Glazier should learn next, the comments are right below.
P.S. - I’ve another secret project in the works and I’m using it to declaratively generate Open Graph social cards for the blog. I thought I’d create a format dedicated to Glazier releases.
Comments