blob: 7d2d044503c7764f0722860d09be9e11104e7f82 [file] [log] [blame]
package environment
import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
"github.com/docker/docker/opts"
"golang.org/x/net/context"
)
const (
// DefaultDockerBinary is the name of the docker binary
DefaultDockerBinary = "docker"
)
// Execution holds informations about the test execution environment.
type Execution struct {
daemonPlatform string
localDaemon bool
experimentalDaemon bool
daemonStorageDriver string
isolation container.Isolation
daemonPid int
daemonKernelVersion string
// For a local daemon on Linux, these values will be used for testing
// user namespace support as the standard graph path(s) will be
// appended with the root remapped uid.gid prefix
dockerBasePath string
volumesConfigPath string
containerStoragePath string
// baseImage is the name of the base image for testing
// Environment variable WINDOWS_BASE_IMAGE can override this
baseImage string
dockerBinary string
protectedElements protectedElements
}
// New creates a new Execution struct
func New() (*Execution, error) {
localDaemon := true
// 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.
if len(os.Getenv("DOCKER_REMOTE_DAEMON")) > 0 {
localDaemon = false
}
info, err := getDaemonDockerInfo()
if err != nil {
return nil, err
}
daemonPlatform := info.OSType
if daemonPlatform != "linux" && daemonPlatform != "windows" {
return nil, fmt.Errorf("Cannot run tests against platform: %s", daemonPlatform)
}
baseImage := "scratch"
volumesConfigPath := filepath.Join(info.DockerRootDir, "volumes")
containerStoragePath := filepath.Join(info.DockerRootDir, "containers")
// 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.
if daemonPlatform == "windows" {
volumesConfigPath = strings.Replace(volumesConfigPath, `/`, `\`, -1)
containerStoragePath = strings.Replace(containerStoragePath, `/`, `\`, -1)
baseImage = "microsoft/windowsservercore"
if len(os.Getenv("WINDOWS_BASE_IMAGE")) > 0 {
baseImage = os.Getenv("WINDOWS_BASE_IMAGE")
fmt.Println("INFO: Windows Base image is ", baseImage)
}
} else {
volumesConfigPath = strings.Replace(volumesConfigPath, `\`, `/`, -1)
containerStoragePath = strings.Replace(containerStoragePath, `\`, `/`, -1)
}
var daemonPid int
dest := os.Getenv("DEST")
b, err := ioutil.ReadFile(filepath.Join(dest, "docker.pid"))
if err == nil {
if p, err := strconv.ParseInt(string(b), 10, 32); err == nil {
daemonPid = int(p)
}
}
var dockerBinary = DefaultDockerBinary
if dockerBin := os.Getenv("DOCKER_BINARY"); dockerBin != "" {
dockerBinary = dockerBin
}
dockerBinary, err = exec.LookPath(dockerBinary)
if err != nil {
return nil, err
}
return &Execution{
localDaemon: localDaemon,
daemonPlatform: daemonPlatform,
daemonStorageDriver: info.Driver,
daemonKernelVersion: info.KernelVersion,
dockerBasePath: info.DockerRootDir,
volumesConfigPath: volumesConfigPath,
containerStoragePath: containerStoragePath,
isolation: info.Isolation,
daemonPid: daemonPid,
experimentalDaemon: info.ExperimentalBuild,
baseImage: baseImage,
dockerBinary: dockerBinary,
protectedElements: protectedElements{
images: map[string]struct{}{},
},
}, nil
}
func getDaemonDockerInfo() (types.Info, error) {
// FIXME(vdemeester) should be safe to use as is
client, err := client.NewEnvClient()
if err != nil {
return types.Info{}, err
}
return client.Info(context.Background())
}
// LocalDaemon is true if the daemon under test is on the same
// host as the CLI.
func (e *Execution) LocalDaemon() bool {
return e.localDaemon
}
// DaemonPlatform is held globally so that tests can make intelligent
// decisions on how to configure themselves according to the platform
// of the daemon. This is initialized in docker_utils by sending
// a version call to the daemon and examining the response header.
func (e *Execution) DaemonPlatform() string {
return e.daemonPlatform
}
// DockerBasePath is the base path of the docker folder (by default it is -/var/run/docker)
func (e *Execution) DockerBasePath() string {
return e.dockerBasePath
}
// VolumesConfigPath is the path of the volume configuration for the testing daemon
func (e *Execution) VolumesConfigPath() string {
return e.volumesConfigPath
}
// ContainerStoragePath is the path where the container are stored for the testing daemon
func (e *Execution) ContainerStoragePath() string {
return e.containerStoragePath
}
// DaemonStorageDriver is held globally so that tests can know the storage
// driver of the daemon. This is initialized in docker_utils by sending
// a version call to the daemon and examining the response header.
func (e *Execution) DaemonStorageDriver() string {
return e.daemonStorageDriver
}
// Isolation is the isolation mode of the daemon under test
func (e *Execution) Isolation() container.Isolation {
return e.isolation
}
// DaemonPID is the pid of the main test daemon
func (e *Execution) DaemonPID() int {
return e.daemonPid
}
// ExperimentalDaemon tell whether the main daemon has
// experimental features enabled or not
func (e *Execution) ExperimentalDaemon() bool {
return e.experimentalDaemon
}
// MinimalBaseImage is the image used for minimal builds (it depends on the platform)
func (e *Execution) MinimalBaseImage() string {
return e.baseImage
}
// DaemonKernelVersion is the kernel version of the daemon as a string, as returned
// by an INFO call to the daemon.
func (e *Execution) DaemonKernelVersion() string {
return e.daemonKernelVersion
}
// DaemonKernelVersionNumeric is the kernel version of the daemon as an integer.
// Mostly useful on Windows where DaemonKernelVersion holds the full string such
// as `10.0 14393 (14393.447.amd64fre.rs1_release_inmarket.161102-0100)`, but
// integration tests really only need the `14393` piece to make decisions.
func (e *Execution) DaemonKernelVersionNumeric() int {
if e.daemonPlatform != "windows" {
return -1
}
v, _ := strconv.Atoi(strings.Split(e.daemonKernelVersion, " ")[1])
return v
}
// DockerBinary returns the docker binary for this testing environment
func (e *Execution) DockerBinary() string {
return e.dockerBinary
}
// DaemonHost return the daemon host string for this test execution
func DaemonHost() string {
daemonURLStr := "unix://" + opts.DefaultUnixSocket
if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
daemonURLStr = daemonHostVar
}
return daemonURLStr
}