Drew Showalter
October 13, 2016

Salt is dead.

Just kidding! It’s totally fine for some purposes; but I think you have to use hyperbolic titles like that when writing about code. It’s like a rite of passage. But at Prismatik, it’s true; no more Salt.

Our use of SaltStack was on the way out. Instead, we were working on a new tool for provisioning new machines. I worked on a small feature of the tool, and this is a chronicle of my small diversion from Node.js into Go.

Enter Config

So, instead of tool-knowledge, we switched to something much more broadly usable: learning Go. Right out of the gate Go looks like the right tool for the job. Any Go code can be compiled for a litany of different hardware, and needs no dependencies to execute. It’s also a great deal more readable, maintainable, and reliable than a spaghetti tower of shell scripts.

So we started ripping out all the old functionality of salt, and creating go code to duplicate the behaviour. This turned out to be a great way to get acquainted with coding in Go!

As it turns out, the parts of salt we actually needed were just:

  • Running a shell command.
  • Running a shell command, and ignoring failure.
  • Writing a file.
  • Adding a user. (just a special run command)
  • Figuring out the linux distro.

Simple, right? At first, we had included the blob of functions in config, but then we split it out into the aptly named Jabba.


Having never coded in Go, one of the first things I needed to do was understand the file structure, function, and variable notation. Coming from Node/JS land, there were some very notable differences.

Right off the bat things got a little weird, and very telling of developing in go. To make things work smoothly, the repo was cloned into /Users/USERNAME/go/src/github.com/prismatik since prismatik owned the repo. It was a convention over configuration choice that go favours.

Every go file begins by exporting the name of the package. And the file is also named the same with a .go on the end.

package jabba

Followed swiftly by any dependencies that need to be used, including other named packages that may exist in relative directories.

import (

You may notice the formatting. I didn’t do it! Another of the oddities of coding in Go is gofmt. Go code knows how it should look. If you pass -w to gofmt it applies this formatting for you. I never really saw much difference between this and automatic linting, it’s definitely a perk in my book.

Now, to create an object:

type User struct {
   Username string
   Key string
   Sudo bool

Awesome! We can even tell what type each property of the object is supposed to be.

And finally, a function. As this was the function I found I needed to create, let’s work through it.

Stringy Commands

The command to see the distro of whatever linux box you’re working on is lsb_release -cs. To install distro-specific versions of some apps, you need have access to a stringified version of what this command returns.

Since we already have a run() function, I tried just modifying the returns from that. WRONG. Apparently the docs don’t like describing what type each function returns. Those return some variety of io bytestring (type io.Writer), that refuses to be coerced to string.

Ok, so start again. Looking at the documentation, it looks like chaining .Output() returns Stdout and any errors. So, first line:

something, err := exec.Command(“lsb_release”, “-cs”).Output()

Multiple lefthand variable assignment woo! It looks like that something might be able to be turned into a string, as the documentation shows an example using it:

fmt.Printf(“The date is %s\n”, something)

And just string() wrapping seems to coerce the something into the format we’re looking for, sweet! So, after fatally erroring whenever err isn’t nil (just like elsewhere in the app), we’re left with a function that returns a string of the distro!

First thing’s first.

gofmt -w #if happy
go build jabba.go

If go build returns nothing, then we can rest assured that the program hasn’t hit any snags, and is doing generally what we expect! This quick-compile feels like a great sanity check. Then, all we have to do to check that it’s working is run the script in a dockerised instance, which, after caching the other build steps happens in seconds.

Why this matters

Golang is powerful. It’s fast. It’s documentation may have some flaws if you’re not extremely vigilant in looking at typing and looking up what that typing means; but it generally tells yo what you need to know.

Starting with no knowledge of go, and a program written by a senior dev; I was able to expand my knowledge and the functionality of our machine configuration tool, and dump an over-engineered old one.

Jabba and Config are open source. We used Glide to install Jabba as a dependancy of Config. (Just be sure to watch your GOROOT and GOPATHenvironment variables).

glide install

Glide also installs a separate private secrets repo. Obviously I can’t reveal what’s in this repo, but it’s just a secure way of storing and accessing our secret strings. It’s simple to write your own, however, just satisfy the Secrets API and declare it as the secrets dependancy in your own version of Config.

Using this setup, we’re able to generate a binary for any new machine that will configure it with zero hassle. All it takes to compile a new version for an ubuntu machine is:

env GOOS=linux GOARCH=amd64 go build -v config.go

I’ve only been coding a year and a half, and this is my first foray into Golang. If you’re another budding devOps person (or even an old-hat), fork our repos! Take an couple hours or a day to rework minimal parts of the code like user public keys, and you too can automate what can be a tricky and slow part of devops! I promise you won’t feel like the time has been wasted.

Related Posts