diff --git a/components/engine/api/server/router/container/backend.go b/components/engine/api/server/router/container/backend.go index 5e261622f8..8a32f26e6c 100644 --- a/components/engine/api/server/router/container/backend.go +++ b/components/engine/api/server/router/container/backend.go @@ -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) } diff --git a/components/engine/api/server/router/container/container_routes.go b/components/engine/api/server/router/container/container_routes.go index c5c38c093b..30fd3a15bf 100644 --- a/components/engine/api/server/router/container/container_routes.go +++ b/components/engine/api/server/router/container/container_routes.go @@ -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) diff --git a/components/engine/api/server/router/image/image_routes.go b/components/engine/api/server/router/image/image_routes.go index eb1394a780..86d73df0e1 100644 --- a/components/engine/api/server/router/image/image_routes.go +++ b/components/engine/api/server/router/image/image_routes.go @@ -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 diff --git a/components/engine/api/server/router/swarm/helpers.go b/components/engine/api/server/router/swarm/helpers.go index 7d2944208f..2c411f7d73 100644 --- a/components/engine/api/server/router/swarm/helpers.go +++ b/components/engine/api/server/router/swarm/helpers.go @@ -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 diff --git a/components/engine/builder/dockerfile/dispatchers.go b/components/engine/builder/dockerfile/dispatchers.go index 3d7f44bca9..d8a835802a 100644 --- a/components/engine/builder/dockerfile/dispatchers.go +++ b/components/engine/builder/dockerfile/dispatchers.go @@ -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 diff --git a/components/engine/builder/dockerfile/internals.go b/components/engine/builder/dockerfile/internals.go index 2a5fac0392..b18118ce36 100644 --- a/components/engine/builder/dockerfile/internals.go +++ b/components/engine/builder/dockerfile/internals.go @@ -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 } diff --git a/components/engine/builder/remotecontext/remote.go b/components/engine/builder/remotecontext/remote.go index 706eefd7e4..6733ff9e54 100644 --- a/components/engine/builder/remotecontext/remote.go +++ b/components/engine/builder/remotecontext/remote.go @@ -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]) diff --git a/components/engine/client/service_create.go b/components/engine/client/service_create.go index a36839443c..6b9364d6f2 100644 --- a/components/engine/client/service_create.go +++ b/components/engine/client/service_create.go @@ -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 diff --git a/components/engine/cmd/dockerd/daemon.go b/components/engine/cmd/dockerd/daemon.go index 0a3a0be388..c2f3781d88 100644 --- a/components/engine/cmd/dockerd/daemon.go +++ b/components/engine/cmd/dockerd/daemon.go @@ -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) diff --git a/components/engine/container/container.go b/components/engine/container/container.go index 40d493be6c..188c017cf9 100644 --- a/components/engine/container/container.go +++ b/components/engine/container/container.go @@ -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 { diff --git a/components/engine/daemon/cluster/convert/swarm.go b/components/engine/daemon/cluster/convert/swarm.go index 2ea89b968e..3ef3205e9e 100644 --- a/components/engine/daemon/cluster/convert/swarm.go +++ b/components/engine/daemon/cluster/convert/swarm.go @@ -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) diff --git a/components/engine/daemon/cluster/executor/container/controller.go b/components/engine/daemon/cluster/executor/container/controller.go index 7fa4a864d7..3ba4302d55 100644 --- a/components/engine/daemon/cluster/executor/container/controller.go +++ b/components/engine/daemon/cluster/executor/container/controller.go @@ -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 { diff --git a/components/engine/daemon/exec.go b/components/engine/daemon/exec.go index c913ffb75d..9b3e583bf9 100644 --- a/components/engine/daemon/exec.go +++ b/components/engine/daemon/exec.go @@ -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 diff --git a/components/engine/daemon/graphdriver/overlay/copy.go b/components/engine/daemon/graphdriver/overlay/copy.go index b8c3176941..8c35b91ddc 100644 --- a/components/engine/daemon/graphdriver/overlay/copy.go +++ b/components/engine/daemon/graphdriver/overlay/copy.go @@ -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 } diff --git a/components/engine/daemon/info.go b/components/engine/daemon/info.go index 1c4aa561bd..b14e7ba809 100644 --- a/components/engine/daemon/info.go +++ b/components/engine/daemon/info.go @@ -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(), diff --git a/components/engine/daemon/listeners/listeners_unix.go b/components/engine/daemon/listeners/listeners_unix.go index dc54606894..0a4e5e4e31 100644 --- a/components/engine/daemon/listeners/listeners_unix.go +++ b/components/engine/daemon/listeners/listeners_unix.go @@ -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 { diff --git a/components/engine/daemon/oci_linux.go b/components/engine/daemon/oci_linux.go index 36333130ac..9cf6674dfe 100644 --- a/components/engine/daemon/oci_linux.go +++ b/components/engine/daemon/oci_linux.go @@ -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) { diff --git a/components/engine/distribution/metadata/v2_metadata_service.go b/components/engine/distribution/metadata/v2_metadata_service.go index 7524f63ce7..af599be041 100644 --- a/components/engine/distribution/metadata/v2_metadata_service.go +++ b/components/engine/distribution/metadata/v2_metadata_service.go @@ -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 diff --git a/components/engine/distribution/pull_v2.go b/components/engine/distribution/pull_v2.go index 55c83f58cc..08a24e5a5b 100644 --- a/components/engine/distribution/pull_v2.go +++ b/components/engine/distribution/pull_v2.go @@ -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. diff --git a/components/engine/distribution/push_v2.go b/components/engine/distribution/push_v2.go index 3d32061e8a..5ceac8b7ea 100644 --- a/components/engine/distribution/push_v2.go +++ b/components/engine/distribution/push_v2.go @@ -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++ { diff --git a/components/engine/hack/validate/gometalinter.json b/components/engine/hack/validate/gometalinter.json index e4614b5e8e..2e0a6c2f79 100644 --- a/components/engine/hack/validate/gometalinter.json +++ b/components/engine/hack/validate/gometalinter.json @@ -8,12 +8,17 @@ "api/types/container/container_.*", "integration-cli/" ], + "Skip": [ + "integration-cli/" + ], "Enable": [ "deadcode", "gofmt", "goimports", "golint", + "interfacer", + "unconvert", "vet" ], diff --git a/components/engine/image/tarexport/load.go b/components/engine/image/tarexport/load.go index d69c9342ad..480400cd64 100644 --- a/components/engine/image/tarexport/load.go +++ b/components/engine/image/tarexport/load.go @@ -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 { diff --git a/components/engine/integration-cli/check_test.go b/components/engine/integration-cli/check_test.go index 6af5229f04..87517e7db7 100644 --- a/components/engine/integration-cli/check_test.go +++ b/components/engine/integration-cli/check_test.go @@ -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() { diff --git a/components/engine/integration-cli/cli/build/fakegit/fakegit.go b/components/engine/integration-cli/cli/build/fakegit/fakegit.go index 74faffd922..ad028dc723 100644 --- a/components/engine/integration-cli/cli/build/fakegit/fakegit.go +++ b/components/engine/integration-cli/cli/build/fakegit/fakegit.go @@ -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{}) diff --git a/components/engine/integration-cli/cli/build/fakestorage/fixtures.go b/components/engine/integration-cli/cli/build/fakestorage/fixtures.go index f6a63dcf03..8a6bb137ad 100644 --- a/components/engine/integration-cli/cli/build/fakestorage/fixtures.go +++ b/components/engine/integration-cli/cli/build/fakestorage/fixtures.go @@ -30,7 +30,7 @@ func ensureHTTPServerImage(t testingT) { } defer os.RemoveAll(tmp) - goos := testEnv.DaemonPlatform() + goos := testEnv.DaemonInfo.OSType if goos == "" { goos = "linux" } diff --git a/components/engine/integration-cli/cli/build/fakestorage/storage.go b/components/engine/integration-cli/cli/build/fakestorage/storage.go index 49f47e4368..25cd872f50 100644 --- a/components/engine/integration-cli/cli/build/fakestorage/storage.go +++ b/components/engine/integration-cli/cli/build/fakestorage/storage.go @@ -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{ diff --git a/components/engine/integration-cli/cli/cli.go b/components/engine/integration-cli/cli/cli.go index d7fadee47d..b8230b2da4 100644 --- a/components/engine/integration-cli/cli/cli.go +++ b/components/engine/integration-cli/cli/cli.go @@ -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 diff --git a/components/engine/integration-cli/docker_cli_rename_test.go b/components/engine/integration-cli/docker_cli_rename_test.go index d043620d4f..de277596fe 100644 --- a/components/engine/integration-cli/docker_cli_rename_test.go +++ b/components/engine/integration-cli/docker_cli_rename_test.go @@ -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)) } diff --git a/components/engine/integration-cli/docker_cli_run_test.go b/components/engine/integration-cli/docker_cli_run_test.go index 504c659884..340ad4b90f 100644 --- a/components/engine/integration-cli/docker_cli_run_test.go +++ b/components/engine/integration-cli/docker_cli_run_test.go @@ -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) } } diff --git a/components/engine/integration-cli/docker_utils_test.go b/components/engine/integration-cli/docker_utils_test.go index 79a9e009e8..95d2e93cfe 100644 --- a/components/engine/integration-cli/docker_utils_test.go +++ b/components/engine/integration-cli/docker_utils_test.go @@ -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() diff --git a/components/engine/integration-cli/environment/clean.go b/components/engine/integration-cli/environment/clean.go deleted file mode 100644 index 9df2470153..0000000000 --- a/components/engine/integration-cli/environment/clean.go +++ /dev/null @@ -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] == "" || fields[1] == "" { - if fields[2] != "" { - 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 -} diff --git a/components/engine/integration-cli/environment/environment.go b/components/engine/integration-cli/environment/environment.go index a8a1045901..4e04ba76f3 100644 --- a/components/engine/integration-cli/environment/environment.go +++ b/components/engine/integration-cli/environment/environment.go @@ -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 } diff --git a/components/engine/integration-cli/environment/protect.go b/components/engine/integration-cli/environment/protect.go deleted file mode 100644 index 173fba5425..0000000000 --- a/components/engine/integration-cli/environment/protect.go +++ /dev/null @@ -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 -} diff --git a/components/engine/integration-cli/fixtures/load/frozen.go b/components/engine/integration-cli/fixtures/load/frozen.go index 13cd393f36..4bec323b51 100644 --- a/components/engine/integration-cli/fixtures/load/frozen.go +++ b/components/engine/integration-cli/fixtures/load/frozen.go @@ -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, "\\") diff --git a/components/engine/integration-cli/fixtures_linux_daemon_test.go b/components/engine/integration-cli/fixtures_linux_daemon_test.go index 0011797c0e..1508762060 100644 --- a/components/engine/integration-cli/fixtures_linux_daemon_test.go +++ b/components/engine/integration-cli/fixtures_linux_daemon_test.go @@ -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 diff --git a/components/engine/integration-cli/requirement/requirement.go b/components/engine/integration-cli/requirement/requirement.go index f60917447c..9486c32520 100644 --- a/components/engine/integration-cli/requirement/requirement.go +++ b/components/engine/integration-cli/requirement/requirement.go @@ -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 { diff --git a/components/engine/integration-cli/requirements_test.go b/components/engine/integration-cli/requirements_test.go index d6cc27b1d0..0b10969996 100644 --- a/components/engine/integration-cli/requirements_test.go +++ b/components/engine/integration-cli/requirements_test.go @@ -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...) } diff --git a/components/engine/integration-cli/requirements_unix_test.go b/components/engine/integration-cli/requirements_unix_test.go index 2ed04f6e10..6ef900fc18 100644 --- a/components/engine/integration-cli/requirements_unix_test.go +++ b/components/engine/integration-cli/requirements_unix_test.go @@ -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 } diff --git a/components/engine/integration/container/main_test.go b/components/engine/integration/container/main_test.go index a54b8d9094..1c4e078400 100644 --- a/components/engine/integration/container/main_test.go +++ b/components/engine/integration/container/main_test.go @@ -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) } } diff --git a/components/engine/integration/service/inspect_test.go b/components/engine/integration/service/inspect_test.go index 601e16cd8f..e4459af437 100644 --- a/components/engine/integration/service/inspect_test.go +++ b/components/engine/integration/service/inspect_test.go @@ -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, diff --git a/components/engine/integration/service/main_test.go b/components/engine/integration/service/main_test.go index fac7427819..4d6af81895 100644 --- a/components/engine/integration/service/main_test.go +++ b/components/engine/integration/service/main_test.go @@ -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) } } diff --git a/components/engine/internal/test/environment/clean.go b/components/engine/internal/test/environment/clean.go new file mode 100644 index 0000000000..c6392dc1bc --- /dev/null +++ b/components/engine/internal/test/environment/clean.go @@ -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) + } +} diff --git a/components/engine/internal/test/environment/environment.go b/components/engine/internal/test/environment/environment.go new file mode 100644 index 0000000000..afe8929350 --- /dev/null +++ b/components/engine/internal/test/environment/environment.go @@ -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 +} diff --git a/components/engine/internal/test/environment/protect.go b/components/engine/internal/test/environment/protect.go new file mode 100644 index 0000000000..5821863298 --- /dev/null +++ b/components/engine/internal/test/environment/protect.go @@ -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 != ":" { + result = append(result, tag) + } + } + for _, digest := range image.RepoDigests { + if digest != "@" { + 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 +} diff --git a/components/engine/libcontainerd/client_unix.go b/components/engine/libcontainerd/client_unix.go index 129e87bd72..202a5b09b1 100644 --- a/components/engine/libcontainerd/client_unix.go +++ b/components/engine/libcontainerd/client_unix.go @@ -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 } diff --git a/components/engine/opts/env_test.go b/components/engine/opts/env_test.go index 6f6c7a7a29..454e989438 100644 --- a/components/engine/opts/env_test.go +++ b/components/engine/opts/env_test.go @@ -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) } } } diff --git a/components/engine/opts/opts.go b/components/engine/opts/opts.go index 8d82f76792..a86d74d60a 100644 --- a/components/engine/opts/opts.go +++ b/components/engine/opts/opts.go @@ -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 diff --git a/components/engine/opts/quotedstring.go b/components/engine/opts/quotedstring.go index fb1e5374bc..09c68a5261 100644 --- a/components/engine/opts/quotedstring.go +++ b/components/engine/opts/quotedstring.go @@ -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 { diff --git a/components/engine/pkg/archive/archive_unix.go b/components/engine/pkg/archive/archive_unix.go index 1213174322..73814d58d6 100644 --- a/components/engine/pkg/archive/archive_unix.go +++ b/components/engine/pkg/archive/archive_unix.go @@ -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 diff --git a/components/engine/pkg/archive/changes_linux.go b/components/engine/pkg/archive/changes_linux.go index b987e52245..8e96d961f3 100644 --- a/components/engine/pkg/archive/changes_linux.go +++ b/components/engine/pkg/archive/changes_linux.go @@ -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 } } diff --git a/components/engine/pkg/archive/changes_unix.go b/components/engine/pkg/archive/changes_unix.go index 98e2b39aea..7aa1226d7f 100644 --- a/components/engine/pkg/archive/changes_unix.go +++ b/components/engine/pkg/archive/changes_unix.go @@ -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 { diff --git a/components/engine/pkg/devicemapper/devmapper_log.go b/components/engine/pkg/devicemapper/devmapper_log.go index d2c0d4c76a..65a202ad2a 100644 --- a/components/engine/pkg/devicemapper/devmapper_log.go +++ b/components/engine/pkg/devicemapper/devmapper_log.go @@ -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 { diff --git a/components/engine/pkg/directory/directory_unix.go b/components/engine/pkg/directory/directory_unix.go index 397251bdb8..d4f2970a64 100644 --- a/components/engine/pkg/directory/directory_unix.go +++ b/components/engine/pkg/directory/directory_unix.go @@ -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 diff --git a/components/engine/pkg/system/stat_linux.go b/components/engine/pkg/system/stat_linux.go index 66bf6e28ee..1939f95181 100644 --- a/components/engine/pkg/system/stat_linux.go +++ b/components/engine/pkg/system/stat_linux.go @@ -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 } diff --git a/components/engine/pkg/term/ascii.go b/components/engine/pkg/term/ascii.go index f5262bccf5..55873c0556 100644 --- a/components/engine/pkg/term/ascii.go +++ b/components/engine/pkg/term/ascii.go @@ -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 diff --git a/components/engine/pkg/term/winsize.go b/components/engine/pkg/term/winsize.go index f58367fe66..85c4d9d67e 100644 --- a/components/engine/pkg/term/winsize.go +++ b/components/engine/pkg/term/winsize.go @@ -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) } diff --git a/components/engine/plugin/manager_linux.go b/components/engine/plugin/manager_linux.go index f1364e071a..84bf606346 100644 --- a/components/engine/plugin/manager_linux.go +++ b/components/engine/plugin/manager_linux.go @@ -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) diff --git a/components/engine/registry/auth.go b/components/engine/registry/auth.go index 9222dfffbc..b0a03d0480 100644 --- a/components/engine/registry/auth.go +++ b/components/engine/registry/auth.go @@ -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