Learning Golang - simple scripting

Today I decided to perform a simple scripting task in Golang. One of the challenging aspects of coding with AWS is testing code locally. In AWS we usually use instance credentials for anything that requires permissions to AWS resources. This happens automagically via the AWS sdk when you run code within AWS. The trouble is that when you try to test the same code locally, it doesn’t have access to these instance roles - so you need to handle that.

There are a few different ways to provide credentials to the sdk. At my job we historically have used environment variables. We even wrote a script that sourced credentials from a yaml file and exported the correct env variables. The workflow went something like this:

  1. eval $(script to export env variables)
  2. Run your program, which wold now find the needed env variables.

This works great, until you open a new shell and forget to update the env. Or you update ~/.profile to have the right env but you need to switch to a different AWS account and still need to update your env when you switch shells. Or you need to test something ran by a supervisor like daemontools and it’s env is different unless you take the time to modify it. You get the idea. Environment variables are lovely, but it winds up being a pain. You test for 10 minutes only to realize you’ve been hitting the wrong AWS account because the env got dropped somewhere.

My personal solution to this problem goes something like this:

  1. Stop using a custom yaml file to store AWS credentials.
  2. Stop using env variables for testing outside of AWS.
  3. Start using the AWS sdk defined credentials file (and it’s format).

With this approach, so long as you have the default account defined for a particular user - everything just works. This is very close to how it works when ran within AWS (via instance roles) and this is what I wanted to mimic.

Automate the AWS sdk credentials file

The AWS sdk supports profiles, which is grand - but I was trying to mimic instance credentials. I needed the code to work using whatever it was granted by default. This meant I needed to easily be able to change the default credentials (versus adding conditional logic that would only be used during development/testing). More specifically I wanted to describe each AWS account I used as a profile in the AWS credentials file, but toggle the default one via automation. I also had the practical reality of a custom credentials yaml file… so I wanted to be able to reference that as a fall-back if a profile wasn’t defined. Typically this is a task I would have quickly written up in Python… but I wanted to do it in Golang :)

This struck me as a nice coding exercise in a language I wanted to learn. It was clearly not a challenging task, and involved a few common things that I’d need to learn anyway:

  1. Command line arguments
  2. Reading text files
  3. Writing text files
  4. Consuming yaml
  5. Simple data structures

Final outcome

The end result was as you’d expect. The code was pretty simple, about 100 lines. It wasn’t hard to write, and does the job nicely. But I did have a few different areas where I got stuck.

Command line args

I’ve historically always written most every command line argument with both short and long forms. For example -e and --environment which would hold a string representing the desired environment. With Golang I was surprised to learn that the flag library only supports a single type of expression that happens to mimic google-gflags. This means you can use -e or -env but not both. Interestingly I have observed that you can use - or -- with whatever argument you specify.

Working with ini files

Considering how common (and old) ini files are I honestly expected Golang to handle this via stdlib. Turns out this is not the case, but go-ini worked nicely. The only part that took a little time was learning how to actually mutate a value in the object representation of the ini file. I read the documentation a few times, searched for phrases like set and write but didn’t find anything on how to actually change a value. I then noticed a link to the api documentation and the same phrase search found what I was looking for: SetValue.

Working with yaml

This was where things became challenging (for me). I was working with yaml that looked something like this:

qa1:
    key: foo
    secret: bar

staging:
    key: foo
    secret: bar

When I started looking for information on how to consume this type of structure, I started by searching for examples. All of the documentation I found talked about creating a struct. I really liked this idea because I would have a type to describe the data I was consuming. This felt clean, organized, consistent with my desire to learn Golang in the first place. I saw lots of examples along the lines of this:

type T struct {
        A string
        B struct {
                RenamedC int   `yaml:"c"`
                D        []int `yaml:",flow"`
        }
}

I tried a bunch of permutations but always wound up with an empty data structure after calling Unmarshal. I noticed the yaml type hints in many of the documents I read, but chasing that turned out to be a waste of time. Fundamentally I was looking to consume data that was a simple nested map. A map of maps, who’s keys and values were all strings. I was struggling on how to do this as all of the examples minimally defined the top level structure, even if below that it was just a map[string]string. Because I clearly am out of my league, I decided to take the path of least resistance. So I created a struct to describe each pair:

type Environment struct {
    key    string
    secret string
}

Then I actually enumerated the known aws keypairs I was dealing with, so something like this:

type CustomCredentials struct {
    env1 Environment
    env2 Environment
    env3 Environment
}

Nothing. At this point I wondered if I was actually even testing what I was testing. Maybe I was consuming the wrong yaml file and passing it to the ini library, or maybe I was consuming the right file… but was passing the wrong data type to the library. I was really scratching my head. Eventually I learned that the first letter of the field defined in the struct had to be capitalized. For example if the top level key in the yaml file was named env1 then the struct needed to be Capitalized. So the following made things spring to life:

