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

Utilizing Go packages

Go provides reusable blocks of code that can be imported into other code using packages. Packages in Go are synonymous with libraries or modules in other languages. Packages are the building blocks of Go programs that divide the content into understandable parts.

This section will cover how to declare and import a package. We will discuss how to deal with package name conflicts, explore rules around packages, and we will write our first main package.

Declaring a package

Go divides programs into packages, sometimes called modules or libraries in other languages. Packages live on a path, and the path is made to look like a path to a directory on a Unix-like filesystem.

All Go files in a directory must belong to the same package. The package is most commonly named the same as the directory it lives in.

Declaring a package happens at the top of the file, and should only be preceded by a comment. Declaring a package is as simple as the following:

// Package main is the entrance point for our binary.
// The double slashes provides a comment until the end of the line.
/*
This is a comment that lasts until the closing star slash.
*/
package main

package main is special. All other package names declare a package that must be imported into another package to be used. package main will declare func main(), which is the starting point for a binary to run.

All Go files in a directory must have the same package header (compiler-enforced). These files, for most practical purposes, act as if they are concatenated together.

Let's say you have a directory structure as follows:

mypackage/
  file1.go
  file2.go

Then, file1.go and file2.go should have the following:

package mypackage

When mypackage is imported by another package, it will include everything declared in all files in the mypackage directory.

Importing a package

There are two general types of packages:

  • The standard library (stdlib) packages
  • All other packages

Standard library packages stand out because they don't list some repository information in their path, such as the following:

"fmt"
"encoding/json"
"archive/zip"

All other packages generally have repository information preceding them, as follows:

"github.com/johnsiilver/golib/lru"
"github.com/kylelemons/godebug/pretty"

Note

A complete listing of stdlib packages can be found at the following link: https://golang.org/pkg/.

To import packages, we use the import keyword. So, let's import the standard library fmt package and the mypackage package, which lives at github.com/devopsforgo/mypackage:

package main
import (
     "fmt"
     "github.com/devopsforgo/mypackage"
)

It is important to note that the filenames are not part of the package path, but simply the directory path.

Using a package

Once you've imported a package, you can start accessing functions, types, or variables declared in the package by prefacing what you want to access with the name of the package and a period.

For example, the fmt package has a function called Println() that can be used to print a line to stdout. If we want to use it, it is as simple as the following:

fmt.Println("Hello!")

Package name conflicts

Let's say you have two packages named mypackage. They both have the same name, so our program won't be able to tell which one we are referring to. You can rename a package import into whatever name you want:

import(
     "github.com/devopsforgo/mypackage"
     jpackage "github.com/johnsiilver/mypackage"
)

jpackage declares that in this package, we will refer to github.com/johnsiilver/mypackage as jpackage.

This ability allows us to use two similarly named packages as follows:

mypackage.Print()
jpackage.Send()

Now, we will look at an important rule around packages that improves compile-time and binary size.

Packages must be used

Let's introduce you to the following rule: If you import a package, you must use it.

One of the things that the Go authors noticed about many of the other programming languages being used at Google was that they often had unused imports.

This was leading to compile times that were longer than needed and, in some cases, binary sizes that were much bigger than required. Python files were packaged in a proprietary format to ship around production, and some of these unused imports were adding hundreds of megabytes to the files.

To prevent these types of problems, Go will not compile a program that imports a package but doesn't use it, as shown here:

package main
import (
     "fmt"
     "sync"
)
func main() {
     fmt.Println("Hello, playground")
}

The preceding code outputs the following:

./prog.go:5:2: imported and not used: "sync"

In certain rare circumstances, you may need to do a side effects import, in which just loading the package causes something to happen, but you don't use the package. This should always be done in package main and requires prepending with an underscore (_):

package main
import (
     "fmt"
     _ "sync" //Just an example 
)
func main() {
     fmt.Println("Hello, playground")
}

Next, we will declare a main package and discuss the basics of writing a Go program that imports a package.

A Go Hello World

Let's write a simple hello world program that is similar to the default program in the Go Playground. This example will demonstrate the following:

  • Declaring a package
  • Importing the fmt package from the standard library, which can print to our screen
  • Declaring the main() function of a program
  • Declaring a string variable using the := operator
  • Printing the variable to the screen

Let's see what this looks like:

1 package main
2 
3 import "fmt"
4
5 func main() {
6    hello := "Hello World!"
7    fmt.Println(hello)
8
9 }

In our first line, we declared the name of our package using the package keyword. The entrance point for any Go binary is a package named main that has a function called main().

In our third line, we import the fmt package. fmt has functions for doing string formatting and writing to various outputs.

On our fifth line, we declare a function called main that takes no arguments and returns no values. main() is special, as when a binary is run, it starts by running the main() function.

Go uses {} to show where a function starts and where a function ends (similar to C).

The sixth line declares a variable named hello using the := operator. This operator indicates that we wish to create a new variable and assign it a value in a single line. This is the most common, but not the only, way to declare a variable.

As Go is typed, so := will assign the type based on the value. In this case, it will be a string, but if the value was an integer (such as 3), it would be the int type, and if a floating-point (such as 2.4), it would be the float64 type. If we wanted to declare a specific type, such as int8 or float32, we would need some modifications (which we will talk about later).

On the seventh line, we call a function that is in the fmt package called Println. Println() will print the contents of the hello variable to stdout followed by a new line character (\n).

You will notice that the way to use a function declared in another package is to use the package name (without quotes) + a period + the name of the function. In this case, fmt.Println().

In this section, you have learned how to declare a package, import a package, what the function of the main package is, and how to write a basic Go program with a variable declaration. In the next section, we will go into some depth on declaring and using variables.