2019-08-29 16:52:40 -04:00
|
|
|
package environment // import "github.com/docker/docker/testutil/environment"
|
2017-08-25 18:48:36 -04:00
|
|
|
|
|
|
|
import (
|
2018-04-19 18:30:59 -04:00
|
|
|
"context"
|
2017-08-25 18:48:36 -04:00
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
2019-09-23 08:23:01 -04:00
|
|
|
"testing"
|
2017-08-25 18:48:36 -04:00
|
|
|
|
|
|
|
"github.com/docker/docker/api/types"
|
2019-03-12 03:37:31 -04:00
|
|
|
"github.com/docker/docker/api/types/filters"
|
2017-08-25 18:48:36 -04:00
|
|
|
"github.com/docker/docker/client"
|
2019-08-29 16:52:40 -04:00
|
|
|
"github.com/docker/docker/testutil/fixtures/load"
|
2017-08-25 18:48:36 -04:00
|
|
|
"github.com/pkg/errors"
|
2020-02-07 08:39:24 -05:00
|
|
|
"gotest.tools/v3/assert"
|
2017-08-25 18:48:36 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// Execution contains information about the current test execution and daemon
|
|
|
|
// under test
|
|
|
|
type Execution struct {
|
|
|
|
client client.APIClient
|
|
|
|
DaemonInfo types.Info
|
2017-09-20 08:47:49 -04:00
|
|
|
OSType string
|
2017-08-25 18:48:36 -04:00
|
|
|
PlatformDefaults PlatformDefaults
|
|
|
|
protectedElements protectedElements
|
|
|
|
}
|
|
|
|
|
|
|
|
// PlatformDefaults are defaults values for the platform of the daemon under test
|
|
|
|
type PlatformDefaults struct {
|
|
|
|
BaseImage string
|
|
|
|
VolumesConfigPath string
|
|
|
|
ContainerStoragePath string
|
|
|
|
}
|
|
|
|
|
|
|
|
// New creates a new Execution struct
|
2019-08-28 12:42:16 -04:00
|
|
|
// This is configured using the env client (see client.FromEnv)
|
2017-08-25 18:48:36 -04:00
|
|
|
func New() (*Execution, error) {
|
2019-07-24 14:32:05 -04:00
|
|
|
c, err := client.NewClientWithOpts(client.FromEnv)
|
2017-08-25 18:48:36 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to create client")
|
|
|
|
}
|
2019-07-24 14:32:05 -04:00
|
|
|
return FromClient(c)
|
|
|
|
}
|
2017-08-25 18:48:36 -04:00
|
|
|
|
2019-07-24 14:32:05 -04:00
|
|
|
// FromClient creates a new Execution environment from the passed in client
|
|
|
|
func FromClient(c *client.Client) (*Execution, error) {
|
|
|
|
info, err := c.Info(context.Background())
|
2017-08-25 18:48:36 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to get info from daemon")
|
|
|
|
}
|
|
|
|
|
2017-09-20 08:47:49 -04:00
|
|
|
osType := getOSType(info)
|
|
|
|
|
2017-08-25 18:48:36 -04:00
|
|
|
return &Execution{
|
2019-07-24 14:32:05 -04:00
|
|
|
client: c,
|
2017-08-25 18:48:36 -04:00
|
|
|
DaemonInfo: info,
|
2017-09-20 08:47:49 -04:00
|
|
|
OSType: osType,
|
|
|
|
PlatformDefaults: getPlatformDefaults(info, osType),
|
2017-08-25 18:48:36 -04:00
|
|
|
protectedElements: newProtectedElements(),
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2017-09-20 08:47:49 -04:00
|
|
|
func getOSType(info types.Info) string {
|
|
|
|
// Docker EE does not set the OSType so allow the user to override this value.
|
|
|
|
userOsType := os.Getenv("TEST_OSTYPE")
|
|
|
|
if userOsType != "" {
|
|
|
|
return userOsType
|
|
|
|
}
|
|
|
|
return info.OSType
|
|
|
|
}
|
|
|
|
|
|
|
|
func getPlatformDefaults(info types.Info, osType string) PlatformDefaults {
|
2017-08-25 18:48:36 -04:00
|
|
|
volumesPath := filepath.Join(info.DockerRootDir, "volumes")
|
|
|
|
containersPath := filepath.Join(info.DockerRootDir, "containers")
|
|
|
|
|
2017-09-20 08:47:49 -04:00
|
|
|
switch osType {
|
2017-08-25 18:48:36 -04:00
|
|
|
case "linux":
|
|
|
|
return PlatformDefaults{
|
|
|
|
BaseImage: "scratch",
|
|
|
|
VolumesConfigPath: toSlash(volumesPath),
|
|
|
|
ContainerStoragePath: toSlash(containersPath),
|
|
|
|
}
|
|
|
|
case "windows":
|
|
|
|
baseImage := "microsoft/windowsservercore"
|
2019-05-14 16:27:18 -04:00
|
|
|
if overrideBaseImage := os.Getenv("WINDOWS_BASE_IMAGE"); overrideBaseImage != "" {
|
|
|
|
baseImage = overrideBaseImage
|
|
|
|
if overrideBaseImageTag := os.Getenv("WINDOWS_BASE_IMAGE_TAG"); overrideBaseImageTag != "" {
|
|
|
|
baseImage = baseImage + ":" + overrideBaseImageTag
|
|
|
|
}
|
2017-08-25 18:48:36 -04:00
|
|
|
}
|
2019-05-14 16:27:18 -04:00
|
|
|
fmt.Println("INFO: Windows Base image is ", baseImage)
|
2017-08-25 18:48:36 -04:00
|
|
|
return PlatformDefaults{
|
|
|
|
BaseImage: baseImage,
|
|
|
|
VolumesConfigPath: filepath.FromSlash(volumesPath),
|
|
|
|
ContainerStoragePath: filepath.FromSlash(containersPath),
|
|
|
|
}
|
|
|
|
default:
|
2017-09-20 08:47:49 -04:00
|
|
|
panic(fmt.Sprintf("unknown OSType for daemon: %s", osType))
|
2017-08-25 18:48:36 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Make sure in context of daemon, not the local platform. Note we can't
|
|
|
|
// use filepath.FromSlash or ToSlash here as they are a no-op on Unix.
|
|
|
|
func toSlash(path string) string {
|
|
|
|
return strings.Replace(path, `\`, `/`, -1)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsLocalDaemon is true if the daemon under test is on the same
|
2018-03-02 08:45:14 -05:00
|
|
|
// host as the test process.
|
2017-08-25 18:48:36 -04:00
|
|
|
//
|
|
|
|
// Deterministically working out the environment in which CI is running
|
|
|
|
// to evaluate whether the daemon is local or remote is not possible through
|
|
|
|
// a build tag.
|
|
|
|
//
|
|
|
|
// For example Windows to Linux CI under Jenkins tests the 64-bit
|
|
|
|
// Windows binary build with the daemon build tag, but calls a remote
|
|
|
|
// Linux daemon.
|
|
|
|
//
|
|
|
|
// We can't just say if Windows then assume the daemon is local as at
|
|
|
|
// some point, we will be testing the Windows CLI against a Windows daemon.
|
|
|
|
//
|
|
|
|
// Similarly, it will be perfectly valid to also run CLI tests from
|
|
|
|
// a Linux CLI (built with the daemon tag) against a Windows daemon.
|
|
|
|
func (e *Execution) IsLocalDaemon() bool {
|
|
|
|
return os.Getenv("DOCKER_REMOTE_DAEMON") == ""
|
|
|
|
}
|
|
|
|
|
2018-03-02 08:45:14 -05:00
|
|
|
// IsRemoteDaemon is true if the daemon under test is on different host
|
|
|
|
// as the test process.
|
|
|
|
func (e *Execution) IsRemoteDaemon() bool {
|
|
|
|
return !e.IsLocalDaemon()
|
|
|
|
}
|
|
|
|
|
2018-05-18 16:01:44 -04:00
|
|
|
// DaemonAPIVersion returns the negotiated daemon api version
|
2018-03-14 06:21:21 -04:00
|
|
|
func (e *Execution) DaemonAPIVersion() string {
|
|
|
|
version, err := e.APIClient().ServerVersion(context.TODO())
|
|
|
|
if err != nil {
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
return version.APIVersion
|
|
|
|
}
|
|
|
|
|
2017-08-25 18:48:36 -04:00
|
|
|
// Print the execution details to stdout
|
|
|
|
// TODO: print everything
|
|
|
|
func (e *Execution) Print() {
|
|
|
|
if e.IsLocalDaemon() {
|
|
|
|
fmt.Println("INFO: Testing against a local daemon")
|
|
|
|
} else {
|
|
|
|
fmt.Println("INFO: Testing against a remote daemon")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// APIClient returns an APIClient connected to the daemon under test
|
|
|
|
func (e *Execution) APIClient() client.APIClient {
|
|
|
|
return e.client
|
|
|
|
}
|
2017-10-18 17:59:16 -04:00
|
|
|
|
2018-09-13 17:51:03 -04:00
|
|
|
// IsUserNamespace returns whether the user namespace remapping is enabled
|
|
|
|
func (e *Execution) IsUserNamespace() bool {
|
|
|
|
root := os.Getenv("DOCKER_REMAP_ROOT")
|
|
|
|
return root != ""
|
|
|
|
}
|
|
|
|
|
2020-02-18 04:43:56 -05:00
|
|
|
// IsRootless returns whether the rootless mode is enabled
|
|
|
|
func (e *Execution) IsRootless() bool {
|
|
|
|
return os.Getenv("DOCKER_ROOTLESS") != ""
|
|
|
|
}
|
|
|
|
|
2020-12-07 15:30:44 -05:00
|
|
|
// IsUserNamespaceInKernel returns whether the kernel supports user namespaces
|
|
|
|
func (e *Execution) IsUserNamespaceInKernel() bool {
|
|
|
|
if _, err := os.Stat("/proc/self/uid_map"); os.IsNotExist(err) {
|
|
|
|
/*
|
|
|
|
* This kernel-provided file only exists if user namespaces are
|
|
|
|
* supported
|
|
|
|
*/
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need extra check on redhat based distributions
|
|
|
|
if f, err := os.Open("/sys/module/user_namespace/parameters/enable"); err == nil {
|
|
|
|
defer f.Close()
|
|
|
|
b := make([]byte, 1)
|
|
|
|
_, _ = f.Read(b)
|
|
|
|
return string(b) != "N"
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2019-03-12 03:37:31 -04:00
|
|
|
// HasExistingImage checks whether there is an image with the given reference.
|
|
|
|
// Note that this is done by filtering and then checking whether there were any
|
|
|
|
// results -- so ambiguous references might result in false-positives.
|
2019-09-23 08:23:01 -04:00
|
|
|
func (e *Execution) HasExistingImage(t testing.TB, reference string) bool {
|
2019-03-12 03:37:31 -04:00
|
|
|
client := e.APIClient()
|
|
|
|
filter := filters.NewArgs()
|
|
|
|
filter.Add("dangling", "false")
|
|
|
|
filter.Add("reference", reference)
|
|
|
|
imageList, err := client.ImageList(context.Background(), types.ImageListOptions{
|
|
|
|
All: true,
|
|
|
|
Filters: filter,
|
|
|
|
})
|
|
|
|
assert.NilError(t, err, "failed to list images")
|
|
|
|
|
|
|
|
return len(imageList) > 0
|
|
|
|
}
|
|
|
|
|
2017-10-18 17:59:16 -04:00
|
|
|
// EnsureFrozenImagesLinux loads frozen test images into the daemon
|
|
|
|
// if they aren't already loaded
|
|
|
|
func EnsureFrozenImagesLinux(testEnv *Execution) error {
|
|
|
|
if testEnv.OSType == "linux" {
|
|
|
|
err := load.FrozenImagesLinux(testEnv.APIClient(), frozenImages...)
|
|
|
|
if err != nil {
|
|
|
|
return errors.Wrap(err, "error loading frozen images")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|