type CustomCredentials struct {
    Env1 Environment
    Env2 Environment
    Env3 Environment
}

I was consuming the yaml successfully, woo hoo! At this point I was pretty much finished with the program. But there was this tiny little part left. If I could not find the named profile in the credentials file, I needed to see if I could find it from the custom yaml file. For example if I was trying to set env2 as the default account but there was no such profile in the credentials file, I needed to fetch that information from the yaml file, via it’s capitalized field name.

This seemed great, until I realized I was trying to find a struct field name based on a lower cased string representation of it. This was fine though, I’m no stranger to reflection (getattr happens to be one of my favorite programming questions, to see if someone who doesn’t now the language can learn something new).

This sent me down a trail of how to use reflect. I was able to reflectively gain access to the top level keys via something similar to:

r := reflect.ValueOf(myStruct)
value := reflect.Indirect(r).FieldByName("Env1")

But then I got lost on .Elem(), .Type() and things like .Type().String(). I was chasing things I very much didn’t understand.

What again am I trying to solve?

It was a this point that I thought back to the original problem - I was dealing with yaml who’s top level keys were not firmly defined, and I wanted to reference them via a map. If I could just do that, I’d be set. So I tried again to define a type, that described a map of maps. I tried things like:

type CustomCredentials struct {
    map[string]map[string]string
}

and

var CustomCredentials = map[string]map[string]string

Finally I stumbled upon a site somewhere (I honestly forget where) that mentioned the syntax to do exactly this:

type LegacyAwsCreds map[string]map[string]string

Boom, I was done. Writing the program wasn’t hard, but easily 75% of the time was dominated by trying to learn how to use the custom yaml file that contained the keys.

Scoping

I also learned a quick lesson in scoping. Initially I was trying things like this:

value, err := getCredentialsFromProfile(key)
if err != nil {
    value = getCredentialsFromYamlFile(key)
}
key.SetValue(value)

I quickly realized this is not allowed, and updated my program to calculate the value and return it to something else who’s job it was to use the value. Something closer to this:

value, err = getCredentialsFromProfile()
if err != nil {
    return getCredentialsFromYamlFile()
}
return value

This worked nicely, and caused me to have better separation of concerns anyway. Felt nice.

Go install

Another silly thing that I didn’t expect to struggle with, was go install. It went something like this:

$ go install aws-credentials.go
go install: no install location for .go files listed on command line (GOBIN not set)

Then I spent some time chasing $GOBIN and ultimately $GOPATH and finally learned I was just doing it wrong:

go install

worked perfectly, I just wasted some time along the way :)

Summary

The program worked out great. It’s small, easy to read, fast, and something I can easily share with co-workers. I especially like the part where they can download the binary and not have to satisfy dependencies like PyYAML :)

Update (2015/11/23)

I was using the program recently and noticed that I had AWS keys at the root of the credentials file, versus in a env specific section. I figured the issue was some sort of defect in my code. I spent a good chunk of time trying to understand why or how I could create a section, write a key to that section, and then persist the configuration to a file and have they keys - but not in the section specified.

Here’s a snippet that I expected to produce a section with a single key:

cfg.Section("section").NewKey("key", "value")

Here the Section() function fetches a named section and creates it if it doesn’t already exist. It only returns the section object, and thus it’s legal to immediately use that to add a NewKey(). I stared at this for some time and was simply unable to spot a defect in my usage of the library. It was only by accident that I noticed a certain behavior. If the ini file opened by ini.load() did not exist on disk, the resulting SaveTo() call would persist the keys without a section. However if the ini file did exist (even if it was an empty file) the section and it’s keys were persisted. So basically I was able to fix the defect by touching the ini file prior to running the program. Ultimately I fixed the program by having the program itself create an empty file if missing, prior to calling ini.load().

I’ll file an issue upstream just in case this is actually a defect. Here’s an example program to illustrate the behavior:

package main

import (
	"fmt"
	"gopkg.in/ini.v1"
)

func main() {
	p := "/tmp/missing-file.ini"
	cfg, err := ini.Load(p)
	fmt.Println(cfg, err)
	cfg.Section("section").NewKey("key", "value")
	cfg.SaveTo(p)
}

Running the program results in this:

$ go run test.go
&{true {{0 0} 0 0 0 0} [{/tmp/missing-file.ini}] map[] [] <nil>} open /tmp/missing-file.ini: no such file or directory
$ cat /tmp/missing-file.ini
key = value

Because the write ultimately succeeded, running the same program again has this result:

$ go run test.go
&{true {{0 0} 0 0 0 0} [{/tmp/missing-file.ini}] map[DEFAULT:0xc820018190] [DEFAULT] <nil>} <nil>
$ cat /tmp/missing-file.ini
key = value

[section]
key = value

Learning a new language takes work :)