Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions useful for interacting with docker images and building remote images #1062

Merged
merged 4 commits into from
Feb 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

@denis256 denis256 Feb 11, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

t.Parallel() is missing by intention?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good catch! Was not intentional. Added in! 443f3f8

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))
}