Recently I wrote tests for my Kubernetes infrastructure in Go. These tests are split across multiple different repositories. However, there is a lot of overlap in testing logic between the test suites in each repository. In attempts to follow good programming practices and keep my code DRY, I split out the common code between the repositories into reusable functions. These functions exist in their own Go module, which is imported into the test suites as a dependency.
Go modules are part of Go's dependency management system1. They consist of a collection of packages, which are defied in a go.mod file. Go modules can be used as dependencies in other modules, as is the case with my reusable test function module and my Kubernetes test modules.
In this article, I first show how to configure a Go module, using my reusable test function module as an example. Then, I show how the reusable test function module is used in my Kubernetes test modules.
Go modules are defined in a go.mod file, similar to how a npm package is defined in a package.json file. go.mod exists in the root directory of a Go module. go.mod files have their own syntax separate from the Go programming language or any other configuration language2. The go.mod file for my reusable test function module has the following content:
module github.com/ajarombek/cloud-modules/kubernetes-test-functions
go 1.15
require (
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.3-beta.0
k8s.io/client-go v0.17.0
)
Go module files consist of multiple directives3. In the code above, module
, go
, and require
are directives.
The module
directive defines the path of the module, which is used as an identifier when it is imported into other modules. The path of my module is github.com/ajarombek/cloud-modules/kubernetes-test-functions
. Notice that the module path is similar to the modules GitHub repository URL - github.com/AJarombek/cloud-modules/tree/master/kubernetes-test-functions. This is no coincidence; the module path needs to match the location where it is hosted4. Module paths with hosting locations allow Go to find the module when its path is used. In my module path, github.com
is the hosting domain, ajarombek
is the GitHub user, cloud-modules
is the repository name, and kubernetes-test-functions
is the directory within the repository containing the go.mod file.
The go
directive specifies the version of Go that the module is written in. In my case, that version is 1.15, with the latest version of Go being 1.17 (as of October 2021).
The require
directive specifies all the Go module dependencies and their minimum versions. My Go module has three dependency modules, all of which are Kubernetes modules.
Go modules can have additional directives, but these three are the most common ones you will see. The last thing needed to configure the Go module is to add tags to the GitHub repository. These tags specify different versions of the module. For example, one of my tags is kubernetes-test-functions/v0.2.10. This tag declares the version as v0.2.10, with kubernetes-test-functions specifying the directory containing the Go module.
With a Go module created and pushed to GitHub with tags specifying different versions, it is time to use the module within another module. In a different Go module, the require
directive can be used to specify github.com/ajarombek/cloud-modules/kubernetes-test-functions
as a dependency. The following code snippet is the go.mod file from one of my Go modules which tests the Kubernetes infrastructure for jarombek.com.
module github.com/ajarombek/jarombek-com-infrastructure/test-k8s
go 1.14
require (
github.com/ajarombek/cloud-modules/kubernetes-test-functions v0.2.10
k8s.io/apimachinery v0.17.3-beta.0
k8s.io/client-go v0.17.0
)
The require
directive specifies that version 0.2.10 (at a minimum) of the github.com/ajarombek/cloud-modules/kubernetes-test-functions
module is a dependency of this Go module.
Now we can import and use the kubernetes-test-functions
module. Go modules contain one or more packages, which are collections of source code files. In Go, the import
statement consists of one or many package paths, which are strings. Using my jarombek.com Kubernetes tests as an example once again, the github.com/ajarombek/cloud-modules/kubernetes-test-functions
package is imported and given an alias with the following code:
import (
k8sfuncs "github.com/ajarombek/cloud-modules/kubernetes-test-functions"
...
)
The k8sfuncs
package alias is used to invoke functions from the package, such as the following example:
func TestJarombekComDeploymentExists(t *testing.T) {
k8sfuncs.DeploymentExists(t, ClientSet, "jarombek-com", namespace)
}
To learn more about the Kubernetes test functions themselves, you can check out my previous article on writing Kubernetes tests with Go.
Two takeaways I had while writing Go modules were how easy they are to create and the elegance of the go.mod syntax. I love how simply naming Go modules with their host domain, in my case github.com, allows Go to resolve module dependencies. In my view, a great dependency management system can enhance a programming language and make it more viable for projects. Go modules make me even more likely to use the Go programming language in the future, as it continues to climb in my programming rankings. The code shown in this article is found in my cloud-modules and jarombek-com-infrastructure repositories.