Book Image

Go for DevOps

By : John Doak, David Justice
5 (1)
Book Image

Go for DevOps

5 (1)
By: John Doak, David Justice

Overview of this book

Go is the go-to language for DevOps libraries and services, and without it, achieving fast and safe automation is a challenge. With the help of Go for DevOps, you'll learn how to deliver services with ease and safety, becoming a better DevOps engineer in the process. Some of the key things this book will teach you are how to write Go software to automate configuration management, update remote machines, author custom automation in GitHub Actions, and interact with Kubernetes. As you advance through the chapters, you'll explore how to automate the cloud using software development kits (SDKs), extend HashiCorp's Terraform and Packer using Go, develop your own DevOps services with gRPC and REST, design system agents, and build robust workflow systems. By the end of this Go for DevOps book, you'll understand how to apply development principles to automate operations and provide operational insights using Go, which will allow you to react quickly to resolve system failures before your customers realize something has gone wrong.
Table of Contents (22 chapters)
1
Section 1: Getting Up and Running with Go
10
Section 2: Instrumenting, Observing, and Responding
14
Section 3: Cloud ready Go

Learning about functions

Functions in Go are what you'd expect from a modern programming language. There are only a few things that make Go functions different:

  • Multiple return values are supported
  • Variadic arguments
  • Named return values

The basic function signature is as follows:

func functionName([varName] [varType], ...) ([return value], [return value], ...){
}

Let's make a basic function that adds two numbers together and returns the result:

func add(x int, y int) int {
     return x + y
}

As you can see, this takes in two integers, x and y, adds them together, and returns the result (which is an integer). Let's show how we can call this function and print its output:

result := add(2, 2)
fmt.Println(result)

We can simplify this function signature by declaring both x and y types with a single int keyword:

func add(x, y int) int {
     return x + y
}

This is equivalent to the previous one.

Returning multiple values and named results

In Go, we can return multiple values. For example, consider a function that divides two integers and returns two variables, the result and the remainder, as follows:

func divide(num, div int) (res, rem int) {
	result = num / div
	remainder = num % div
	return res, rem
}

This code demonstrates a few new features in our function:

  • Argument num is the number to be divided
  • Argument div is the number to divide by
  • Return value res is the result of the division
  • Return value rem is the remainder of the division

First is named returns (res and rem). These variables are automatically created and ready for use inside the function.

Notice I use = and not := when doing assignments to those variables. This is because the variable already exists, and we want to assign a value (=). := means create and assign. You can only create a new variable that doesn't exist. You will also notice that now the return type is in parenthesis. You will need to use parenthesis if you use more than one return value or named returns (or in this case, both).

Calling this function is just as simple as calling add() before, as shown here:

result, remainder := divide(3, 2)
fmt.Printf("Result: %d, Remainder %d", result, remainder)

Strickly speaking, you don't have to use return to return the values. However, doing so will prevent some ugly bugs that you will eventually encounter.

Next, we will look at how we can have a variable number of arguments as function input that allows us to create functions such as fmt.Println(), which you have been using in this chapter.

Variadic arguments

A variadic argument is when you want to provide 0 to infinite arguments. A good example would be calculating a sum of integers. Without variadic arguments, you might use a slice (a growable array type, which we will talk about later), as follows:

func sum(numbers []int) int {
     sum := 0
     for _, n := range numbers {
          sum += n
     }
     return sum
}

While this is fine, using it is cumbersome:

args := []int{1,2,3,4,5}
fmt.Println(sum(args))

We can accomplish this same thing by using the variadic (...) notation:

func sum(numbers ...int) int {
     // Same code
}

numbers is still []int, but has a different calling convention that is more elegant:

fmt.Println(sum(1,2,3,4,5))

Note

You can use variadic arguments with other arguments, but it must be the last argument in the function.

Anonymous functions

Go has a concept of anonymous functions, which means a function without a name (also called a function closure).

This can be useful to take advantage of special statements that honor function boundaries, such as defer, or in goroutines. We will show how to take advantage of these for goroutines later, but for now let's show how to execute an anonymous function. This is a contrived example that is only useful in teaching the concept:

func main() {
     result := func(word1, word2 string) string {
          return word1 + " " + word2
     }("hello", "world")
     fmt.Println(result)
}

This code does the following:

  • Defines a single-use function (func(word1, word2 string) string)
  • Executes the function with the hello and world arguments
  • Assigns the string return value to the result variable
  • Prints result

Now that we have arrived at the end of this section, we have learned about how Go functions are declared, the use of multiple return values, variadic arguments for simplified function calling, and anonymous functions. Multiple return values will be important in future chapters where we deal with errors, and anonymous functions are key components of our future defer statements and for use with concurrency.

In the next section, we will explore public and private types.