Merged in feature/testingopts (pull request #129)
Testing Options and Clean Up * bitsandbobs * lint * healthchecks * imgintests * singlegenerate * testclean * network * networkclean * cmds
This commit is contained in:
+202
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
terratest
|
||||
Copyright 2018 Gruntwork, Inc.
|
||||
|
||||
This product includes software developed at Gruntwork (https://www.gruntwork.io/).
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package collections allows to interact with lists of things.
|
||||
package collections
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
package collections
|
||||
|
||||
import "fmt"
|
||||
|
||||
// SliceValueNotFoundError is returned when a provided values file input is not found on the host path.
|
||||
type SliceValueNotFoundError struct {
|
||||
sourceString string
|
||||
}
|
||||
|
||||
func (err SliceValueNotFoundError) Error() string {
|
||||
return fmt.Sprintf("Could not resolve requested slice value from string %s", err.sourceString)
|
||||
}
|
||||
|
||||
// NewSliceValueNotFoundError creates a new slice found error
|
||||
func NewSliceValueNotFoundError(sourceString string) SliceValueNotFoundError {
|
||||
return SliceValueNotFoundError{sourceString}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package collections
|
||||
|
||||
// ListIntersection returns all the items in both list1 and list2. Note that this will dedup the items so that the
|
||||
// output is more predictable. Otherwise, the end list depends on which list was used as the base.
|
||||
func ListIntersection(list1 []string, list2 []string) []string {
|
||||
out := []string{}
|
||||
|
||||
// Only need to iterate list1, because we want items in both lists, not union.
|
||||
for _, item := range list1 {
|
||||
if ListContains(list2, item) && !ListContains(out, item) {
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ListSubtract removes all the items in list2 from list1.
|
||||
func ListSubtract(list1 []string, list2 []string) []string {
|
||||
out := []string{}
|
||||
|
||||
for _, item := range list1 {
|
||||
if !ListContains(list2, item) {
|
||||
out = append(out, item)
|
||||
}
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// ListContains returns true if the given list of strings (haystack) contains the given string (needle).
|
||||
func ListContains(haystack []string, needle string) bool {
|
||||
for _, str := range haystack {
|
||||
if needle == str {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
package collections
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetSliceLastValueE will take a source string and returns the last value when split by the separator char.
|
||||
func GetSliceLastValueE(source string, separator string) (string, error) {
|
||||
if len(source) > 0 && len(separator) > 0 && strings.Contains(source, separator) {
|
||||
tmp := strings.Split(source, separator)
|
||||
return tmp[len(tmp)-1], nil
|
||||
}
|
||||
return "", NewSliceValueNotFoundError(source)
|
||||
}
|
||||
|
||||
// GetSliceIndexValueE will take a source string and returns the value at the given index when split by
|
||||
// the separator char.
|
||||
func GetSliceIndexValueE(source string, separator string, index int) (string, error) {
|
||||
if len(source) > 0 && len(separator) > 0 && strings.Contains(source, separator) && index >= 0 {
|
||||
tmp := strings.Split(source, separator)
|
||||
if index > len(tmp) {
|
||||
return "", NewSliceValueNotFoundError(source)
|
||||
}
|
||||
return tmp[index], nil
|
||||
}
|
||||
return "", NewSliceValueNotFoundError(source)
|
||||
}
|
||||
+222
@@ -0,0 +1,222 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/logger"
|
||||
"github.com/gruntwork-io/terratest/modules/shell"
|
||||
"github.com/gruntwork-io/terratest/modules/testing"
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// BuildOptions defines options that can be passed to the 'docker build' command.
|
||||
type BuildOptions struct {
|
||||
// Tags for the Docker image
|
||||
Tags []string
|
||||
|
||||
// Build args to pass the 'docker build' command
|
||||
BuildArgs []string
|
||||
|
||||
// Target build arg to pass to the 'docker build' command
|
||||
Target string
|
||||
|
||||
// All architectures to target in a multiarch build. Configuring this variable will cause terratest to use docker
|
||||
// buildx to construct multiarch images.
|
||||
// You can read more about multiarch docker builds in the official documentation for buildx:
|
||||
// https://docs.docker.com/buildx/working-with-buildx/
|
||||
// NOTE: This list does not automatically include the current platform. For example, if you are building images on
|
||||
// an Apple Silicon based MacBook, and you configure this variable to []string{"linux/amd64"} to build an amd64
|
||||
// image, the buildx command will not automatically include linux/arm64 - you must include that explicitly.
|
||||
Architectures []string
|
||||
|
||||
// Whether or not to push images directly to the registry on build. Note that for multiarch images (Architectures is
|
||||
// not empty), this must be true to ensure availability of all architectures - only the image for the current
|
||||
// platform will be loaded into the daemon (due to a limitation of the docker daemon), so you won't be able to run a
|
||||
// `docker push` command later to push the multiarch image.
|
||||
// See https://github.com/moby/moby/pull/38738 for more info on the limitation of multiarch images in docker daemon.
|
||||
Push bool
|
||||
|
||||
// Whether or not to load the image into the docker daemon at the end of a multiarch build so that it can be used
|
||||
// locally. Note that this is only used when Architectures is set, and assumes the current architecture is already
|
||||
// included in the Architectures list.
|
||||
Load bool
|
||||
|
||||
// Custom CLI options that will be passed as-is to the 'docker build' command. This is an "escape hatch" that allows
|
||||
// Terratest to not have to support every single command-line option offered by the 'docker build' command, and
|
||||
// solely focus on the most important ones.
|
||||
OtherOptions []string
|
||||
|
||||
// Whether ot not to enable buildkit. You can find more information about buildkit here https://docs.docker.com/build/buildkit/#getting-started.
|
||||
EnableBuildKit bool
|
||||
|
||||
// Additional environment variables to pass in when running docker build command.
|
||||
Env map[string]string
|
||||
|
||||
// Set a logger that should be used. See the logger package for more info.
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// Build runs the 'docker build' command at the given path with the given options and fails the test if there are any
|
||||
// errors.
|
||||
func Build(t testing.TestingT, path string, options *BuildOptions) {
|
||||
require.NoError(t, BuildE(t, path, options))
|
||||
}
|
||||
|
||||
// BuildE runs the 'docker build' command at the given path with the given options and returns any errors.
|
||||
func BuildE(t testing.TestingT, path string, options *BuildOptions) error {
|
||||
options.Logger.Logf(t, "Running 'docker build' in %s", path)
|
||||
|
||||
env := make(map[string]string)
|
||||
if options.Env != nil {
|
||||
env = options.Env
|
||||
}
|
||||
|
||||
if options.EnableBuildKit {
|
||||
env["DOCKER_BUILDKIT"] = "1"
|
||||
}
|
||||
|
||||
cmd := shell.Command{
|
||||
Command: "docker",
|
||||
Args: formatDockerBuildArgs(path, options),
|
||||
Logger: options.Logger,
|
||||
Env: env,
|
||||
}
|
||||
|
||||
if err := shell.RunCommandE(t, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For non multiarch images, we need to call docker push for each tag since build does not have a push option like
|
||||
// buildx.
|
||||
if len(options.Architectures) == 0 && options.Push {
|
||||
var errorsOccurred = new(multierror.Error)
|
||||
for _, tag := range options.Tags {
|
||||
if err := PushE(t, options.Logger, tag); err != nil {
|
||||
options.Logger.Logf(t, "ERROR: error pushing tag %s", tag)
|
||||
errorsOccurred = multierror.Append(err)
|
||||
}
|
||||
}
|
||||
return errorsOccurred.ErrorOrNil()
|
||||
}
|
||||
|
||||
// For multiarch images, if a load is requested call the load command to export the built image into the daemon.
|
||||
if len(options.Architectures) > 0 && options.Load {
|
||||
loadCmd := shell.Command{
|
||||
Command: "docker",
|
||||
Args: formatDockerBuildxLoadArgs(path, options),
|
||||
Logger: options.Logger,
|
||||
}
|
||||
return shell.RunCommandE(t, loadCmd)
|
||||
}
|
||||
|
||||
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 := os.MkdirTemp("", "")
|
||||
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{}
|
||||
|
||||
if len(options.Architectures) > 0 {
|
||||
args = append(
|
||||
args,
|
||||
"buildx",
|
||||
"build",
|
||||
"--platform",
|
||||
strings.Join(options.Architectures, ","),
|
||||
)
|
||||
if options.Push {
|
||||
args = append(args, "--push")
|
||||
}
|
||||
} else {
|
||||
args = append(args, "build")
|
||||
}
|
||||
|
||||
return append(args, formatDockerBuildBaseArgs(path, options)...)
|
||||
}
|
||||
|
||||
// formatDockerBuildxLoadArgs formats the arguments for calling load on the 'docker buildx' command.
|
||||
func formatDockerBuildxLoadArgs(path string, options *BuildOptions) []string {
|
||||
args := []string{
|
||||
"buildx",
|
||||
"build",
|
||||
"--load",
|
||||
}
|
||||
return append(args, formatDockerBuildBaseArgs(path, options)...)
|
||||
}
|
||||
|
||||
// formatDockerBuildBaseArgs formats the common args for the build command, both for `build` and `buildx`.
|
||||
func formatDockerBuildBaseArgs(path string, options *BuildOptions) []string {
|
||||
args := []string{}
|
||||
for _, tag := range options.Tags {
|
||||
args = append(args, "--tag", tag)
|
||||
}
|
||||
|
||||
for _, arg := range options.BuildArgs {
|
||||
args = append(args, "--build-arg", arg)
|
||||
}
|
||||
|
||||
if len(options.Target) > 0 {
|
||||
args = append(args, "--target", options.Target)
|
||||
}
|
||||
|
||||
args = append(args, options.OtherOptions...)
|
||||
|
||||
args = append(args, path)
|
||||
return args
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package docker allows to interact with Docker and docker compose resources.
|
||||
package docker
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
"gotest.tools/v3/icmd"
|
||||
)
|
||||
|
||||
// Options are Docker options.
|
||||
type Options struct {
|
||||
WorkingDir string
|
||||
EnvVars map[string]string
|
||||
|
||||
// Whether ot not to enable buildkit. You can find more information about buildkit here https://docs.docker.com/build/buildkit/#getting-started.
|
||||
EnableBuildKit bool
|
||||
|
||||
// Set a logger that should be used. See the logger package for more info.
|
||||
Logger *logger.Logger
|
||||
ProjectName string
|
||||
}
|
||||
|
||||
// RunDockerCompose runs docker compose with the given arguments and options and return stdout/stderr.
|
||||
func RunDockerCompose(t testing.TestingT, options *Options, args ...string) string {
|
||||
out, err := runDockerComposeE(t, false, options, args...)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// RunDockerComposeAndGetStdout runs docker compose with the given arguments and options and returns only stdout.
|
||||
func RunDockerComposeAndGetStdOut(t testing.TestingT, options *Options, args ...string) string {
|
||||
out, err := runDockerComposeE(t, true, options, args...)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
|
||||
// RunDockerComposeE runs docker compose with the given arguments and options and return stdout/stderr.
|
||||
func RunDockerComposeE(t testing.TestingT, options *Options, args ...string) (string, error) {
|
||||
return runDockerComposeE(t, false, options, args...)
|
||||
}
|
||||
|
||||
func runDockerComposeE(t testing.TestingT, stdout bool, options *Options, args ...string) (string, error) {
|
||||
var cmd shell.Command
|
||||
|
||||
projectName := options.ProjectName
|
||||
if len(projectName) <= 0 {
|
||||
projectName = strings.ToLower(t.Name())
|
||||
}
|
||||
|
||||
dockerComposeVersionCmd := icmd.Command("docker", "compose", "version")
|
||||
result := icmd.RunCmd(dockerComposeVersionCmd)
|
||||
|
||||
if options.EnableBuildKit {
|
||||
if options.EnvVars == nil {
|
||||
options.EnvVars = make(map[string]string)
|
||||
}
|
||||
|
||||
options.EnvVars["DOCKER_BUILDKIT"] = "1"
|
||||
options.EnvVars["COMPOSE_DOCKER_CLI_BUILD"] = "1"
|
||||
}
|
||||
|
||||
if result.ExitCode == 0 {
|
||||
cmd = shell.Command{
|
||||
Command: "docker",
|
||||
Args: append([]string{"compose", "--project-name", generateValidDockerComposeProjectName(projectName)}, args...),
|
||||
WorkingDir: options.WorkingDir,
|
||||
Env: options.EnvVars,
|
||||
Logger: options.Logger,
|
||||
}
|
||||
} else {
|
||||
cmd = shell.Command{
|
||||
Command: "docker-compose",
|
||||
// We append --project-name to ensure containers from multiple different tests using Docker Compose don't end
|
||||
// up in the same project and end up conflicting with each other.
|
||||
Args: append([]string{"--project-name", generateValidDockerComposeProjectName(projectName)}, args...),
|
||||
WorkingDir: options.WorkingDir,
|
||||
Env: options.EnvVars,
|
||||
Logger: options.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
if stdout {
|
||||
return shell.RunCommandAndGetStdOut(t, cmd), nil
|
||||
}
|
||||
|
||||
return shell.RunCommandAndGetOutputE(t, cmd)
|
||||
}
|
||||
|
||||
// Note: docker-compose command doesn't like lower case or special characters, other than -.
|
||||
func generateValidDockerComposeProjectName(str string) string {
|
||||
lower_str := strings.ToLower(str)
|
||||
return regexp.MustCompile(`[^a-zA-Z0-9 ]+`).ReplaceAllString(lower_str, "-")
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetDockerHost returns the name or address of the host on which the Docker engine is running.
|
||||
func GetDockerHost() string {
|
||||
return getDockerHostFromEnv(os.Environ())
|
||||
}
|
||||
|
||||
func getDockerHostFromEnv(env []string) string {
|
||||
// Parses the DOCKER_HOST environment variable to find the address
|
||||
//
|
||||
// For valid formats see:
|
||||
// https://github.com/docker/cli/blob/6916b427a0b07e8581d121967633235ced6db9a1/opts/hosts.go#L69
|
||||
var dockerUrl []string
|
||||
|
||||
for _, item := range env {
|
||||
envVar := strings.Split(item, "=")
|
||||
if len(envVar) == 2 && envVar[0] == "DOCKER_HOST" {
|
||||
dockerUrl = strings.Split(envVar[1], ":")
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if len(dockerUrl) < 2 {
|
||||
// DOCKER_HOST was empty, not present or not a valid URL
|
||||
return "localhost"
|
||||
}
|
||||
|
||||
switch dockerUrl[0] {
|
||||
case "tcp", "ssh", "fd":
|
||||
return strings.TrimPrefix(dockerUrl[1], "//")
|
||||
default:
|
||||
// if DOCKER_HOST is not in one of the formats listed above, return default
|
||||
return "localhost"
|
||||
}
|
||||
}
|
||||
+112
@@ -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)
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/logger"
|
||||
"github.com/gruntwork-io/terratest/modules/shell"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// ContainerInspect defines the output of the Inspect method, with the options returned by 'docker inspect'
|
||||
// converted into a more friendly and testable interface
|
||||
type ContainerInspect struct {
|
||||
// ID of the inspected container
|
||||
ID string
|
||||
|
||||
// Name of the inspected container
|
||||
Name string
|
||||
|
||||
// time.Time that the container was created
|
||||
Created time.Time
|
||||
|
||||
// String representing the container's status
|
||||
Status string
|
||||
|
||||
// Whether the container is currently running or not
|
||||
Running bool
|
||||
|
||||
// Container's exit code
|
||||
ExitCode uint8
|
||||
|
||||
// String with the container's error message, if there is any
|
||||
Error string
|
||||
|
||||
// Ports exposed by the container
|
||||
Ports []Port
|
||||
|
||||
// Volume bindings made to the container
|
||||
Binds []VolumeBind
|
||||
|
||||
// Health check
|
||||
Health HealthCheck
|
||||
}
|
||||
|
||||
// Port represents a single port mapping exported by the container
|
||||
type Port struct {
|
||||
HostPort uint16
|
||||
ContainerPort uint16
|
||||
Protocol string
|
||||
}
|
||||
|
||||
// VolumeBind represents a single volume binding made to the container
|
||||
type VolumeBind struct {
|
||||
Source string
|
||||
Destination string
|
||||
}
|
||||
|
||||
// HealthCheck represents the current health history of the container
|
||||
type HealthCheck struct {
|
||||
// Health check status
|
||||
Status string
|
||||
|
||||
// Current count of failing health checks
|
||||
FailingStreak uint8
|
||||
|
||||
// Log of failures
|
||||
Log []HealthLog
|
||||
}
|
||||
|
||||
// HealthLog represents the output of a single Health check of the container
|
||||
type HealthLog struct {
|
||||
// Start time of health check
|
||||
Start string
|
||||
|
||||
// End time of health check
|
||||
End string
|
||||
|
||||
// Exit code of health check
|
||||
ExitCode uint8
|
||||
|
||||
// Output of health check
|
||||
Output string
|
||||
}
|
||||
|
||||
// inspectOutput defines options that will be returned by 'docker inspect', in JSON format.
|
||||
// Not all options are included here, only the ones that we might need
|
||||
type inspectOutput struct {
|
||||
Id string
|
||||
Created string
|
||||
Name string
|
||||
State struct {
|
||||
Health HealthCheck
|
||||
Status string
|
||||
Running bool
|
||||
ExitCode uint8
|
||||
Error string
|
||||
}
|
||||
NetworkSettings struct {
|
||||
Ports map[string][]struct {
|
||||
HostIp string
|
||||
HostPort string
|
||||
}
|
||||
}
|
||||
HostConfig struct {
|
||||
Binds []string
|
||||
}
|
||||
}
|
||||
|
||||
// Inspect runs the 'docker inspect {container id}' command and returns a ContainerInspect
|
||||
// struct, converted from the output JSON, along with any errors
|
||||
func Inspect(t *testing.T, id string) *ContainerInspect {
|
||||
out, err := InspectE(t, id)
|
||||
require.NoError(t, err)
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
// InspectE runs the 'docker inspect {container id}' command and returns a ContainerInspect
|
||||
// struct, converted from the output JSON, along with any errors
|
||||
func InspectE(t *testing.T, id string) (*ContainerInspect, error) {
|
||||
cmd := shell.Command{
|
||||
Command: "docker",
|
||||
Args: []string{"container", "inspect", id},
|
||||
// inspect is a short-running command, don't print the output.
|
||||
Logger: logger.Discard,
|
||||
}
|
||||
|
||||
out, err := shell.RunCommandAndGetStdOutE(t, cmd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var containers []inspectOutput
|
||||
err = json.Unmarshal([]byte(out), &containers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(containers) == 0 {
|
||||
return nil, fmt.Errorf("no container found with ID %s", id)
|
||||
}
|
||||
|
||||
container := containers[0]
|
||||
|
||||
return transformContainer(t, container)
|
||||
}
|
||||
|
||||
// transformContainerPorts converts 'docker inspect' output JSON into a more friendly and testable format
|
||||
func transformContainer(t *testing.T, container inspectOutput) (*ContainerInspect, error) {
|
||||
name := strings.TrimLeft(container.Name, "/")
|
||||
|
||||
ports, err := transformContainerPorts(container)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
volumes := transformContainerVolumes(container)
|
||||
|
||||
created, err := time.Parse(time.RFC3339Nano, container.Created)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
inspect := ContainerInspect{
|
||||
ID: container.Id,
|
||||
Name: name,
|
||||
Created: created,
|
||||
Status: container.State.Status,
|
||||
Running: container.State.Running,
|
||||
ExitCode: container.State.ExitCode,
|
||||
Error: container.State.Error,
|
||||
Ports: ports,
|
||||
Binds: volumes,
|
||||
Health: HealthCheck{
|
||||
Status: container.State.Health.Status,
|
||||
FailingStreak: container.State.Health.FailingStreak,
|
||||
Log: container.State.Health.Log,
|
||||
},
|
||||
}
|
||||
|
||||
return &inspect, nil
|
||||
}
|
||||
|
||||
// transformContainerPorts converts Docker's ports from the following json into a more testable format
|
||||
//
|
||||
// {
|
||||
// "80/tcp": [
|
||||
// {
|
||||
// "HostIp": ""
|
||||
// "HostPort": "8080"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
func transformContainerPorts(container inspectOutput) ([]Port, error) {
|
||||
var ports []Port
|
||||
|
||||
cPorts := container.NetworkSettings.Ports
|
||||
|
||||
for key, portBinding := range cPorts {
|
||||
split := strings.Split(key, "/")
|
||||
|
||||
containerPort, err := strconv.ParseUint(split[0], 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var protocol string
|
||||
if len(split) > 1 {
|
||||
protocol = split[1]
|
||||
}
|
||||
|
||||
for _, port := range portBinding {
|
||||
hostPort, err := strconv.ParseUint(port.HostPort, 10, 16)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ports = append(ports, Port{
|
||||
HostPort: uint16(hostPort),
|
||||
ContainerPort: uint16(containerPort),
|
||||
Protocol: protocol,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return ports, nil
|
||||
}
|
||||
|
||||
// GetExposedHostPort returns an exposed host port according to requested container port. Returns 0 if the requested port is not exposed.
|
||||
func (inspectOutput ContainerInspect) GetExposedHostPort(containerPort uint16) uint16 {
|
||||
for _, port := range inspectOutput.Ports {
|
||||
if port.ContainerPort == containerPort {
|
||||
return port.HostPort
|
||||
}
|
||||
}
|
||||
return uint16(0)
|
||||
}
|
||||
|
||||
// transformContainerVolumes converts Docker's volume bindings from the
|
||||
// format "/foo/bar:/foo/baz" into a more testable one
|
||||
func transformContainerVolumes(container inspectOutput) []VolumeBind {
|
||||
binds := container.HostConfig.Binds
|
||||
volumes := make([]VolumeBind, 0, len(binds))
|
||||
|
||||
for _, bind := range binds {
|
||||
var source, dest string
|
||||
|
||||
split := strings.Split(bind, ":")
|
||||
|
||||
// Considering it as an unbound volume
|
||||
dest = split[0]
|
||||
|
||||
if len(split) == 2 {
|
||||
source = split[0]
|
||||
dest = split[1]
|
||||
}
|
||||
|
||||
volumes = append(volumes, VolumeBind{
|
||||
Source: source,
|
||||
Destination: dest,
|
||||
})
|
||||
}
|
||||
|
||||
return volumes
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// Push runs the 'docker push' command to push the given tag. This will fail the test if there are any errors.
|
||||
func Push(t testing.TestingT, logger *logger.Logger, tag string) {
|
||||
require.NoError(t, PushE(t, logger, tag))
|
||||
}
|
||||
|
||||
// PushE runs the 'docker push' command to push the given tag.
|
||||
func PushE(t testing.TestingT, logger *logger.Logger, tag string) error {
|
||||
logger.Logf(t, "Running 'docker push' for tag %s", tag)
|
||||
|
||||
cmd := shell.Command{
|
||||
Command: "docker",
|
||||
Args: []string{"push", tag},
|
||||
Logger: logger,
|
||||
}
|
||||
return shell.RunCommandE(t, cmd)
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
// RunOptions defines options that can be passed to the 'docker run' command.
|
||||
type RunOptions struct {
|
||||
// Override the default COMMAND of the Docker image
|
||||
Command []string
|
||||
|
||||
// If set to true, pass the --detach flag to 'docker run' to run the container in the background
|
||||
Detach bool
|
||||
|
||||
// Override the default ENTRYPOINT of the Docker image
|
||||
Entrypoint string
|
||||
|
||||
// Set environment variables
|
||||
EnvironmentVariables []string
|
||||
|
||||
// If set to true, pass the --init flag to 'docker run' to run an init inside the container that forwards signals
|
||||
// and reaps processes
|
||||
Init bool
|
||||
|
||||
// Assign a name to the container
|
||||
Name string
|
||||
|
||||
// Set platform
|
||||
Platform string
|
||||
|
||||
// If set to true, pass the --privileged flag to 'docker run' to give extended privileges to the container
|
||||
Privileged bool
|
||||
|
||||
// If set to true, pass the --rm flag to 'docker run' to automatically remove the container when it exits
|
||||
Remove bool
|
||||
|
||||
// If set to true, pass the -tty flag to 'docker run' to allocate a pseudo-TTY
|
||||
Tty bool
|
||||
|
||||
// Username or UID
|
||||
User string
|
||||
|
||||
// Bind mount these volume(s) when running the container
|
||||
Volumes []string
|
||||
|
||||
// Custom CLI options that will be passed as-is to the 'docker run' command. This is an "escape hatch" that allows
|
||||
// Terratest to not have to support every single command-line option offered by the 'docker run' command, and
|
||||
// solely focus on the most important ones.
|
||||
OtherOptions []string
|
||||
|
||||
// Set a logger that should be used. See the logger package for more info.
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// Run runs the 'docker run' command on the given image with the given options and return stdout/stderr. This method
|
||||
// fails the test if there are any errors.
|
||||
func Run(t testing.TestingT, image string, options *RunOptions) string {
|
||||
out, err := RunE(t, image, options)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
|
||||
// RunE runs the 'docker run' command on the given image with the given options and return stdout/stderr, or any error.
|
||||
func RunE(t testing.TestingT, image string, options *RunOptions) (string, error) {
|
||||
options.Logger.Logf(t, "Running 'docker run' on image '%s'", image)
|
||||
|
||||
args, err := formatDockerRunArgs(image, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cmd := shell.Command{
|
||||
Command: "docker",
|
||||
Args: args,
|
||||
Logger: options.Logger,
|
||||
}
|
||||
|
||||
return shell.RunCommandAndGetOutputE(t, cmd)
|
||||
}
|
||||
|
||||
// RunAndGetID runs the 'docker run' command on the given image with the given options and returns the container ID
|
||||
// that is returned in stdout. This method fails the test if there are any errors.
|
||||
func RunAndGetID(t testing.TestingT, image string, options *RunOptions) string {
|
||||
out, err := RunAndGetIDE(t, image, options)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
|
||||
// RunAndGetIDE runs the 'docker run' command on the given image with the given options and returns the container ID
|
||||
// that is returned in stdout, or any error.
|
||||
func RunAndGetIDE(t testing.TestingT, image string, options *RunOptions) (string, error) {
|
||||
options.Logger.Logf(t, "Running 'docker run' on image '%s', returning stdout", image)
|
||||
|
||||
args, err := formatDockerRunArgs(image, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cmd := shell.Command{
|
||||
Command: "docker",
|
||||
Args: args,
|
||||
Logger: options.Logger,
|
||||
}
|
||||
|
||||
return shell.RunCommandAndGetStdOutE(t, cmd)
|
||||
}
|
||||
|
||||
// formatDockerRunArgs formats the arguments for the 'docker run' command.
|
||||
func formatDockerRunArgs(image string, options *RunOptions) ([]string, error) {
|
||||
args := []string{"run"}
|
||||
|
||||
if options.Detach {
|
||||
args = append(args, "--detach")
|
||||
}
|
||||
|
||||
if options.Entrypoint != "" {
|
||||
args = append(args, "--entrypoint", options.Entrypoint)
|
||||
}
|
||||
|
||||
for _, envVar := range options.EnvironmentVariables {
|
||||
args = append(args, "--env", envVar)
|
||||
}
|
||||
|
||||
if options.Init {
|
||||
args = append(args, "--init")
|
||||
}
|
||||
|
||||
if options.Name != "" {
|
||||
args = append(args, "--name", options.Name)
|
||||
}
|
||||
|
||||
if options.Platform != "" {
|
||||
args = append(args, "--platform", options.Platform)
|
||||
}
|
||||
|
||||
if options.Privileged {
|
||||
args = append(args, "--privileged")
|
||||
}
|
||||
|
||||
if options.Remove {
|
||||
args = append(args, "--rm")
|
||||
}
|
||||
|
||||
if options.Tty {
|
||||
args = append(args, "--tty")
|
||||
}
|
||||
|
||||
if options.User != "" {
|
||||
args = append(args, "--user", options.User)
|
||||
}
|
||||
|
||||
for _, volume := range options.Volumes {
|
||||
args = append(args, "--volume", volume)
|
||||
}
|
||||
|
||||
args = append(args, options.OtherOptions...)
|
||||
|
||||
args = append(args, image)
|
||||
|
||||
args = append(args, options.Command...)
|
||||
|
||||
return args, nil
|
||||
}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
// StopOptions defines the options that can be passed to the 'docker stop' command
|
||||
type StopOptions struct {
|
||||
// Seconds to wait for stop before killing the container (default 10)
|
||||
Time int
|
||||
|
||||
// Set a logger that should be used. See the logger package for more info.
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// Stop runs the 'docker stop' command for the given containers and return the stdout/stderr. This method fails
|
||||
// the test if there are any errors
|
||||
func Stop(t testing.TestingT, containers []string, options *StopOptions) string {
|
||||
out, err := StopE(t, containers, options)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
|
||||
// StopE runs the 'docker stop' command for the given containers and returns any errors.
|
||||
func StopE(t testing.TestingT, containers []string, options *StopOptions) (string, error) {
|
||||
options.Logger.Logf(t, "Running 'docker stop' on containers '%s'", containers)
|
||||
|
||||
args, err := formatDockerStopArgs(containers, options)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
cmd := shell.Command{
|
||||
Command: "docker",
|
||||
Args: args,
|
||||
Logger: options.Logger,
|
||||
}
|
||||
|
||||
return shell.RunCommandAndGetOutputE(t, cmd)
|
||||
|
||||
}
|
||||
|
||||
// formatDockerStopArgs formats the arguments for the 'docker stop' command
|
||||
func formatDockerStopArgs(containers []string, options *StopOptions) ([]string, error) {
|
||||
args := []string{"stop"}
|
||||
|
||||
if options.Time != 0 {
|
||||
args = append(args, "--time", strconv.Itoa(options.Time))
|
||||
}
|
||||
|
||||
args = append(args, containers...)
|
||||
|
||||
return args, nil
|
||||
}
|
||||
+178
@@ -0,0 +1,178 @@
|
||||
// Package logger contains different methods to log.
|
||||
package logger
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
gotesting "testing"
|
||||
"time"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/testing"
|
||||
)
|
||||
|
||||
var (
|
||||
// Default is the default logger that is used for the Logf function, if no one is provided. It uses the
|
||||
// TerratestLogger to log messages. This can be overwritten to change the logging globally.
|
||||
Default = New(terratestLogger{})
|
||||
// Discard discards all logging.
|
||||
Discard = New(discardLogger{})
|
||||
// Terratest logs the given format and arguments, formatted using fmt.Sprintf, to stdout, along with a timestamp and
|
||||
// information about what test and file is doing the logging. Before Go 1.14, this is an alternative to t.Logf as it
|
||||
// logs to stdout immediately, rather than buffering all log output and only displaying it at the very end of the test.
|
||||
// This is useful because:
|
||||
//
|
||||
// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
|
||||
// right away, rather than at the very end of the test run.
|
||||
//
|
||||
// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
|
||||
// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
|
||||
// output available.
|
||||
//
|
||||
// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
|
||||
// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
|
||||
// this log method, you get log output continuously.
|
||||
//
|
||||
Terratest = New(terratestLogger{})
|
||||
// TestingT can be used to use Go's testing.T to log. If this is used, but no testing.T is provided, it will fallback
|
||||
// to Default.
|
||||
TestingT = New(testingT{})
|
||||
)
|
||||
|
||||
type TestLogger interface {
|
||||
Logf(t testing.TestingT, format string, args ...interface{})
|
||||
}
|
||||
|
||||
type Logger struct {
|
||||
l TestLogger
|
||||
}
|
||||
|
||||
func New(l TestLogger) *Logger {
|
||||
return &Logger{
|
||||
l,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *Logger) Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
if tt, ok := t.(helper); ok {
|
||||
tt.Helper()
|
||||
}
|
||||
|
||||
// methods can be called on (typed) nil pointers. In this case, use the Default function to log. This enables the
|
||||
// caller to do `var l *Logger` and then use the logger already.
|
||||
if l == nil || l.l == nil {
|
||||
Default.Logf(t, format, args...)
|
||||
return
|
||||
}
|
||||
|
||||
l.l.Logf(t, format, args...)
|
||||
}
|
||||
|
||||
// helper is used to mark this library as a "helper", and thus not appearing in the line numbers. testing.T implements
|
||||
// this interface, for example.
|
||||
type helper interface {
|
||||
Helper()
|
||||
}
|
||||
|
||||
type discardLogger struct{}
|
||||
|
||||
func (_ discardLogger) Logf(_ testing.TestingT, format string, args ...interface{}) {}
|
||||
|
||||
type testingT struct{}
|
||||
|
||||
func (_ testingT) Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
// this should never fail
|
||||
tt, ok := t.(*gotesting.T)
|
||||
if !ok {
|
||||
// fallback
|
||||
DoLog(t, 2, os.Stdout, fmt.Sprintf(format, args...))
|
||||
return
|
||||
}
|
||||
|
||||
tt.Helper()
|
||||
tt.Logf(format, args...)
|
||||
return
|
||||
}
|
||||
|
||||
type terratestLogger struct{}
|
||||
|
||||
func (_ terratestLogger) Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
DoLog(t, 3, os.Stdout, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Deprecated: use Logger instead, as it provides more flexibility on logging.
|
||||
// Logf logs the given format and arguments, formatted using fmt.Sprintf, to stdout, along with a timestamp and information
|
||||
// about what test and file is doing the logging. Before Go 1.14, this is an alternative to t.Logf as it logs to stdout
|
||||
// immediately, rather than buffering all log output and only displaying it at the very end of the test. This is useful
|
||||
// because:
|
||||
//
|
||||
// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
|
||||
// right away, rather than at the very end of the test run.
|
||||
//
|
||||
// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
|
||||
// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
|
||||
// output available.
|
||||
//
|
||||
// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
|
||||
// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
|
||||
// this log method, you get log output continuously.
|
||||
//
|
||||
// Although t.Logf now supports streaming output since Go 1.14, this is kept for compatibility purposes.
|
||||
func Logf(t testing.TestingT, format string, args ...interface{}) {
|
||||
if tt, ok := t.(helper); ok {
|
||||
tt.Helper()
|
||||
}
|
||||
|
||||
mutexStdout.Lock()
|
||||
defer mutexStdout.Unlock()
|
||||
DoLog(t, 2, os.Stdout, fmt.Sprintf(format, args...))
|
||||
}
|
||||
|
||||
// Log logs the given arguments to stdout, along with a timestamp and information about what test and file is doing the
|
||||
// logging. This is an alternative to t.Logf that logs to stdout immediately, rather than buffering all log output and
|
||||
// only displaying it at the very end of the test. See the Logf method for more info.
|
||||
func Log(t testing.TestingT, args ...interface{}) {
|
||||
if tt, ok := t.(helper); ok {
|
||||
tt.Helper()
|
||||
}
|
||||
|
||||
mutexStdout.Lock()
|
||||
defer mutexStdout.Unlock()
|
||||
DoLog(t, 2, os.Stdout, args...)
|
||||
}
|
||||
|
||||
var mutexStdout sync.Mutex
|
||||
|
||||
// DoLog logs the given arguments to the given writer, along with a timestamp and information about what test and file is
|
||||
// doing the logging.
|
||||
func DoLog(t testing.TestingT, callDepth int, writer io.Writer, args ...interface{}) {
|
||||
date := time.Now()
|
||||
prefix := fmt.Sprintf("%s %s %s:", t.Name(), date.Format(time.RFC3339), CallerPrefix(callDepth+1))
|
||||
allArgs := append([]interface{}{prefix}, args...)
|
||||
fmt.Fprintln(writer, allArgs...)
|
||||
}
|
||||
|
||||
// CallerPrefix returns the file and line number information about the methods that called this method, based on the current
|
||||
// goroutine's stack. The argument callDepth is the number of stack frames to ascend, with 0 identifying the method
|
||||
// that called CallerPrefix, 1 identifying the method that called that method, and so on.
|
||||
//
|
||||
// This code is adapted from testing.go, where it is in a private method called decorate.
|
||||
func CallerPrefix(callDepth int) string {
|
||||
_, file, line, ok := runtime.Caller(callDepth)
|
||||
if ok {
|
||||
// Truncate file name at last file name separator.
|
||||
if index := strings.LastIndex(file, "/"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
} else if index = strings.LastIndex(file, "\\"); index >= 0 {
|
||||
file = file[index+1:]
|
||||
}
|
||||
} else {
|
||||
file = "???"
|
||||
line = 1
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s:%d", file, line)
|
||||
}
|
||||
+252
@@ -0,0 +1,252 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/gruntwork-io/terratest/modules/logger"
|
||||
"github.com/gruntwork-io/terratest/modules/testing"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Command is a simpler struct for defining commands than Go's built-in Cmd.
|
||||
type Command struct {
|
||||
Command string // The command to run
|
||||
Args []string // The args to pass to the command
|
||||
WorkingDir string // The working directory
|
||||
Env map[string]string // Additional environment variables to set
|
||||
// Use the specified logger for the command's output. Use logger.Discard to not print the output while executing the command.
|
||||
Logger *logger.Logger
|
||||
}
|
||||
|
||||
// RunCommand runs a shell command and redirects its stdout and stderr to the stdout of the atomic script itself. If
|
||||
// there are any errors, fail the test.
|
||||
func RunCommand(t testing.TestingT, command Command) {
|
||||
err := RunCommandE(t, command)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// RunCommandE runs a shell command and redirects its stdout and stderr to the stdout of the atomic script itself. Any
|
||||
// returned error will be of type ErrWithCmdOutput, containing the output streams and the underlying error.
|
||||
func RunCommandE(t testing.TestingT, command Command) error {
|
||||
output, err := runCommand(t, command)
|
||||
if err != nil {
|
||||
return &ErrWithCmdOutput{err, output}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunCommandAndGetOutput runs a shell command and returns its stdout and stderr as a string. The stdout and stderr of
|
||||
// that command will also be logged with Command.Log to make debugging easier. If there are any errors, fail the test.
|
||||
func RunCommandAndGetOutput(t testing.TestingT, command Command) string {
|
||||
out, err := RunCommandAndGetOutputE(t, command)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
|
||||
// RunCommandAndGetOutputE runs a shell command and returns its stdout and stderr as a string. The stdout and stderr of
|
||||
// that command will also be logged with Command.Log to make debugging easier. Any returned error will be of type
|
||||
// ErrWithCmdOutput, containing the output streams and the underlying error.
|
||||
func RunCommandAndGetOutputE(t testing.TestingT, command Command) (string, error) {
|
||||
output, err := runCommand(t, command)
|
||||
if err != nil {
|
||||
return output.Combined(), &ErrWithCmdOutput{err, output}
|
||||
}
|
||||
|
||||
return output.Combined(), nil
|
||||
}
|
||||
|
||||
// RunCommandAndGetStdOut runs a shell command and returns solely its stdout (but not stderr) as a string. The stdout and
|
||||
// stderr of that command will also be logged with Command.Log to make debugging easier. If there are any errors, fail
|
||||
// the test.
|
||||
func RunCommandAndGetStdOut(t testing.TestingT, command Command) string {
|
||||
output, err := RunCommandAndGetStdOutE(t, command)
|
||||
require.NoError(t, err)
|
||||
return output
|
||||
}
|
||||
|
||||
// RunCommandAndGetStdOutE runs a shell command and returns solely its stdout (but not stderr) as a string. The stdout
|
||||
// and stderr of that command will also be printed to the stdout and stderr of this Go program to make debugging easier.
|
||||
// Any returned error will be of type ErrWithCmdOutput, containing the output streams and the underlying error.
|
||||
func RunCommandAndGetStdOutE(t testing.TestingT, command Command) (string, error) {
|
||||
output, err := runCommand(t, command)
|
||||
if err != nil {
|
||||
return output.Stdout(), &ErrWithCmdOutput{err, output}
|
||||
}
|
||||
|
||||
return output.Stdout(), nil
|
||||
}
|
||||
|
||||
// RunCommandAndGetStdOutErr runs a shell command and returns solely its stdout and stderr as a string. The stdout and
|
||||
// stderr of that command will also be logged with Command.Log to make debugging easier. If there are any errors, fail
|
||||
// the test.
|
||||
func RunCommandAndGetStdOutErr(t testing.TestingT, command Command) (stdout string, stderr string) {
|
||||
stdout, stderr, err := RunCommandAndGetStdOutErrE(t, command)
|
||||
require.NoError(t, err)
|
||||
return stdout, stderr
|
||||
}
|
||||
|
||||
// RunCommandAndGetStdOutErrE runs a shell command and returns solely its stdout and stderr as a string. The stdout
|
||||
// and stderr of that command will also be printed to the stdout and stderr of this Go program to make debugging easier.
|
||||
// Any returned error will be of type ErrWithCmdOutput, containing the output streams and the underlying error.
|
||||
func RunCommandAndGetStdOutErrE(t testing.TestingT, command Command) (stdout string, stderr string, err error) {
|
||||
output, err := runCommand(t, command)
|
||||
if err != nil {
|
||||
return output.Stdout(), output.Stderr(), &ErrWithCmdOutput{err, output}
|
||||
}
|
||||
|
||||
return output.Stdout(), output.Stderr(), nil
|
||||
}
|
||||
|
||||
type ErrWithCmdOutput struct {
|
||||
Underlying error
|
||||
Output *output
|
||||
}
|
||||
|
||||
func (e *ErrWithCmdOutput) Error() string {
|
||||
return fmt.Sprintf("error while running command: %v; %s", e.Underlying, e.Output.Stderr())
|
||||
}
|
||||
|
||||
// runCommand runs a shell command and stores each line from stdout and stderr in Output. Depending on the logger, the
|
||||
// stdout and stderr of that command will also be printed to the stdout and stderr of this Go program to make debugging
|
||||
// easier.
|
||||
func runCommand(t testing.TestingT, command Command) (*output, error) {
|
||||
command.Logger.Logf(t, "Running command %s with args %s", command.Command, command.Args)
|
||||
|
||||
cmd := exec.Command(command.Command, command.Args...)
|
||||
cmd.Dir = command.WorkingDir
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Env = formatEnvVars(command)
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
stderr, err := cmd.StderrPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = cmd.Start()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
output, err := readStdoutAndStderr(t, command.Logger, stdout, stderr)
|
||||
if err != nil {
|
||||
return output, err
|
||||
}
|
||||
|
||||
return output, cmd.Wait()
|
||||
}
|
||||
|
||||
// This function captures stdout and stderr into the given variables while still printing it to the stdout and stderr
|
||||
// of this Go program
|
||||
func readStdoutAndStderr(t testing.TestingT, log *logger.Logger, stdout, stderr io.ReadCloser) (*output, error) {
|
||||
out := newOutput()
|
||||
stdoutReader := bufio.NewReader(stdout)
|
||||
stderrReader := bufio.NewReader(stderr)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
|
||||
wg.Add(2)
|
||||
var stdoutErr, stderrErr error
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
stdoutErr = readData(t, log, stdoutReader, out.stdout)
|
||||
}()
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
stderrErr = readData(t, log, stderrReader, out.stderr)
|
||||
}()
|
||||
wg.Wait()
|
||||
|
||||
if stdoutErr != nil {
|
||||
return out, stdoutErr
|
||||
}
|
||||
if stderrErr != nil {
|
||||
return out, stderrErr
|
||||
}
|
||||
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func readData(t testing.TestingT, log *logger.Logger, reader *bufio.Reader, writer io.StringWriter) error {
|
||||
var line string
|
||||
var readErr error
|
||||
for {
|
||||
line, readErr = reader.ReadString('\n')
|
||||
|
||||
// remove newline, our output is in a slice,
|
||||
// one element per line.
|
||||
line = strings.TrimSuffix(line, "\n")
|
||||
|
||||
// only return early if the line does not have
|
||||
// any contents. We could have a line that does
|
||||
// not not have a newline before io.EOF, we still
|
||||
// need to add it to the output.
|
||||
if len(line) == 0 && readErr == io.EOF {
|
||||
break
|
||||
}
|
||||
|
||||
// logger.Logger has a Logf method, but not a Log method.
|
||||
// We have to use the format string indirection to avoid
|
||||
// interpreting any possible formatting characters in
|
||||
// the line.
|
||||
//
|
||||
// See https://github.com/gruntwork-io/terratest/issues/982.
|
||||
log.Logf(t, "%s", line)
|
||||
|
||||
if _, err := writer.WriteString(line); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if readErr != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if readErr != io.EOF {
|
||||
return readErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetExitCodeForRunCommandError tries to read the exit code for the error object returned from running a shell command. This is a bit tricky to do
|
||||
// in a way that works across platforms.
|
||||
func GetExitCodeForRunCommandError(err error) (int, error) {
|
||||
if errWithOutput, ok := err.(*ErrWithCmdOutput); ok {
|
||||
err = errWithOutput.Underlying
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/10385867/483528
|
||||
if exitErr, ok := err.(*exec.ExitError); ok {
|
||||
// The program has exited with an exit code != 0
|
||||
|
||||
// This works on both Unix and Windows. Although package
|
||||
// syscall is generally platform dependent, WaitStatus is
|
||||
// defined for both Unix and Windows and in both cases has
|
||||
// an ExitStatus() method with the same signature.
|
||||
if status, ok := exitErr.Sys().(syscall.WaitStatus); ok {
|
||||
return status.ExitStatus(), nil
|
||||
}
|
||||
return 1, errors.New("could not determine exit code")
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func formatEnvVars(command Command) []string {
|
||||
env := os.Environ()
|
||||
for key, value := range command.Env {
|
||||
env = append(env, fmt.Sprintf("%s=%s", key, value))
|
||||
}
|
||||
return env
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
package shell
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// output contains the output after runnig a command.
|
||||
type output struct {
|
||||
stdout *outputStream
|
||||
stderr *outputStream
|
||||
// merged contains stdout and stderr merged into one stream.
|
||||
merged *merged
|
||||
}
|
||||
|
||||
func newOutput() *output {
|
||||
m := new(merged)
|
||||
return &output{
|
||||
merged: m,
|
||||
stdout: &outputStream{
|
||||
merged: m,
|
||||
},
|
||||
stderr: &outputStream{
|
||||
merged: m,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (o *output) Stdout() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return o.stdout.String()
|
||||
}
|
||||
|
||||
func (o *output) Stderr() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return o.stderr.String()
|
||||
}
|
||||
|
||||
func (o *output) Combined() string {
|
||||
if o == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return o.merged.String()
|
||||
}
|
||||
|
||||
type outputStream struct {
|
||||
Lines []string
|
||||
*merged
|
||||
}
|
||||
|
||||
func (st *outputStream) WriteString(s string) (n int, err error) {
|
||||
st.Lines = append(st.Lines, string(s))
|
||||
return st.merged.WriteString(s)
|
||||
}
|
||||
|
||||
func (st *outputStream) String() string {
|
||||
if st == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.Join(st.Lines, "\n")
|
||||
}
|
||||
|
||||
type merged struct {
|
||||
// ensure that there are no parallel writes
|
||||
sync.Mutex
|
||||
Lines []string
|
||||
}
|
||||
|
||||
func (m *merged) String() string {
|
||||
if m == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.Join(m.Lines, "\n")
|
||||
}
|
||||
|
||||
func (m *merged) WriteString(s string) (n int, err error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
m.Lines = append(m.Lines, string(s))
|
||||
|
||||
return len(s), nil
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Package shell allows to run commands in a shell.
|
||||
package shell
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
package testing
|
||||
|
||||
// TestingT is an interface that describes the implementation of the testing object
|
||||
// that the majority of Terratest functions accept as first argument.
|
||||
// Using an interface that describes testing.T instead of the actual implementation
|
||||
// makes terratest usable in a wider variety of contexts (e.g. use with ginkgo : https://godoc.org/github.com/onsi/ginkgo#GinkgoT)
|
||||
type TestingT interface {
|
||||
//Fail marks the function as having failed but continues execution.
|
||||
Fail()
|
||||
// FailNow marks the function as having failed and stops its execution
|
||||
// by calling runtime.Goexit (which then runs all deferred calls in the
|
||||
// current goroutine).
|
||||
// Execution will continue at the next test or benchmark.
|
||||
// FailNow must be called from the goroutine running the
|
||||
// test or benchmark function, not from other goroutines
|
||||
// created during the test. Calling FailNow does not stop
|
||||
// those other goroutines.
|
||||
FailNow()
|
||||
Fatal(args ...interface{})
|
||||
// Fatalf is equivalent to Logf followed by FailNow.
|
||||
Fatalf(format string, args ...interface{})
|
||||
// Error is equivalent to Log followed by Fail.
|
||||
Error(args ...interface{})
|
||||
// Errorf is equivalent to Logf followed by Fail.
|
||||
Errorf(format string, args ...interface{})
|
||||
// Name returns the name of the running test or benchmark.
|
||||
Name() string
|
||||
}
|
||||
Reference in New Issue
Block a user