Merge component 'engine' from git@github.com:moby/moby master

This commit is contained in:
Andrew Hsu
2017-09-01 17:28:12 -07:00
58 changed files with 700 additions and 695 deletions
@@ -18,7 +18,7 @@ type execBackend interface {
ContainerExecCreate(name string, config *types.ExecConfig) (string, error)
ContainerExecInspect(id string) (*backend.ExecInspect, error)
ContainerExecResize(name string, height, width int) error
ContainerExecStart(ctx context.Context, name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error
ContainerExecStart(ctx context.Context, name string, stdin io.Reader, stdout io.Writer, stderr io.Writer) error
ExecExists(name string) (bool, error)
}
@@ -70,7 +70,7 @@ func (s *containerRouter) getContainersStats(ctx context.Context, w http.Respons
config := &backend.ContainerStatsConfig{
Stream: stream,
OutStream: w,
Version: string(httputils.VersionFromContext(ctx)),
Version: httputils.VersionFromContext(ctx),
}
return s.backend.ContainerStats(ctx, vars["name"], config)
@@ -66,9 +66,7 @@ func (s *imageRouter) postCommit(ctx context.Context, w http.ResponseWriter, r *
return err
}
return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{
ID: string(imgID),
})
return httputils.WriteJSON(w, http.StatusCreated, &types.IDResponse{ID: imgID})
}
// Creates an image from Pull or from Import
@@ -2,6 +2,7 @@ package swarm
import (
"fmt"
"io"
"net/http"
"github.com/docker/docker/api/server/httputils"
@@ -12,7 +13,7 @@ import (
// swarmLogs takes an http response, request, and selector, and writes the logs
// specified by the selector to the response
func (sr *swarmRouter) swarmLogs(ctx context.Context, w http.ResponseWriter, r *http.Request, selector *backend.LogSelector) error {
func (sr *swarmRouter) swarmLogs(ctx context.Context, w io.Writer, r *http.Request, selector *backend.LogSelector) error {
// Args are validated before the stream starts because when it starts we're
// sending HTTP 200 by writing an empty chunk of data to tell the client that
// daemon is going to stream. By sending this initial HTTP 200 we can't report
@@ -555,7 +555,7 @@ func parseOptInterval(f *Flag) (time.Duration, error) {
if err != nil {
return 0, err
}
if d < time.Duration(container.MinimumDuration) {
if d < container.MinimumDuration {
return 0, fmt.Errorf("Interval %#v cannot be less than %s", f.name, container.MinimumDuration)
}
return d, nil
@@ -348,7 +348,7 @@ func (b *Builder) probeCache(dispatchState *dispatchState, runConfig *container.
}
fmt.Fprint(b.Stdout, " ---> Using cache\n")
dispatchState.imageID = string(cachedID)
dispatchState.imageID = cachedID
b.buildStages.update(dispatchState.imageID)
return true, nil
}
@@ -110,7 +110,7 @@ func GetWithStatusError(address string) (resp *http.Response, err error) {
// - an io.Reader for the response body
// - an error value which will be non-nil either when something goes wrong while
// reading bytes from r or when the detected content-type is not acceptable.
func inspectResponse(ct string, r io.ReadCloser, clen int64) (string, io.ReadCloser, error) {
func inspectResponse(ct string, r io.Reader, clen int64) (string, io.ReadCloser, error) {
plen := clen
if plen <= 0 || plen > maxPreambleLength {
plen = maxPreambleLength
@@ -119,10 +119,10 @@ func inspectResponse(ct string, r io.ReadCloser, clen int64) (string, io.ReadClo
preamble := make([]byte, plen, plen)
rlen, err := r.Read(preamble)
if rlen == 0 {
return ct, r, errors.New("empty response")
return ct, ioutil.NopCloser(r), errors.New("empty response")
}
if err != nil && err != io.EOF {
return ct, r, err
return ct, ioutil.NopCloser(r), err
}
preambleR := bytes.NewReader(preamble[:rlen])
+2 -2
View File
@@ -7,7 +7,7 @@ import (
"github.com/docker/distribution/reference"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/swarm"
"github.com/opencontainers/go-digest"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
@@ -85,7 +85,7 @@ func (cli *Client) ServiceCreate(ctx context.Context, service swarm.ServiceSpec,
return response, err
}
func imageDigestAndPlatforms(ctx context.Context, cli *Client, image, encodedAuth string) (string, []swarm.Platform, error) {
func imageDigestAndPlatforms(ctx context.Context, cli DistributionAPIClient, image, encodedAuth string) (string, []swarm.Platform, error) {
distributionInspect, err := cli.DistributionInspect(ctx, image, encodedAuth)
imageWithDigest := image
var platforms []swarm.Platform
+1 -1
View File
@@ -539,7 +539,7 @@ func initRouter(opts routerOptions) {
}
// TODO: remove this from cli and return the authzMiddleware
func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config, pluginStore *plugin.Store) error {
func (cli *DaemonCli) initMiddlewares(s *apiserver.Server, cfg *apiserver.Config, pluginStore plugingetter.PluginGetter) error {
v := cfg.Version
exp := middleware.NewExperimentalMiddleware(cli.Config.Experimental)
+6 -2
View File
@@ -655,8 +655,12 @@ func (container *Container) BuildEndpointInfo(n libnetwork.Network, ep libnetwor
return nil
}
type named interface {
Name() string
}
// UpdateJoinInfo updates network settings when container joins network n with endpoint ep.
func (container *Container) UpdateJoinInfo(n libnetwork.Network, ep libnetwork.Endpoint) error {
func (container *Container) UpdateJoinInfo(n named, ep libnetwork.Endpoint) error {
if err := container.buildPortMapInfo(ep); err != nil {
return err
}
@@ -684,7 +688,7 @@ func (container *Container) UpdateSandboxNetworkSettings(sb libnetwork.Sandbox)
}
// BuildJoinOptions builds endpoint Join options from a given network.
func (container *Container) BuildJoinOptions(n libnetwork.Network) ([]libnetwork.EndpointOption, error) {
func (container *Container) BuildJoinOptions(n named) ([]libnetwork.EndpointOption, error) {
var joinOptions []libnetwork.EndpointOption
if epConfig, ok := container.NetworkSettings.Networks[n.Name()]; ok {
for _, str := range epConfig.Links {
@@ -3,7 +3,6 @@ package convert
import (
"fmt"
"strings"
"time"
types "github.com/docker/docker/api/types/swarm"
swarmapi "github.com/docker/swarmkit/api"
@@ -115,7 +114,7 @@ func MergeSwarmSpecToGRPC(s types.Spec, spec swarmapi.ClusterSpec) (swarmapi.Clu
spec.Raft.ElectionTick = uint32(s.Raft.ElectionTick)
}
if s.Dispatcher.HeartbeatPeriod != 0 {
spec.Dispatcher.HeartbeatPeriod = gogotypes.DurationProto(time.Duration(s.Dispatcher.HeartbeatPeriod))
spec.Dispatcher.HeartbeatPeriod = gogotypes.DurationProto(s.Dispatcher.HeartbeatPeriod)
}
if s.CAConfig.NodeCertExpiry != 0 {
spec.CAConfig.NodeCertExpiry = gogotypes.DurationProto(s.CAConfig.NodeCertExpiry)
@@ -659,7 +659,7 @@ func (e *exitError) Error() string {
}
func (e *exitError) ExitCode() int {
return int(e.code)
return e.code
}
func (e *exitError) Cause() error {
+1 -1
View File
@@ -142,7 +142,7 @@ func (d *Daemon) ContainerExecCreate(name string, config *types.ExecConfig) (str
// ContainerExecStart starts a previously set up exec instance. The
// std streams are set up.
// If ctx is cancelled, the process is terminated.
func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) {
func (d *Daemon) ContainerExecStart(ctx context.Context, name string, stdin io.Reader, stdout io.Writer, stderr io.Writer) (err error) {
var (
cStdin io.ReadCloser
cStdout, cStderr io.Writer
@@ -158,8 +158,8 @@ func copyDir(srcDir, dstDir string, flags copyFlags) error {
// system.Chtimes doesn't support a NOFOLLOW flag atm
if !isSymlink {
aTime := time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec))
mTime := time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec))
aTime := time.Unix(stat.Atim.Sec, stat.Atim.Nsec)
mTime := time.Unix(stat.Mtim.Sec, stat.Mtim.Nsec)
if err := system.Chtimes(dstPath, aTime, mTime); err != nil {
return err
}
+4 -4
View File
@@ -97,10 +97,10 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
drivers = strings.TrimSpace(drivers)
v := &types.Info{
ID: daemon.ID,
Containers: int(cRunning + cPaused + cStopped),
ContainersRunning: int(cRunning),
ContainersPaused: int(cPaused),
ContainersStopped: int(cStopped),
Containers: cRunning + cPaused + cStopped,
ContainersRunning: cRunning,
ContainersPaused: cPaused,
ContainersStopped: cStopped,
Images: imageCount,
Driver: drivers,
DriverStatus: daemon.stores[p].layerStore.DriverStatus(),
@@ -86,7 +86,7 @@ func listenFD(addr string, tlsConfig *tls.Config) ([]net.Listener, error) {
return nil, fmt.Errorf("failed to parse systemd fd address: should be a number: %v", addr)
}
fdOffset := fdNum - 3
if len(listeners) < int(fdOffset)+1 {
if len(listeners) < fdOffset+1 {
return nil, fmt.Errorf("too few socket activated files passed in by systemd")
}
if listeners[fdOffset] == nil {
+1 -1
View File
@@ -833,7 +833,7 @@ func (daemon *Daemon) createSpec(c *container.Container) (*specs.Spec, error) {
s.Process.OOMScoreAdj = &c.HostConfig.OomScoreAdj
s.Linux.MountLabel = c.MountLabel
return (*specs.Spec)(&s), nil
return &s, nil
}
func clearReadOnly(m *specs.Mount) {
@@ -84,7 +84,7 @@ func ComputeV2MetadataHMACKey(authConfig *types.AuthConfig) ([]byte, error) {
if err != nil {
return nil, err
}
return []byte(digest.FromBytes([]byte(buf))), nil
return []byte(digest.FromBytes(buf)), nil
}
// authConfigKeyInput is a reduced AuthConfig structure holding just relevant credential data eligible for
+3 -3
View File
@@ -28,7 +28,7 @@ import (
"github.com/docker/docker/pkg/system"
refstore "github.com/docker/docker/reference"
"github.com/docker/docker/registry"
"github.com/opencontainers/go-digest"
digest "github.com/opencontainers/go-digest"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
@@ -435,7 +435,7 @@ func (p *v2Puller) pullV2Tag(ctx context.Context, ref reference.Named) (tagUpdat
return true, nil
}
func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Named, unverifiedManifest *schema1.SignedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
func (p *v2Puller) pullSchema1(ctx context.Context, ref reference.Reference, unverifiedManifest *schema1.SignedManifest) (id digest.Digest, manifestDigest digest.Digest, err error) {
var verifiedManifest *schema1.Manifest
verifiedManifest, err = verifySchema1Manifest(unverifiedManifest, ref)
if err != nil {
@@ -838,7 +838,7 @@ func allowV1Fallback(err error) error {
return err
}
func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Named) (m *schema1.Manifest, err error) {
func verifySchema1Manifest(signedManifest *schema1.SignedManifest, ref reference.Reference) (m *schema1.Manifest, err error) {
// If pull by digest, then verify the manifest digest. NOTE: It is
// important to do this first, before any other content validation. If the
// digest cannot be verified, don't even bother with those other things.
+2 -1
View File
@@ -24,7 +24,7 @@ import (
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/registry"
"github.com/opencontainers/go-digest"
digest "github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)
@@ -651,6 +651,7 @@ func (bla byLikeness) Swap(i, j int) {
}
func (bla byLikeness) Len() int { return len(bla.arr) }
// nolint: interfacer
func sortV2MetadataByLikenessAndAge(repoInfo reference.Named, hmacKey []byte, marr []metadata.V2Metadata) {
// reverse the metadata array to shift the newest entries to the beginning
for i := 0; i < len(marr)/2; i++ {
@@ -8,12 +8,17 @@
"api/types/container/container_.*",
"integration-cli/"
],
"Skip": [
"integration-cli/"
],
"Enable": [
"deadcode",
"gofmt",
"goimports",
"golint",
"interfacer",
"unconvert",
"vet"
],
+3 -6
View File
@@ -23,7 +23,7 @@ import (
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/system"
"github.com/opencontainers/go-digest"
digest "github.com/opencontainers/go-digest"
"github.com/sirupsen/logrus"
)
@@ -212,15 +212,12 @@ func (l *tarexporter) loadLayer(filename string, rootFS image.RootFS, id string,
return l.ls.Register(inflatedLayerData, rootFS.ChainID(), platform)
}
func (l *tarexporter) setLoadedTag(ref reference.NamedTagged, imgID digest.Digest, outStream io.Writer) error {
func (l *tarexporter) setLoadedTag(ref reference.Named, imgID digest.Digest, outStream io.Writer) error {
if prevID, err := l.rs.Get(ref); err == nil && prevID != imgID {
fmt.Fprintf(outStream, "The image %s already exists, renaming the old one with ID %s to empty string\n", reference.FamiliarString(ref), string(prevID)) // todo: this message is wrong in case of multiple tags
}
if err := l.rs.AddTag(ref, imgID, true); err != nil {
return err
}
return nil
return l.rs.AddTag(ref, imgID, true)
}
func (l *tarexporter) legacyLoad(tmpDir string, outStream io.Writer, progressOutput progress.Output) error {
+23 -14
View File
@@ -2,10 +2,12 @@ package main
import (
"fmt"
"io/ioutil"
"net/http/httptest"
"os"
"path"
"path/filepath"
"strconv"
"sync"
"syscall"
"testing"
@@ -20,6 +22,7 @@ import (
"github.com/docker/docker/integration-cli/environment"
"github.com/docker/docker/integration-cli/fixtures/plugin"
"github.com/docker/docker/integration-cli/registry"
ienv "github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/pkg/reexec"
"github.com/go-check/check"
"golang.org/x/net/context"
@@ -57,20 +60,14 @@ func init() {
func TestMain(m *testing.M) {
dockerBinary = testEnv.DockerBinary()
if testEnv.LocalDaemon() {
fmt.Println("INFO: Testing against a local daemon")
} else {
fmt.Println("INFO: Testing against a remote daemon")
}
exitCode := m.Run()
os.Exit(exitCode)
testEnv.Print()
os.Exit(m.Run())
}
func Test(t *testing.T) {
cli.EnsureTestEnvIsLoaded(t)
fakestorage.EnsureTestEnvIsLoaded(t)
environment.ProtectImages(t, testEnv)
cli.SetTestEnvironment(testEnv)
fakestorage.SetTestEnvironment(&testEnv.Execution)
ienv.ProtectImages(t, &testEnv.Execution)
check.TestingT(t)
}
@@ -82,13 +79,25 @@ type DockerSuite struct {
}
func (s *DockerSuite) OnTimeout(c *check.C) {
if testEnv.DaemonPID() > 0 && testEnv.LocalDaemon() {
daemon.SignalDaemonDump(testEnv.DaemonPID())
path := filepath.Join(os.Getenv("DEST"), "docker.pid")
b, err := ioutil.ReadFile(path)
if err != nil {
c.Fatalf("Failed to get daemon PID from %s\n", path)
}
rawPid, err := strconv.ParseInt(string(b), 10, 32)
if err != nil {
c.Fatalf("Failed to parse pid from %s: %s\n", path, err)
}
daemonPid := int(rawPid)
if daemonPid > 0 && testEnv.IsLocalDaemon() {
daemon.SignalDaemonDump(daemonPid)
}
}
func (s *DockerSuite) TearDownTest(c *check.C) {
testEnv.Clean(c, dockerBinary)
testEnv.Clean(c)
}
func init() {
@@ -11,9 +11,11 @@ import (
"github.com/docker/docker/integration-cli/cli/build/fakecontext"
"github.com/docker/docker/integration-cli/cli/build/fakestorage"
"github.com/stretchr/testify/require"
)
type testingT interface {
require.TestingT
logT
Fatal(args ...interface{})
Fatalf(string, ...interface{})
@@ -30,7 +30,7 @@ func ensureHTTPServerImage(t testingT) {
}
defer os.RemoveAll(tmp)
goos := testEnv.DaemonPlatform()
goos := testEnv.DaemonInfo.OSType
if goos == "" {
goos = "linux"
}
@@ -8,39 +8,20 @@ import (
"net/url"
"os"
"strings"
"sync"
"github.com/docker/docker/integration-cli/cli"
"github.com/docker/docker/integration-cli/cli/build"
"github.com/docker/docker/integration-cli/cli/build/fakecontext"
"github.com/docker/docker/integration-cli/environment"
"github.com/docker/docker/integration-cli/request"
"github.com/docker/docker/internal/test/environment"
"github.com/docker/docker/pkg/stringutils"
"github.com/stretchr/testify/require"
)
var (
testEnv *environment.Execution
onlyOnce sync.Once
)
// EnsureTestEnvIsLoaded make sure the test environment is loaded for this package
func EnsureTestEnvIsLoaded(t testingT) {
var doIt bool
var err error
onlyOnce.Do(func() {
doIt = true
})
if !doIt {
return
}
testEnv, err = environment.New()
if err != nil {
t.Fatalf("error loading testenv : %v", err)
}
}
var testEnv *environment.Execution
type testingT interface {
require.TestingT
logT
Fatal(args ...interface{})
Fatalf(string, ...interface{})
@@ -58,11 +39,20 @@ type Fake interface {
CtxDir() string
}
// SetTestEnvironment sets a static test environment
// TODO: decouple this package from environment
func SetTestEnvironment(env *environment.Execution) {
testEnv = env
}
// New returns a static file server that will be use as build context.
func New(t testingT, dir string, modifiers ...func(*fakecontext.Fake) error) Fake {
if testEnv == nil {
t.Fatal("fakstorage package requires SetTestEnvironment() to be called before use.")
}
ctx := fakecontext.New(t, dir, modifiers...)
if testEnv.LocalDaemon() {
return newLocalFakeStorage(t, ctx)
if testEnv.IsLocalDaemon() {
return newLocalFakeStorage(ctx)
}
return newRemoteFileServer(t, ctx)
}
@@ -86,7 +76,7 @@ func (s *localFileStorage) Close() error {
return s.Fake.Close()
}
func newLocalFakeStorage(t testingT, ctx *fakecontext.Fake) *localFileStorage {
func newLocalFakeStorage(ctx *fakecontext.Fake) *localFileStorage {
handler := http.FileServer(http.Dir(ctx.Dir))
server := httptest.NewServer(handler)
return &localFileStorage{
+6 -21
View File
@@ -4,7 +4,6 @@ import (
"fmt"
"io"
"strings"
"sync"
"time"
"github.com/docker/docker/integration-cli/daemon"
@@ -13,26 +12,12 @@ import (
"github.com/pkg/errors"
)
var (
testEnv *environment.Execution
onlyOnce sync.Once
)
var testEnv *environment.Execution
// EnsureTestEnvIsLoaded make sure the test environment is loaded for this package
func EnsureTestEnvIsLoaded(t testingT) {
var doIt bool
var err error
onlyOnce.Do(func() {
doIt = true
})
if !doIt {
return
}
testEnv, err = environment.New()
if err != nil {
t.Fatalf("error loading testenv : %v", err)
}
// SetTestEnvironment sets a static test environment
// TODO: decouple this package from environment
func SetTestEnvironment(env *environment.Execution) {
testEnv = env
}
// CmdOperator defines functions that can modify a command
@@ -130,7 +115,7 @@ func Docker(cmd icmd.Cmd, cmdOperators ...CmdOperator) *icmd.Result {
// validateArgs is a checker to ensure tests are not running commands which are
// not supported on platforms. Specifically on Windows this is 'busybox top'.
func validateArgs(args ...string) error {
if testEnv.DaemonPlatform() != "windows" {
if testEnv.DaemonInfo.OSType != "windows" {
return nil
}
foundBusybox := -1
@@ -69,6 +69,7 @@ func (s *DockerSuite) TestRenameCheckNames(c *check.C) {
})
}
// TODO: move to unit test
func (s *DockerSuite) TestRenameInvalidName(c *check.C) {
runSleepingContainer(c, "--name", "myname")
@@ -76,18 +77,6 @@ func (s *DockerSuite) TestRenameInvalidName(c *check.C) {
c.Assert(err, checker.NotNil, check.Commentf("Renaming container to invalid name should have failed: %s", out))
c.Assert(out, checker.Contains, "Invalid container name", check.Commentf("%v", err))
out, _, err = dockerCmdWithError("rename", "myname")
c.Assert(err, checker.NotNil, check.Commentf("Renaming container to invalid name should have failed: %s", out))
c.Assert(out, checker.Contains, "requires exactly 2 argument(s).", check.Commentf("%v", err))
out, _, err = dockerCmdWithError("rename", "myname", "")
c.Assert(err, checker.NotNil, check.Commentf("Renaming container to invalid name should have failed: %s", out))
c.Assert(out, checker.Contains, "may be empty", check.Commentf("%v", err))
out, _, err = dockerCmdWithError("rename", "", "newname")
c.Assert(err, checker.NotNil, check.Commentf("Renaming container with empty name should have failed: %s", out))
c.Assert(out, checker.Contains, "may be empty", check.Commentf("%v", err))
out, _ = dockerCmd(c, "ps", "-a")
c.Assert(out, checker.Contains, "myname", check.Commentf("Output of docker ps should have included 'myname': %s", out))
}
@@ -2215,7 +2215,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
out, err = inspectMountSourceField("dark_helmet", prefix+slash+`foo`)
c.Assert(err, check.IsNil)
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.VolumesConfigPath())) {
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.PlatformDefaults.VolumesConfigPath)) {
c.Fatalf("Volume was not defined for %s/foo\n%q", prefix, out)
}
@@ -2226,7 +2226,7 @@ func (s *DockerSuite) TestRunVolumesCleanPaths(c *check.C) {
out, err = inspectMountSourceField("dark_helmet", prefix+slash+"bar")
c.Assert(err, check.IsNil)
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.VolumesConfigPath())) {
if !strings.Contains(strings.ToLower(out), strings.ToLower(testEnv.PlatformDefaults.VolumesConfigPath)) {
c.Fatalf("Volume was not defined for %s/bar\n%q", prefix, out)
}
}
@@ -234,7 +234,7 @@ func readFile(src string, c *check.C) (content string) {
}
func containerStorageFile(containerID, basename string) string {
return filepath.Join(testEnv.ContainerStoragePath(), containerID, basename)
return filepath.Join(testEnv.PlatformDefaults.ContainerStoragePath, containerID, basename)
}
// docker commands that use this function must be run with the '-d' switch.
@@ -266,7 +266,7 @@ func readContainerFileWithExec(c *check.C, containerID, filename string) []byte
// daemonTime provides the current time on the daemon host
func daemonTime(c *check.C) time.Time {
if testEnv.LocalDaemon() {
if testEnv.IsLocalDaemon() {
return time.Now()
}
cli, err := client.NewEnvClient()
@@ -1,198 +0,0 @@
package environment
import (
"regexp"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/gotestyourself/gotestyourself/icmd"
"golang.org/x/net/context"
)
type testingT interface {
logT
Fatalf(string, ...interface{})
}
type logT interface {
Logf(string, ...interface{})
}
// Clean the environment, preserving protected objects (images, containers, ...)
// and removing everything else. It's meant to run after any tests so that they don't
// depend on each others.
func (e *Execution) Clean(t testingT, dockerBinary string) {
cli, err := client.NewEnvClient()
if err != nil {
t.Fatalf("%v", err)
}
defer cli.Close()
if (e.DaemonPlatform() != "windows") || (e.DaemonPlatform() == "windows" && e.Isolation() == "hyperv") {
unpauseAllContainers(t, dockerBinary)
}
deleteAllContainers(t, dockerBinary)
deleteAllImages(t, dockerBinary, e.protectedElements.images)
deleteAllVolumes(t, cli)
deleteAllNetworks(t, cli, e.DaemonPlatform())
if e.DaemonPlatform() == "linux" {
deleteAllPlugins(t, cli, dockerBinary)
}
}
func unpauseAllContainers(t testingT, dockerBinary string) {
containers := getPausedContainers(t, dockerBinary)
if len(containers) > 0 {
icmd.RunCommand(dockerBinary, append([]string{"unpause"}, containers...)...).Assert(t, icmd.Success)
}
}
func getPausedContainers(t testingT, dockerBinary string) []string {
result := icmd.RunCommand(dockerBinary, "ps", "-f", "status=paused", "-q", "-a")
result.Assert(t, icmd.Success)
return strings.Fields(result.Combined())
}
var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
func deleteAllContainers(t testingT, dockerBinary string) {
containers := getAllContainers(t, dockerBinary)
if len(containers) > 0 {
result := icmd.RunCommand(dockerBinary, append([]string{"rm", "-fv"}, containers...)...)
if result.Error != nil {
// If the error is "No such container: ..." this means the container doesn't exists anymore,
// or if it is "... removal of container ... is already in progress" it will be removed eventually.
// We can safely ignore those.
if strings.Contains(result.Stderr(), "No such container") || alreadyExists.MatchString(result.Stderr()) {
return
}
t.Fatalf("error removing containers %v : %v (%s)", containers, result.Error, result.Combined())
}
}
}
func getAllContainers(t testingT, dockerBinary string) []string {
result := icmd.RunCommand(dockerBinary, "ps", "-q", "-a")
result.Assert(t, icmd.Success)
return strings.Fields(result.Combined())
}
func deleteAllImages(t testingT, dockerBinary string, protectedImages map[string]struct{}) {
result := icmd.RunCommand(dockerBinary, "images", "--digests")
result.Assert(t, icmd.Success)
lines := strings.Split(string(result.Combined()), "\n")[1:]
imgMap := map[string]struct{}{}
for _, l := range lines {
if l == "" {
continue
}
fields := strings.Fields(l)
imgTag := fields[0] + ":" + fields[1]
if _, ok := protectedImages[imgTag]; !ok {
if fields[0] == "<none>" || fields[1] == "<none>" {
if fields[2] != "<none>" {
imgMap[fields[0]+"@"+fields[2]] = struct{}{}
} else {
imgMap[fields[3]] = struct{}{}
}
// continue
} else {
imgMap[imgTag] = struct{}{}
}
}
}
if len(imgMap) != 0 {
imgs := make([]string, 0, len(imgMap))
for k := range imgMap {
imgs = append(imgs, k)
}
icmd.RunCommand(dockerBinary, append([]string{"rmi", "-f"}, imgs...)...).Assert(t, icmd.Success)
}
}
func deleteAllVolumes(t testingT, c client.APIClient) {
var errs []string
volumes, err := getAllVolumes(c)
if err != nil {
t.Fatalf("%v", err)
}
for _, v := range volumes {
err := c.VolumeRemove(context.Background(), v.Name, true)
if err != nil {
errs = append(errs, err.Error())
continue
}
}
if len(errs) > 0 {
t.Fatalf("%v", strings.Join(errs, "\n"))
}
}
func getAllVolumes(c client.APIClient) ([]*types.Volume, error) {
volumes, err := c.VolumeList(context.Background(), filters.Args{})
if err != nil {
return nil, err
}
return volumes.Volumes, nil
}
func deleteAllNetworks(t testingT, c client.APIClient, daemonPlatform string) {
networks, err := getAllNetworks(c)
if err != nil {
t.Fatalf("%v", err)
}
var errs []string
for _, n := range networks {
if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
continue
}
if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
// nat is a pre-defined network on Windows and cannot be removed
continue
}
err := c.NetworkRemove(context.Background(), n.ID)
if err != nil {
errs = append(errs, err.Error())
continue
}
}
if len(errs) > 0 {
t.Fatalf("%v", strings.Join(errs, "\n"))
}
}
func getAllNetworks(c client.APIClient) ([]types.NetworkResource, error) {
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
if err != nil {
return nil, err
}
return networks, nil
}
func deleteAllPlugins(t testingT, c client.APIClient, dockerBinary string) {
plugins, err := getAllPlugins(c)
if err != nil {
t.Fatalf("%v", err)
}
var errs []string
for _, p := range plugins {
err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
if err != nil {
errs = append(errs, err.Error())
continue
}
}
if len(errs) > 0 {
t.Fatalf("%v", strings.Join(errs, "\n"))
}
}
func getAllPlugins(c client.APIClient) (types.PluginsListResponse, error) {
plugins, err := c.PluginList(context.Background(), filters.Args{})
if err != nil {
return nil, err
}
return plugins, nil
}
@@ -1,19 +1,11 @@
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"
"os/exec"
"github.com/docker/docker/internal/test/environment"
)
var (
@@ -23,89 +15,28 @@ var (
func init() {
if DefaultClientBinary == "" {
// TODO: to be removed once we no longer depend on the docker cli for integration tests
//panic("TEST_CLIENT_BINARY must be set")
DefaultClientBinary = "docker"
}
}
// Execution holds informations about the test execution environment.
// Execution contains information about the current test execution and daemon
// under test
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
environment.Execution
dockerBinary string
protectedElements protectedElements
}
// New creates a new Execution struct
// DockerBinary returns the docker binary for this testing environment
func (e *Execution) DockerBinary() string {
return e.dockerBinary
}
// New returns details about the testing environment
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()
env, err := environment.New()
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)
}
}
dockerBinary, err := exec.LookPath(DefaultClientBinary)
if err != nil {
@@ -113,117 +44,36 @@ func New() (*Execution, error) {
}
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{}{},
},
Execution: *env,
dockerBinary: dockerBinary,
}, 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())
// DockerBasePath is the base path of the docker folder (by default it is -/var/run/docker)
// TODO: remove
// Deprecated: use Execution.DaemonInfo.DockerRootDir
func (e *Execution) DockerBasePath() string {
return e.DaemonInfo.DockerRootDir
}
// LocalDaemon is true if the daemon under test is on the same
// host as the CLI.
func (e *Execution) LocalDaemon() bool {
return e.localDaemon
// ExperimentalDaemon tell whether the main daemon has
// experimental features enabled or not
// Deprecated: use DaemonInfo.ExperimentalBuild
func (e *Execution) ExperimentalDaemon() bool {
return e.DaemonInfo.ExperimentalBuild
}
// 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.
// Deprecated: use Execution.DaemonInfo.OSType
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
return e.DaemonInfo.OSType
}
// MinimalBaseImage is the image used for minimal builds (it depends on the platform)
// Deprecated: use Execution.PlatformDefaults.BaseImage
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
return e.PlatformDefaults.BaseImage
}
@@ -1,48 +0,0 @@
package environment
import (
"strings"
"github.com/docker/docker/integration-cli/fixtures/load"
"github.com/gotestyourself/gotestyourself/icmd"
)
type protectedElements struct {
images map[string]struct{}
}
// ProtectImage adds the specified image(s) to be protected in case of clean
func (e *Execution) ProtectImage(t testingT, images ...string) {
for _, image := range images {
e.protectedElements.images[image] = struct{}{}
}
}
// ProtectImages protects existing images and on linux frozen images from being
// cleaned up at the end of test runs
func ProtectImages(t testingT, testEnv *Execution) {
images := getExistingImages(t, testEnv)
if testEnv.DaemonPlatform() == "linux" {
images = append(images, ensureFrozenImagesLinux(t, testEnv)...)
}
testEnv.ProtectImage(t, images...)
}
func getExistingImages(t testingT, testEnv *Execution) []string {
// TODO: use API instead of cli
result := icmd.RunCommand(testEnv.dockerBinary, "images", "-f", "dangling=false", "--format", "{{.Repository}}:{{.Tag}}")
result.Assert(t, icmd.Success)
return strings.Split(strings.TrimSpace(result.Stdout()), "\n")
}
func ensureFrozenImagesLinux(t testingT, testEnv *Execution) []string {
images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
err := load.FrozenImagesLinux(testEnv.DockerBinary(), images...)
if err != nil {
result := icmd.RunCommand(testEnv.DockerBinary(), "image", "ls")
t.Logf(result.String())
t.Fatalf("%+v", err)
}
return images
}
@@ -9,21 +9,27 @@ import (
"strings"
"sync"
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/term"
"github.com/pkg/errors"
)
var frozenImgDir = "/docker-frozen-images"
const frozenImgDir = "/docker-frozen-images"
// FrozenImagesLinux loads the frozen image set for the integration suite
// If the images are not available locally it will download them
// TODO: This loads whatever is in the frozen image dir, regardless of what
// images were passed in. If the images need to be downloaded, then it will respect
// the passed in images
func FrozenImagesLinux(dockerBinary string, images ...string) error {
func FrozenImagesLinux(client client.APIClient, images ...string) error {
imgNS := os.Getenv("TEST_IMAGE_NAMESPACE")
var loadImages []struct{ srcName, destName string }
for _, img := range images {
if err := exec.Command(dockerBinary, "inspect", "--type=image", img).Run(); err != nil {
if !imageExists(client, img) {
srcName := img
// hello-world:latest gets re-tagged as hello-world:frozen
// there are some tests that use hello-world:latest specifically so it pulls
@@ -46,35 +52,41 @@ func FrozenImagesLinux(dockerBinary string, images ...string) error {
return nil
}
ctx := context.Background()
fi, err := os.Stat(frozenImgDir)
if err != nil || !fi.IsDir() {
srcImages := make([]string, 0, len(loadImages))
for _, img := range loadImages {
srcImages = append(srcImages, img.srcName)
}
if err := pullImages(dockerBinary, srcImages); err != nil {
if err := pullImages(ctx, client, srcImages); err != nil {
return errors.Wrap(err, "error pulling image list")
}
} else {
if err := loadFrozenImages(dockerBinary); err != nil {
if err := loadFrozenImages(ctx, client); err != nil {
return err
}
}
for _, img := range loadImages {
if img.srcName != img.destName {
if out, err := exec.Command(dockerBinary, "tag", img.srcName, img.destName).CombinedOutput(); err != nil {
return errors.Errorf("%v: %s", err, string(out))
if err := client.ImageTag(ctx, img.srcName, img.destName); err != nil {
return errors.Wrapf(err, "failed to tag %s as %s", img.srcName, img.destName)
}
if out, err := exec.Command(dockerBinary, "rmi", img.srcName).CombinedOutput(); err != nil {
return errors.Errorf("%v: %s", err, string(out))
if _, err := client.ImageRemove(ctx, img.srcName, types.ImageRemoveOptions{}); err != nil {
return errors.Wrapf(err, "failed to remove %s", img.srcName)
}
}
}
return nil
}
func loadFrozenImages(dockerBinary string) error {
func imageExists(client client.APIClient, name string) bool {
_, _, err := client.ImageInspectWithRaw(context.Background(), name)
return err == nil
}
func loadFrozenImages(ctx context.Context, client client.APIClient) error {
tar, err := exec.LookPath("tar")
if err != nil {
return errors.Wrap(err, "could not find tar binary")
@@ -90,15 +102,16 @@ func loadFrozenImages(dockerBinary string) error {
tarCmd.Start()
defer tarCmd.Wait()
cmd := exec.Command(dockerBinary, "load")
cmd.Stdin = out
if out, err := cmd.CombinedOutput(); err != nil {
return errors.Errorf("%v: %s", err, string(out))
resp, err := client.ImageLoad(ctx, out, true)
if err != nil {
return errors.Wrap(err, "failed to load frozen images")
}
return nil
defer resp.Body.Close()
fd, isTerminal := term.GetFdInfo(os.Stdout)
return jsonmessage.DisplayJSONMessagesStream(resp.Body, os.Stdout, fd, isTerminal, nil)
}
func pullImages(dockerBinary string, images []string) error {
func pullImages(ctx context.Context, client client.APIClient, images []string) error {
cwd, err := os.Getwd()
if err != nil {
return errors.Wrap(err, "error getting path to dockerfile")
@@ -119,16 +132,8 @@ func pullImages(dockerBinary string, images []string) error {
wg.Add(1)
go func(tag, ref string) {
defer wg.Done()
if out, err := exec.Command(dockerBinary, "pull", ref).CombinedOutput(); err != nil {
chErr <- errors.Errorf("%v: %s", string(out), err)
return
}
if out, err := exec.Command(dockerBinary, "tag", ref, tag).CombinedOutput(); err != nil {
chErr <- errors.Errorf("%v: %s", string(out), err)
return
}
if out, err := exec.Command(dockerBinary, "rmi", ref).CombinedOutput(); err != nil {
chErr <- errors.Errorf("%v: %s", string(out), err)
if err := pullTagAndRemove(ctx, client, ref, tag); err != nil {
chErr <- err
return
}
}(tag, ref)
@@ -138,6 +143,25 @@ func pullImages(dockerBinary string, images []string) error {
return <-chErr
}
func pullTagAndRemove(ctx context.Context, client client.APIClient, ref string, tag string) error {
resp, err := client.ImagePull(ctx, ref, types.ImagePullOptions{})
if err != nil {
return errors.Wrapf(err, "failed to pull %s", ref)
}
defer resp.Close()
fd, isTerminal := term.GetFdInfo(os.Stdout)
if err := jsonmessage.DisplayJSONMessagesStream(resp, os.Stdout, fd, isTerminal, nil); err != nil {
return err
}
if err := client.ImageTag(ctx, ref, tag); err != nil {
return errors.Wrapf(err, "failed to tag %s as %s", ref, tag)
}
_, err = client.ImageRemove(ctx, ref, types.ImageRemoveOptions{})
return errors.Wrapf(err, "failed to remove %s", ref)
}
func readFrozenImageList(dockerfilePath string, images []string) (map[string]string, error) {
f, err := os.Open(dockerfilePath)
if err != nil {
@@ -156,11 +180,6 @@ func readFrozenImageList(dockerfilePath string, images []string) (map[string]str
continue
}
frozenImgDir = line[2]
if line[2] == frozenImgDir {
frozenImgDir = filepath.Join(os.Getenv("DEST"), "frozen-images")
}
for scanner.Scan() {
img := strings.TrimSpace(scanner.Text())
img = strings.TrimSuffix(img, "\\")
@@ -79,7 +79,7 @@ func ensureSyscallTest(c *check.C) {
}
func ensureSyscallTestBuild(c *check.C) {
err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie")
err := load.FrozenImagesLinux(testEnv.APIClient(), "buildpack-deps:jessie")
c.Assert(err, checker.IsNil)
var buildArgs []string
@@ -126,7 +126,7 @@ func ensureNNPTest(c *check.C) {
}
func ensureNNPTestBuild(c *check.C) {
err := load.FrozenImagesLinux(dockerBinary, "buildpack-deps:jessie")
err := load.FrozenImagesLinux(testEnv.APIClient(), "buildpack-deps:jessie")
c.Assert(err, checker.IsNil)
var buildArgs []string
@@ -8,7 +8,8 @@ import (
"strings"
)
type skipT interface {
// SkipT is the interface required to skip tests
type SkipT interface {
Skip(reason string)
}
@@ -17,7 +18,7 @@ type Test func() bool
// Is checks if the environment satisfies the requirements
// for the test to run or skips the tests.
func Is(s skipT, requirements ...Test) {
func Is(s SkipT, requirements ...Test) {
for _, r := range requirements {
isValid := r()
if !isValid {
@@ -6,57 +6,47 @@ import (
"net/http"
"os"
"os/exec"
"strconv"
"strings"
"time"
"github.com/docker/docker/integration-cli/requirement"
"github.com/go-check/check"
)
func PlatformIs(platform string) bool {
return testEnv.DaemonPlatform() == platform
}
func ArchitectureIs(arch string) bool {
return os.Getenv("DOCKER_ENGINE_GOARCH") == arch
}
func ArchitectureIsNot(arch string) bool {
return os.Getenv("DOCKER_ENGINE_GOARCH") != arch
}
func StorageDriverIs(storageDriver string) bool {
return strings.HasPrefix(testEnv.DaemonStorageDriver(), storageDriver)
}
func StorageDriverIsNot(storageDriver string) bool {
return !strings.HasPrefix(testEnv.DaemonStorageDriver(), storageDriver)
}
func DaemonIsWindows() bool {
return PlatformIs("windows")
return testEnv.DaemonInfo.OSType == "windows"
}
func DaemonIsWindowsAtLeastBuild(buildNumber int) func() bool {
return func() bool {
return DaemonIsWindows() && testEnv.DaemonKernelVersionNumeric() >= buildNumber
if testEnv.DaemonInfo.OSType != "windows" {
return false
}
version := testEnv.DaemonInfo.KernelVersion
numVersion, _ := strconv.Atoi(strings.Split(version, " ")[1])
return numVersion >= buildNumber
}
}
func DaemonIsLinux() bool {
return PlatformIs("linux")
return testEnv.DaemonInfo.OSType == "linux"
}
// Deprecated: use skip.IfCondition(t, !testEnv.DaemonInfo.ExperimentalBuild)
func ExperimentalDaemon() bool {
return testEnv.ExperimentalDaemon()
return testEnv.DaemonInfo.ExperimentalBuild
}
func NotExperimentalDaemon() bool {
return !testEnv.ExperimentalDaemon()
return !testEnv.DaemonInfo.ExperimentalBuild
}
func IsAmd64() bool {
return ArchitectureIs("amd64")
return os.Getenv("DOCKER_ENGINE_GOARCH") == "amd64"
}
func NotArm() bool {
@@ -76,7 +66,7 @@ func NotS390X() bool {
}
func SameHostDaemon() bool {
return testEnv.LocalDaemon()
return testEnv.IsLocalDaemon()
}
func UnixCli() bool {
@@ -127,12 +117,8 @@ func NotaryServerHosting() bool {
return err == nil
}
func NotOverlay() bool {
return StorageDriverIsNot("overlay")
}
func Devicemapper() bool {
return StorageDriverIs("devicemapper")
return strings.HasPrefix(testEnv.DaemonInfo.Driver, "devicemapper")
}
func IPv6() bool {
@@ -177,21 +163,21 @@ func UserNamespaceInKernel() bool {
}
func IsPausable() bool {
if testEnv.DaemonPlatform() == "windows" {
return testEnv.Isolation() == "hyperv"
if testEnv.DaemonInfo.OSType == "windows" {
return testEnv.DaemonInfo.Isolation == "hyperv"
}
return true
}
func NotPausable() bool {
if testEnv.DaemonPlatform() == "windows" {
return testEnv.Isolation() == "process"
if testEnv.DaemonInfo.OSType == "windows" {
return testEnv.DaemonInfo.Isolation == "process"
}
return false
}
func IsolationIs(expectedIsolation string) bool {
return testEnv.DaemonPlatform() == "windows" && string(testEnv.Isolation()) == expectedIsolation
return testEnv.DaemonInfo.OSType == "windows" && string(testEnv.DaemonInfo.Isolation) == expectedIsolation
}
func IsolationIsHyperv() bool {
@@ -204,6 +190,6 @@ func IsolationIsProcess() bool {
// testRequires checks if the environment satisfies the requirements
// for the test to run or skips the tests.
func testRequires(c *check.C, requirements ...requirement.Test) {
func testRequires(c requirement.SkipT, requirements ...requirement.Test) {
requirement.Is(c, requirements...)
}
@@ -101,7 +101,7 @@ func overlay2Supported() bool {
return false
}
daemonV, err := kernel.ParseRelease(testEnv.DaemonKernelVersion())
daemonV, err := kernel.ParseRelease(testEnv.DaemonInfo.KernelVersion)
if err != nil {
return false
}
@@ -5,12 +5,10 @@ import (
"os"
"testing"
"github.com/docker/docker/integration-cli/environment"
"github.com/docker/docker/internal/test/environment"
)
var (
testEnv *environment.Execution
)
var testEnv *environment.Execution
func TestMain(m *testing.M) {
var err error
@@ -20,18 +18,11 @@ func TestMain(m *testing.M) {
os.Exit(1)
}
// TODO: replace this with `testEnv.Print()` to print the full env
if testEnv.LocalDaemon() {
fmt.Println("INFO: Testing against a local daemon")
} else {
fmt.Println("INFO: Testing against a remote daemon")
}
res := m.Run()
os.Exit(res)
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectImages(t, testEnv)
return func() { testEnv.Clean(t, testEnv.DockerBinary()) }
return func() { testEnv.Clean(t) }
}
@@ -110,7 +110,7 @@ const defaultSwarmPort = 2477
func newSwarm(t *testing.T) *daemon.Swarm {
d := &daemon.Swarm{
Daemon: daemon.New(t, "", dockerdBinary, daemon.Config{
Experimental: testEnv.ExperimentalDaemon(),
Experimental: testEnv.DaemonInfo.ExperimentalBuild,
}),
// TODO: better method of finding an unused port
Port: defaultSwarmPort,
@@ -5,7 +5,7 @@ import (
"os"
"testing"
"github.com/docker/docker/integration-cli/environment"
"github.com/docker/docker/internal/test/environment"
)
var testEnv *environment.Execution
@@ -20,18 +20,11 @@ func TestMain(m *testing.M) {
os.Exit(1)
}
// TODO: replace this with `testEnv.Print()` to print the full env
if testEnv.LocalDaemon() {
fmt.Println("INFO: Testing against a local daemon")
} else {
fmt.Println("INFO: Testing against a remote daemon")
}
res := m.Run()
os.Exit(res)
testEnv.Print()
os.Exit(m.Run())
}
func setupTest(t *testing.T) func() {
environment.ProtectImages(t, testEnv)
return func() { testEnv.Clean(t, testEnv.DockerBinary()) }
return func() { testEnv.Clean(t) }
}
@@ -0,0 +1,164 @@
package environment
import (
"regexp"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"golang.org/x/net/context"
)
type testingT interface {
require.TestingT
logT
Fatalf(string, ...interface{})
}
type logT interface {
Logf(string, ...interface{})
}
// Clean the environment, preserving protected objects (images, containers, ...)
// and removing everything else. It's meant to run after any tests so that they don't
// depend on each others.
func (e *Execution) Clean(t testingT) {
client := e.APIClient()
platform := e.DaemonInfo.OSType
if (platform != "windows") || (platform == "windows" && e.DaemonInfo.Isolation == "hyperv") {
unpauseAllContainers(t, client)
}
deleteAllContainers(t, client)
deleteAllImages(t, client, e.protectedElements.images)
deleteAllVolumes(t, client)
deleteAllNetworks(t, client, platform)
if platform == "linux" {
deleteAllPlugins(t, client)
}
}
func unpauseAllContainers(t assert.TestingT, client client.ContainerAPIClient) {
ctx := context.Background()
containers := getPausedContainers(ctx, t, client)
if len(containers) > 0 {
for _, container := range containers {
err := client.ContainerUnpause(ctx, container.ID)
assert.NoError(t, err, "failed to unpause container %s", container.ID)
}
}
}
func getPausedContainers(ctx context.Context, t assert.TestingT, client client.ContainerAPIClient) []types.Container {
filter := filters.NewArgs()
filter.Add("status", "paused")
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Filters: filter,
Quiet: true,
All: true,
})
assert.NoError(t, err, "failed to list containers")
return containers
}
var alreadyExists = regexp.MustCompile(`Error response from daemon: removal of container (\w+) is already in progress`)
func deleteAllContainers(t assert.TestingT, apiclient client.ContainerAPIClient) {
ctx := context.Background()
containers := getAllContainers(ctx, t, apiclient)
if len(containers) == 0 {
return
}
for _, container := range containers {
err := apiclient.ContainerRemove(ctx, container.ID, types.ContainerRemoveOptions{
Force: true,
RemoveVolumes: true,
})
if err == nil || client.IsErrNotFound(err) || alreadyExists.MatchString(err.Error()) {
continue
}
assert.NoError(t, err, "failed to remove %s", container.ID)
}
}
func getAllContainers(ctx context.Context, t assert.TestingT, client client.ContainerAPIClient) []types.Container {
containers, err := client.ContainerList(ctx, types.ContainerListOptions{
Quiet: true,
All: true,
})
assert.NoError(t, err, "failed to list containers")
return containers
}
func deleteAllImages(t testingT, apiclient client.ImageAPIClient, protectedImages map[string]struct{}) {
images, err := apiclient.ImageList(context.Background(), types.ImageListOptions{})
assert.NoError(t, err, "failed to list images")
ctx := context.Background()
for _, image := range images {
tags := tagsFromImageSummary(image)
if len(tags) == 0 {
t.Logf("Removing image %s", image.ID)
removeImage(ctx, t, apiclient, image.ID)
continue
}
for _, tag := range tags {
if _, ok := protectedImages[tag]; !ok {
t.Logf("Removing image %s", tag)
removeImage(ctx, t, apiclient, tag)
continue
}
}
}
}
func removeImage(ctx context.Context, t assert.TestingT, apiclient client.ImageAPIClient, ref string) {
_, err := apiclient.ImageRemove(ctx, ref, types.ImageRemoveOptions{
Force: true,
})
if client.IsErrNotFound(err) {
return
}
assert.NoError(t, err, "failed to remove image %s", ref)
}
func deleteAllVolumes(t assert.TestingT, c client.VolumeAPIClient) {
volumes, err := c.VolumeList(context.Background(), filters.Args{})
assert.NoError(t, err, "failed to list volumes")
for _, v := range volumes.Volumes {
err := c.VolumeRemove(context.Background(), v.Name, true)
assert.NoError(t, err, "failed to remove volume %s", v.Name)
}
}
func deleteAllNetworks(t assert.TestingT, c client.NetworkAPIClient, daemonPlatform string) {
networks, err := c.NetworkList(context.Background(), types.NetworkListOptions{})
assert.NoError(t, err, "failed to list networks")
for _, n := range networks {
if n.Name == "bridge" || n.Name == "none" || n.Name == "host" {
continue
}
if daemonPlatform == "windows" && strings.ToLower(n.Name) == "nat" {
// nat is a pre-defined network on Windows and cannot be removed
continue
}
err := c.NetworkRemove(context.Background(), n.ID)
assert.NoError(t, err, "failed to remove network %s", n.ID)
}
}
func deleteAllPlugins(t assert.TestingT, c client.PluginAPIClient) {
plugins, err := c.PluginList(context.Background(), filters.Args{})
assert.NoError(t, err, "failed to list plugins")
for _, p := range plugins {
err := c.PluginRemove(context.Background(), p.Name, types.PluginRemoveOptions{Force: true})
assert.NoError(t, err, "failed to remove plugin %s", p.ID)
}
}
@@ -0,0 +1,117 @@
package environment
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/pkg/errors"
"golang.org/x/net/context"
)
// Execution contains information about the current test execution and daemon
// under test
type Execution struct {
client client.APIClient
DaemonInfo types.Info
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
func New() (*Execution, error) {
client, err := client.NewEnvClient()
if err != nil {
return nil, errors.Wrapf(err, "failed to create client")
}
info, err := client.Info(context.Background())
if err != nil {
return nil, errors.Wrapf(err, "failed to get info from daemon")
}
return &Execution{
client: client,
DaemonInfo: info,
PlatformDefaults: getPlatformDefaults(info),
protectedElements: newProtectedElements(),
}, nil
}
func getPlatformDefaults(info types.Info) PlatformDefaults {
volumesPath := filepath.Join(info.DockerRootDir, "volumes")
containersPath := filepath.Join(info.DockerRootDir, "containers")
switch info.OSType {
case "linux":
return PlatformDefaults{
BaseImage: "scratch",
VolumesConfigPath: toSlash(volumesPath),
ContainerStoragePath: toSlash(containersPath),
}
case "windows":
baseImage := "microsoft/windowsservercore"
if override := os.Getenv("WINDOWS_BASE_IMAGE"); override != "" {
baseImage = override
fmt.Println("INFO: Windows Base image is ", baseImage)
}
return PlatformDefaults{
BaseImage: baseImage,
VolumesConfigPath: filepath.FromSlash(volumesPath),
ContainerStoragePath: filepath.FromSlash(containersPath),
}
default:
panic(fmt.Sprintf("unknown info.OSType for daemon: %s", info.OSType))
}
}
// 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
// host as the CLI.
//
// 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") == ""
}
// 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
}
@@ -0,0 +1,78 @@
package environment
import (
"context"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/integration-cli/fixtures/load"
"github.com/stretchr/testify/require"
)
type protectedElements struct {
images map[string]struct{}
}
// ProtectImage adds the specified image(s) to be protected in case of clean
func (e *Execution) ProtectImage(t testingT, images ...string) {
for _, image := range images {
e.protectedElements.images[image] = struct{}{}
}
}
func newProtectedElements() protectedElements {
return protectedElements{
images: map[string]struct{}{},
}
}
// ProtectImages protects existing images and on linux frozen images from being
// cleaned up at the end of test runs
func ProtectImages(t testingT, testEnv *Execution) {
images := getExistingImages(t, testEnv)
if testEnv.DaemonInfo.OSType == "linux" {
images = append(images, ensureFrozenImagesLinux(t, testEnv)...)
}
testEnv.ProtectImage(t, images...)
}
func getExistingImages(t require.TestingT, testEnv *Execution) []string {
client := testEnv.APIClient()
filter := filters.NewArgs()
filter.Add("dangling", "false")
imageList, err := client.ImageList(context.Background(), types.ImageListOptions{
Filters: filter,
})
require.NoError(t, err, "failed to list images")
images := []string{}
for _, image := range imageList {
images = append(images, tagsFromImageSummary(image)...)
}
return images
}
func tagsFromImageSummary(image types.ImageSummary) []string {
result := []string{}
for _, tag := range image.RepoTags {
if tag != "<none>:<none>" {
result = append(result, tag)
}
}
for _, digest := range image.RepoDigests {
if digest != "<none>@<none>" {
result = append(result, digest)
}
}
return result
}
func ensureFrozenImagesLinux(t testingT, testEnv *Execution) []string {
images := []string{"busybox:latest", "hello-world:frozen", "debian:jessie"}
err := load.FrozenImagesLinux(testEnv.APIClient(), images...)
if err != nil {
t.Fatalf("Failed to load frozen images: %s", err)
}
return images
}
@@ -50,7 +50,7 @@ func (clnt *client) Create(containerID string, checkpoint string, checkpointDir
return fmt.Errorf("Container %s is already active", containerID)
}
uid, gid, err := getRootIDs(specs.Spec(spec))
uid, gid, err := getRootIDs(spec)
if err != nil {
return err
}
+107 -25
View File
@@ -8,35 +8,117 @@ import (
)
func TestValidateEnv(t *testing.T) {
valids := map[string]string{
"a": "a",
"something": "something",
"_=a": "_=a",
"env1=value1": "env1=value1",
"_env1=value1": "_env1=value1",
"env2=value2=value3": "env2=value2=value3",
"env3=abc!qwe": "env3=abc!qwe",
"env_4=value 4": "env_4=value 4",
"PATH": fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
"PATH=something": "PATH=something",
"asd!qwe": "asd!qwe",
"1asd": "1asd",
"123": "123",
"some space": "some space",
" some space before": " some space before",
"some space after ": "some space after ",
testcase := []struct {
value string
expected string
err error
}{
{
value: "a",
expected: "a",
},
{
value: "something",
expected: "something",
},
{
value: "_=a",
expected: "_=a",
},
{
value: "env1=value1",
expected: "env1=value1",
},
{
value: "_env1=value1",
expected: "_env1=value1",
},
{
value: "env2=value2=value3",
expected: "env2=value2=value3",
},
{
value: "env3=abc!qwe",
expected: "env3=abc!qwe",
},
{
value: "env_4=value 4",
expected: "env_4=value 4",
},
{
value: "PATH",
expected: fmt.Sprintf("PATH=%v", os.Getenv("PATH")),
},
{
value: "=a",
err: fmt.Errorf(fmt.Sprintf("invalid environment variable: %s", "=a")),
},
{
value: "PATH=something",
expected: "PATH=something",
},
{
value: "asd!qwe",
expected: "asd!qwe",
},
{
value: "1asd",
expected: "1asd",
},
{
value: "123",
expected: "123",
},
{
value: "some space",
expected: "some space",
},
{
value: " some space before",
expected: " some space before",
},
{
value: "some space after ",
expected: "some space after ",
},
{
value: "=",
err: fmt.Errorf(fmt.Sprintf("invalid environment variable: %s", "=")),
},
}
// Environment variables are case in-sensitive on Windows
if runtime.GOOS == "windows" {
valids["PaTh"] = fmt.Sprintf("PaTh=%v", os.Getenv("PATH"))
}
for value, expected := range valids {
actual, err := ValidateEnv(value)
if err != nil {
t.Fatal(err)
tmp := struct {
value string
expected string
err error
}{
value: "PaTh",
expected: fmt.Sprintf("PaTh=%v", os.Getenv("PATH")),
}
if actual != expected {
t.Fatalf("Expected [%v], got [%v]", expected, actual)
testcase = append(testcase, tmp)
}
for _, r := range testcase {
actual, err := ValidateEnv(r.value)
if err != nil {
if r.err == nil {
t.Fatalf("Expected err is nil, got err[%v]", err)
}
if err.Error() != r.err.Error() {
t.Fatalf("Expected err[%v], got err[%v]", r.err, err)
}
}
if err == nil && r.err != nil {
t.Fatalf("Expected err[%v], but err is nil", r.err)
}
if actual != r.expected {
t.Fatalf("Expected [%v], got [%v]", r.expected, actual)
}
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ func (opts *MapOpts) GetAll() map[string]string {
}
func (opts *MapOpts) String() string {
return fmt.Sprintf("%v", map[string]string((opts.values)))
return fmt.Sprintf("%v", opts.values)
}
// Type returns a string name for this Option type
+1 -1
View File
@@ -18,7 +18,7 @@ func (s *QuotedString) Type() string {
}
func (s *QuotedString) String() string {
return string(*s.value)
return *s.value
}
func trimQuotes(value string) string {
@@ -50,8 +50,8 @@ func setHeaderForSpecialDevice(hdr *tar.Header, name string, stat interface{}) (
// Currently go does not fill in the major/minors
if s.Mode&unix.S_IFBLK != 0 ||
s.Mode&unix.S_IFCHR != 0 {
hdr.Devmajor = int64(major(uint64(s.Rdev)))
hdr.Devminor = int64(minor(uint64(s.Rdev)))
hdr.Devmajor = int64(major(s.Rdev))
hdr.Devminor = int64(minor(s.Rdev))
}
}
@@ -62,7 +62,7 @@ func getInodeFromStat(stat interface{}) (inode uint64, err error) {
s, ok := stat.(*syscall.Stat_t)
if ok {
inode = uint64(s.Ino)
inode = s.Ino
}
return
@@ -294,7 +294,7 @@ func OverlayChanges(layers []string, rw string) ([]Change, error) {
func overlayDeletedFile(root, path string, fi os.FileInfo) (string, error) {
if fi.Mode()&os.ModeCharDevice != 0 {
s := fi.Sys().(*syscall.Stat_t)
if major(uint64(s.Rdev)) == 0 && minor(uint64(s.Rdev)) == 0 {
if major(s.Rdev) == 0 && minor(s.Rdev) == 0 {
return path, nil
}
}
@@ -29,7 +29,7 @@ func (info *FileInfo) isDir() bool {
}
func getIno(fi os.FileInfo) uint64 {
return uint64(fi.Sys().(*syscall.Stat_t).Ino)
return fi.Sys().(*syscall.Stat_t).Ino
}
func hasHardlinks(fi os.FileInfo) bool {
@@ -74,7 +74,7 @@ type DefaultLogger struct {
// DMLog is the logging callback containing all of the information from
// devicemapper. The interface is identical to the C libdm counterpart.
func (l DefaultLogger) DMLog(level int, file string, line, dmError int, message string) {
if int(level) <= l.Level {
if level <= l.Level {
// Forward the log to the correct logrus level, if allowed by dmLogLevel.
logMsg := fmt.Sprintf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message)
switch level {
@@ -34,11 +34,11 @@ func Size(dir string) (size int64, err error) {
// Check inode to handle hard links correctly
inode := fileInfo.Sys().(*syscall.Stat_t).Ino
// inode is not a uint64 on all platforms. Cast it to avoid issues.
if _, exists := data[uint64(inode)]; exists {
if _, exists := data[inode]; exists {
return nil
}
// inode is not a uint64 on all platforms. Cast it to avoid issues.
data[uint64(inode)] = struct{}{}
data[inode] = struct{}{}
size += s
+2 -2
View File
@@ -5,10 +5,10 @@ import "syscall"
// fromStatT converts a syscall.Stat_t type to a system.Stat_t type
func fromStatT(s *syscall.Stat_t) (*StatT, error) {
return &StatT{size: s.Size,
mode: uint32(s.Mode),
mode: s.Mode,
uid: s.Uid,
gid: s.Gid,
rdev: uint64(s.Rdev),
rdev: s.Rdev,
mtim: s.Mtim}, nil
}
+1 -1
View File
@@ -59,7 +59,7 @@ next:
return nil, fmt.Errorf("Unknown character: '%s'", key)
}
} else {
codes = append(codes, byte(key[0]))
codes = append(codes, key[0])
}
}
return codes, nil
+4 -14
View File
@@ -3,28 +3,18 @@
package term
import (
"unsafe"
"golang.org/x/sys/unix"
)
// GetWinsize returns the window size based on the specified file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {
ws := &Winsize{}
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TIOCGWINSZ), uintptr(unsafe.Pointer(ws)))
// Skipp errno = 0
if err == 0 {
return ws, nil
}
uws, err := unix.IoctlGetWinsize(int(fd), unix.TIOCGWINSZ)
ws := &Winsize{Height: uws.Row, Width: uws.Col, x: uws.Xpixel, y: uws.Ypixel}
return ws, err
}
// SetWinsize tries to set the specified window size for the specified file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {
_, _, err := unix.Syscall(unix.SYS_IOCTL, fd, uintptr(unix.TIOCSWINSZ), uintptr(unsafe.Pointer(ws)))
// Skipp errno = 0
if err == 0 {
return nil
}
return err
uws := &unix.Winsize{Row: ws.Height, Col: ws.Width, Xpixel: ws.x, Ypixel: ws.y}
return unix.IoctlSetWinsize(int(fd), unix.TIOCSWINSZ, uws)
}
+1 -2
View File
@@ -18,7 +18,6 @@ import (
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/plugin/v2"
"github.com/opencontainers/go-digest"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
@@ -62,7 +61,7 @@ func (pm *Manager) enable(p *v2.Plugin, c *controller, force bool) error {
return errors.WithStack(err)
}
if err := pm.containerdClient.Create(p.GetID(), "", "", specs.Spec(*spec), attachToLog(p.GetID())); err != nil {
if err := pm.containerdClient.Create(p.GetID(), "", "", *spec, attachToLog(p.GetID())); err != nil {
if p.PropagatedMount != "" {
if err := mount.Unmount(p.PropagatedMount); err != nil {
logrus.Warnf("Could not unmount %s: %v", p.PropagatedMount, err)
+1
View File
@@ -247,6 +247,7 @@ func (err PingResponseError) Error() string {
// challenge manager for the supported authentication types and
// whether v2 was confirmed by the response. If a response is received but
// cannot be interpreted a PingResponseError will be returned.
// nolint: interfacer
func PingV2Registry(endpoint *url.URL, transport http.RoundTripper) (challenge.Manager, bool, error) {
var (
foundV2 = false