Learning Go - Delivering my first contribution to AzBrowse
Tags: go, azure
Published: 2019-12-10 11:52:53

Hi! I’m Ben Greenier — I’m an engineer at Microsoft working to create awesome open source projects with our partners. We get to [create software](https://github.com/microsoft) to help solve really tricky problems, and [share our stories](https://www.microsoft.com/developerblog) as we go. This means that as part of my job I get to play with lots of new technologies, learn how to use them, and help other folks do the same.

## What is AzBrowse

AzBrowse is an interactive CLI for interacting with Azure resources - inspired by [https://resources.azure.com](https://resources.azure.com). It was created by a few of my colleagues here at Microsoft to allow for faster Azure resource management directly from the shell. Written entirely in Go and maintained by folks down the hall, AzBrowse seemed like the best candidate for me to learn a new language and start contributing quickly.

{% github lawrencegripper/azbrowse %} 

## Learning Go

I decided I needed to learn [Go](https://golang.org) given it's increasing prominence in backend stacks like Kubernetes. As I begin to need to work with folks using these technologies, being able to understand and even modify these Go implementations will be super helpful. To begin, I read the entirety of the [Effective Go](https://golang.org/doc/effective_go.html) documentation. That was a great start, and genuinely a well written resource. However for more practical stuff, I found myself using [Go by Example](https://gobyexample.com/) quite a bit - My colleague recommended it and it doesn't disappoint. Go By Example includes little snippets of code for solving common Go problems - so not only do you get a brief description of a topic, but a fully runnable code example as well.

My first actual Go task was to add some additional regex parsing logic, and increase the test coverage of that logic. It was actually just [an open issue](https://github.com/lawrencegripper/azbrowse/issues/175) on AzBrowse that one of the core maintainers directed me to. It ended up being a pretty great place to start, given that I had an understanding of regex already, so the only new thing was Go.

## Regex in Go

AzBrowse was already using [regexp](https://golang.org/pkg/regexp/) for regex parsing, which as I understand it is the "Way to do it" in Go. I actually found the package documentation to be a little convoluted - I was surprised that the docs referenced other sections of the same doc for seemingly simple concepts. For example, [FindAllStringSubmatch](https://golang.org/pkg/regexp/#Regexp.FindAllStringSubmatch) references a definition of `All` that isn't linked nor inline. This added a tiny bit of extra friction to the development process that I wasn't expecting. That said, the library itself seems pretty feature rich - [Regex101](https://regex101.com/) even has support for it's regex pattern format. This made it easy to test the regex as I was working with it.

[![Regex101 Example](https://thepracticaldev.s3.amazonaws.com/i/oc4bu5usgqmkwc4j51oe.PNG)](https://regex101.com/r/gNqCAN/2/tests)


## Testing in Go

AzBrowse uses [testify](github.com/stretchr/testify) for authoring and running tests. This was pretty easy to get up to speed on - In fact, I didn't even need to read the docs. A few existing examples and previous experience with other test frameworks was sufficient. This made authoring and modifying tests even faster.

One challenge I did face was invalid assertion messages. When I broke the existing tests, the assertion failure messages were all the same. It was obvious to me that the assertions (all on different values) were __not__ all failing for the same reason. Can you find the issue in the code below?

```
// testCases is an array of objects, where each object contains fields:
// desc, input, output
// describing the test, providing the input, and the expected output
for _, test := range testCases {
	t.Run(test.desc, func(t *testing.T) {
		t.Parallel()
		output := stripSecretVals(test.input)
		assert.Equal(t, output, test.expected)
	})
}
```

If you can, that's awesome! If not don't worry, neither could I. The answer is lies in how [go closure scopes](https://github.com/golang/go/wiki/CommonMistakes#using-goroutines-on-loop-iterator-variables) work. As I understand it, this occurs because our actual test run occurs inside a closure, and at some point in the future. Because of that, the variable test is bound to the last value in the actual slice (`testCases`) for all assertions. 

To fix this, and make the assertion failures correct, I used [shadowing](https://yourbasic.org/golang/gotcha-shadowing-variables/) - to ensure that the value of test would be correctly bound to the right value in each iteration of the loop. 

```
// testCases is an array of objects, where each object contains fields:
// desc, input, output
// describing the test, providing the input, and the expected output
for _, test := range testCases {
	// shadow test value to ensure assertion failures
	// print the correct test.expected value
	test := test
	
	t.Run(test.desc, func(t *testing.T) {
		t.Parallel()
		output := stripSecretVals(test.input)
		assert.Equal(t, output, test.expected)
	})
}
```

After making that change, my assertions correctly displayed the right errors! That made it pretty easy to add more entries to `testCases` to add more data-driven tests to the project.

## New features

With my newly learned Go skills applied, I was able to open [a PR](https://github.com/lawrencegripper/azbrowse/pull/194) that added more masking of secret values to the `--demo` mode of AzBrowse. This mode is super useful for demos or videos, where you want to prevent displaying these sensitive values. I tried it out with my Azure subscription, and got to see everything working! The new masking is added from [v1.2.531](https://github.com/lawrencegripper/azbrowse/releases/tag/v1.2.531) and up.

## In Conclusion

Learning the basics of Go was pretty straightforward, thanks to the great documentation put together by the Go team, and some external resources on the web. I was able to transition from learning Go to contributing in under 3 days!

Thanks for reading. If you've had a chance to learn and use Go, let me know what you thought about the process in the comments below. If you'd like to support me personally, follow me on [twitch](https://twitch.tv/ben_greenier) and/or [twitter](https://twitter.com/bengreenier).

💙🌈
-Ben

## Resources

+ [AzBrowse](https://github.com/lawrencegripper/azbrowse)
+ [Effective Go](https://golang.org/doc/effective_go.html)
+ [Go by Example](https://gobyexample.com)
+ [Testify](github.com/stretchr/testify)
+ Title Photo by [小谢](https://unsplash.com/@guogete?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText) on [Unsplash](https://unsplash.com/s/photos/keyboard?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText)