Skip to content

Commit

Permalink
Add functions useful for interacting with docker images and building …
Browse files Browse the repository at this point in the history
…remote images (#1062)

* Add functions useful for interacting with docker images

* Use repo:tag syntax

* Don't build multiarch image

* Add t.parallel call to TestGitCloneAndBuild
  • Loading branch information
yorinasub17 authored Feb 15, 2022
1 parent 08558f5 commit a231e83
Show file tree
Hide file tree
Showing 4 changed files with 222 additions and 0 deletions.
56 changes: 56 additions & 0 deletions modules/docker/build.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package docker

import (
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/gruntwork-io/terratest/modules/logger"
Expand Down Expand Up @@ -97,6 +100,59 @@ func BuildE(t testing.TestingT, path string, options *BuildOptions) error {
return nil
}

// GitCloneAndBuild builds a new Docker image from a given Git repo. This function will clone the given repo at the
// specified ref, and call the docker build command on the cloned repo from the given relative path (relative to repo
// root). This will fail the test if there are any errors.
func GitCloneAndBuild(
t testing.TestingT,
repo string,
ref string,
path string,
dockerBuildOpts *BuildOptions,
) {
require.NoError(t, GitCloneAndBuildE(t, repo, ref, path, dockerBuildOpts))
}

// GitCloneAndBuildE builds a new Docker image from a given Git repo. This function will clone the given repo at the
// specified ref, and call the docker build command on the cloned repo from the given relative path (relative to repo
// root).
func GitCloneAndBuildE(
t testing.TestingT,
repo string,
ref string,
path string,
dockerBuildOpts *BuildOptions,
) error {
workingDir, err := ioutil.TempDir("", "")
if err != nil {
return err
}
defer os.RemoveAll(workingDir)

cloneCmd := shell.Command{
Command: "git",
Args: []string{"clone", repo, workingDir},
}
if err := shell.RunCommandE(t, cloneCmd); err != nil {
return err
}

checkoutCmd := shell.Command{
Command: "git",
Args: []string{"checkout", ref},
WorkingDir: workingDir,
}
if err := shell.RunCommandE(t, checkoutCmd); err != nil {
return err
}

contextPath := filepath.Join(workingDir, path)
if err := BuildE(t, contextPath, dockerBuildOpts); err != nil {
return err
}
return nil
}

// formatDockerBuildArgs formats the arguments for the 'docker build' command.
func formatDockerBuildArgs(path string, options *BuildOptions) []string {
args := []string{}
Expand Down
26 changes: 26 additions & 0 deletions modules/docker/build_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ package docker

import (
"fmt"
"strings"
"testing"

"github.com/gruntwork-io/terratest/modules/git"
"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/random"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -60,3 +64,25 @@ func TestBuildWithTarget(t *testing.T) {
out := Run(t, tag, &RunOptions{Remove: true})
require.Contains(t, out, text1)
}

func TestGitCloneAndBuild(t *testing.T) {
t.Parallel()

uniqueID := strings.ToLower(random.UniqueId())
imageTag := "gruntwork-io-foo-test:" + uniqueID
text := "Hello, World!"

buildOpts := &BuildOptions{
Tags: []string{imageTag},
BuildArgs: []string{fmt.Sprintf("text=%s", text)},
}
gitBranchName := git.GetCurrentBranchName(t)
if gitBranchName == "" {
logger.Logf(t, "WARNING: git.GetCurrentBranchName returned an empty string; falling back to master")
gitBranchName = "master"
}
GitCloneAndBuild(t, "[email protected]:gruntwork-io/terratest.git", gitBranchName, "test/fixtures/docker", buildOpts)

out := Run(t, imageTag, &RunOptions{Remove: true})
require.Contains(t, out, text)
}
112 changes: 112 additions & 0 deletions modules/docker/images.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package docker

import (
"bufio"
"encoding/json"
"fmt"
"strings"

"github.com/gruntwork-io/terratest/modules/collections"
"github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/shell"
"github.com/gruntwork-io/terratest/modules/testing"
"github.com/stretchr/testify/require"
)

// Image represents a docker image, and exports all the fields that the docker images command returns for the
// image.
type Image struct {
// ID is the image ID in docker, and can be used to identify the image in place of the repo and tag.
ID string

// Repository is the image repository.
Repository string

// Tag is the image tag wichin the repository.
Tag string

// CreatedAt represents a timestamp for when the image was created.
CreatedAt string

// CreatedSince is a diff between when the image was created to now.
CreatedSince string

// SharedSize is the amount of space that an image shares with another one (i.e. their common data).
SharedSize string

// UniqueSize is the amount of space that is only used by a given image.
UniqueSize string

// VirtualSize is the total size of the image, combining SharedSize and UniqueSize.
VirtualSize string

// Containers represents the list of containers that are using the image.
Containers string

// Digest is the hash digest of the image, if requested.
Digest string
}

func (image Image) String() string {
return fmt.Sprintf("%s:%s", image.Repository, image.Tag)
}

// DeleteImage removes a docker image using the Docker CLI. This will fail the test if there is an error.
func DeleteImage(t testing.TestingT, img string, logger *logger.Logger) {
require.NoError(t, DeleteImageE(t, img, logger))
}

// DeleteImageE removes a docker image using the Docker CLI.
func DeleteImageE(t testing.TestingT, img string, logger *logger.Logger) error {
cmd := shell.Command{
Command: "docker",
Args: []string{"rmi", img},
Logger: logger,
}
return shell.RunCommandE(t, cmd)
}

// ListImages calls docker images using the Docker CLI to list the available images on the local docker daemon.
func ListImages(t testing.TestingT, logger *logger.Logger) []Image {
out, err := ListImagesE(t, logger)
require.NoError(t, err)
return out
}

// ListImagesE calls docker images using the Docker CLI to list the available images on the local docker daemon.
func ListImagesE(t testing.TestingT, logger *logger.Logger) ([]Image, error) {
cmd := shell.Command{
Command: "docker",
Args: []string{"images", "--format", "{{ json . }}"},
Logger: logger,
}
out, err := shell.RunCommandAndGetOutputE(t, cmd)
if err != nil {
return nil, err
}

// Parse and return the list of image objects.
images := []Image{}
scanner := bufio.NewScanner(strings.NewReader(out))
for scanner.Scan() {
line := scanner.Text()
var image Image
err := json.Unmarshal([]byte(line), &image)
if err != nil {
return nil, err
}
images = append(images, image)
}
return images, nil
}

// DoesImageExist lists the images in the docker daemon and returns true if the given image label (repo:tag) exists.
// This will fail the test if there is an error.
func DoesImageExist(t testing.TestingT, imgLabel string, logger *logger.Logger) bool {
images := ListImages(t, logger)
imageTags := []string{}
for _, image := range images {
imageTags = append(imageTags, image.String())
}
return collections.ListContains(imageTags, imgLabel)
}
28 changes: 28 additions & 0 deletions modules/docker/images_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package docker

import (
"fmt"
"strings"
"testing"

"github.com/gruntwork-io/terratest/modules/random"
"github.com/stretchr/testify/assert"
)

func TestListImagesAndDeleteImage(t *testing.T) {
t.Parallel()

uniqueID := strings.ToLower(random.UniqueId())
repo := "gruntwork-io/test-image"
tag := fmt.Sprintf("v1-%s", uniqueID)
img := fmt.Sprintf("%s:%s", repo, tag)

options := &BuildOptions{
Tags: []string{img},
}
Build(t, "../../test/fixtures/docker", options)

assert.True(t, DoesImageExist(t, img, nil))
DeleteImage(t, img, nil)
assert.False(t, DoesImageExist(t, img, nil))
}

0 comments on commit a231e83

Please sign in to comment.