diff --git a/.dockerignore b/.dockerignore index e660fd93d..00687111e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ bin/ +tests/node-client/node_modules/ diff --git a/Makefile b/Makefile index 4bb58718d..b32125075 100644 --- a/Makefile +++ b/Makefile @@ -44,10 +44,10 @@ cli: ## Compile the cli --target cli e2e-local: ## Run End to end local tests - go run ./tests/e2e/e2e.go + go test -v ./tests/e2e e2e-aci: ## Run End to end ACI tests (requires azure login) - go run ./tests/aci-e2e/e2e-aci.go + go test -v ./tests/aci-e2e cross: ## Compile the CLI for linux, darwin and windows @docker build . \ diff --git a/azure/backend.go b/azure/backend.go index e86c989bd..a0b207437 100644 --- a/azure/backend.go +++ b/azure/backend.go @@ -43,10 +43,7 @@ func getter() interface{} { // New creates a backend that can manage containers func New(ctx context.Context) (backend.Service, error) { currentContext := apicontext.CurrentContext(ctx) - contextStore, err := store.New() - if err != nil { - return nil, err - } + contextStore := store.ContextStore(ctx) metadata, err := contextStore.Get(currentContext, getter) if err != nil { return nil, errors.Wrap(err, "wrong context type") diff --git a/builder.Makefile b/builder.Makefile index 8aeb8dea3..4b2cff499 100644 --- a/builder.Makefile +++ b/builder.Makefile @@ -55,7 +55,7 @@ cross: @GOOS=windows GOARCH=amd64 $(GO_BUILD) -o $(BINARY)-windows-amd64.exe ./cli test: - @go test ./... + @go test -cover $(shell go list ./... | grep -vE 'e2e') lint: golangci-lint run --timeout 10m0s ./... diff --git a/cli/cmd/context/context.go b/cli/cmd/context/context.go index 26edb363f..1d344dca8 100644 --- a/cli/cmd/context/context.go +++ b/cli/cmd/context/context.go @@ -31,12 +31,10 @@ import ( "github.com/spf13/cobra" "github.com/docker/api/cli/cmd/context/login" - - cliopts "github.com/docker/api/cli/options" ) // Command manages contexts -func Command(opts *cliopts.GlobalOpts) *cobra.Command { +func Command() *cobra.Command { cmd := &cobra.Command{ Use: "context", Short: "Manage contexts", @@ -47,7 +45,7 @@ func Command(opts *cliopts.GlobalOpts) *cobra.Command { listCommand(), removeCommand(), showCommand(), - useCommand(opts), + useCommand(), login.Command(), ) diff --git a/cli/cmd/context/use.go b/cli/cmd/context/use.go index 5a0c36001..0d1ce2b14 100644 --- a/cli/cmd/context/use.go +++ b/cli/cmd/context/use.go @@ -34,22 +34,21 @@ import ( "github.com/spf13/cobra" cliconfig "github.com/docker/api/cli/config" - cliopts "github.com/docker/api/cli/options" "github.com/docker/api/context/store" ) -func useCommand(opts *cliopts.GlobalOpts) *cobra.Command { +func useCommand() *cobra.Command { return &cobra.Command{ Use: "use CONTEXT", Short: "Set the default context", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return runUse(cmd.Context(), opts.Config, args[0]) + return runUse(cmd.Context(), args[0]) }, } } -func runUse(ctx context.Context, configDir string, name string) error { +func runUse(ctx context.Context, name string) error { s := store.ContextStore(ctx) // Match behavior of existing CLI if name != store.DefaultContextName { @@ -57,7 +56,7 @@ func runUse(ctx context.Context, configDir string, name string) error { return err } } - if err := cliconfig.WriteCurrentContext(configDir, name); err != nil { + if err := cliconfig.WriteCurrentContext(cliconfig.Dir(ctx), name); err != nil { return err } fmt.Println(name) diff --git a/cli/cmd/serve.go b/cli/cmd/serve.go index 6ea6c0793..5e681884e 100644 --- a/cli/cmd/serve.go +++ b/cli/cmd/serve.go @@ -21,6 +21,7 @@ type serveOpts struct { // ServeCommand returns the command to serve the API func ServeCommand() *cobra.Command { + // FIXME(chris-crone): Should warn that specified context is ignored var opts serveOpts cmd := &cobra.Command{ Use: "serve", @@ -36,14 +37,12 @@ func ServeCommand() *cobra.Command { } func runServe(ctx context.Context, opts serveOpts) error { - s := server.New() + s := server.New(ctx) listener, err := server.CreateListener(opts.address) - if err != nil { return errors.Wrap(err, "listen address "+opts.address) } - // nolint errcheck defer listener.Close() @@ -71,11 +70,7 @@ type cliServer struct { } func (cs *cliServer) Contexts(ctx context.Context, request *cliv1.ContextsRequest) (*cliv1.ContextsResponse, error) { - s, err := store.New() - if err != nil { - logrus.Error(err) - return &cliv1.ContextsResponse{}, err - } + s := store.ContextStore(ctx) contexts, err := s.List() if err != nil { logrus.Error(err) diff --git a/cli/config/config.go b/cli/config/config.go index f473b4139..49058a7d3 100644 --- a/cli/config/config.go +++ b/cli/config/config.go @@ -28,6 +28,7 @@ package config import ( + "context" "encoding/json" "io/ioutil" "os" @@ -38,6 +39,19 @@ import ( "github.com/docker/api/context/store" ) +type dirKey struct{} + +// WithDir sets the config directory path in the context +func WithDir(ctx context.Context, path string) context.Context { + return context.WithValue(ctx, dirKey{}, path) +} + +// Dir returns the config directory path +func Dir(ctx context.Context) string { + cd, _ := ctx.Value(dirKey{}).(string) + return cd +} + // LoadFile loads the docker configuration func LoadFile(dir string) (*File, error) { f := &File{} diff --git a/cli/config/flags.go b/cli/config/flags.go index de0c04f07..b9b3b3ffc 100644 --- a/cli/config/flags.go +++ b/cli/config/flags.go @@ -51,10 +51,14 @@ type ConfigFlags struct { // AddConfigFlags adds persistent (global) flags func (c *ConfigFlags) AddConfigFlags(flags *pflag.FlagSet) { - flags.StringVar(&c.Config, ConfigFlagName, filepath.Join(home(), ConfigFileDir), "Location of the client config files `DIRECTORY`") + flags.StringVar(&c.Config, ConfigFlagName, confDir(), "Location of the client config files `DIRECTORY`") } -func home() string { +func confDir() string { + env := os.Getenv("DOCKER_CONFIG") + if env != "" { + return env + } home, _ := os.UserHomeDir() - return home + return filepath.Join(home, ConfigFileDir) } diff --git a/cli/main.go b/cli/main.go index 7bff9a07a..110b80290 100644 --- a/cli/main.go +++ b/cli/main.go @@ -101,7 +101,7 @@ func main() { } root.AddCommand( - contextcmd.Command(&opts), + contextcmd.Command(), cmd.PsCommand(), cmd.ServeCommand(), run.Command(), @@ -136,13 +136,15 @@ func main() { if opts.Config == "" { fatal(errors.New("config path cannot be empty")) } + configDir := opts.Config + ctx = cliconfig.WithDir(ctx, configDir) - currentContext, err := determineCurrentContext(opts.Context, opts.Config) + currentContext, err := determineCurrentContext(opts.Context, configDir) if err != nil { fatal(errors.New("unable to determine current context")) } - s, err := store.New(store.WithRoot(opts.Config)) + s, err := store.New(store.WithRoot(configDir)) if err != nil { fatal(errors.Wrap(err, "unable to create context store")) } diff --git a/go.mod b/go.mod index 88bf18b6a..4270c3493 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,11 @@ module github.com/docker/api go 1.14 require ( - github.com/Azure/azure-pipeline-go v0.2.1 github.com/Azure/azure-sdk-for-go v42.0.0+incompatible github.com/Azure/azure-storage-file-go v0.7.0 github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/Azure/go-autorest/autorest v0.10.0 github.com/Azure/go-autorest/autorest/adal v0.8.2 - github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 github.com/Azure/go-autorest/autorest/date v0.2.0 github.com/Azure/go-autorest/autorest/to v0.3.0 @@ -45,6 +43,6 @@ require ( golang.org/x/text v0.3.2 // indirect google.golang.org/grpc v1.29.1 google.golang.org/protobuf v1.21.0 - gotest.tools v2.2.0+incompatible // indirect + gotest.tools v2.2.0+incompatible gotest.tools/v3 v3.0.2 ) diff --git a/go.sum b/go.sum index 98e43449f..6f8c8169a 100644 --- a/go.sum +++ b/go.sum @@ -7,20 +7,13 @@ github.com/Azure/azure-storage-file-go v0.7.0 h1:yWoV0MYwzmoSgWACcVkdPolvAULFPNa github.com/Azure/azure-storage-file-go v0.7.0/go.mod h1:3w3mufGcMjcOJ3w+4Gs+5wsSgkT7xDwWWqMMIrXtW4c= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= -github.com/Azure/go-autorest v1.1.1 h1:4G9tVCqooRY3vDTB2bA1Z01PlSALtnUbji0AfzthUSs= -github.com/Azure/go-autorest v14.1.0+incompatible h1:qROrS0rWxAXGfFdNOI33we8553d7T8v78jXf/8tjLBM= github.com/Azure/go-autorest/autorest v0.9.0/go.mod h1:xyHB1BMZT0cuDHU7I0+g046+BFDTQ8rEZB0s4Yfa6bI= -github.com/Azure/go-autorest/autorest v0.9.3/go.mod h1:GsRuLYvwzLjjjRoWEIyMUaYq8GNUx2nRB378IPt/1p0= github.com/Azure/go-autorest/autorest v0.10.0 h1:mvdtztBqcL8se7MdrUweNieTNi4kfNG6GOJuurQJpuY= github.com/Azure/go-autorest/autorest v0.10.0/go.mod h1:/FALq9T/kS7b5J5qsQ+RSTUdAmGFqi0vUdVNNx8q630= -github.com/Azure/go-autorest/autorest v0.10.1 h1:uaB8A32IZU9YKs9v50+/LWIWTDHJk2vlGzbfd7FfESI= github.com/Azure/go-autorest/autorest/adal v0.5.0/go.mod h1:8Z9fGy2MpX0PvDjB1pEgQTmVqjGhiHBW7RJJEciWzS0= github.com/Azure/go-autorest/autorest/adal v0.8.0/go.mod h1:Z6vX6WXXuyieHAXwMj0S6HY6e6wcHn37qQMBQlvY3lc= -github.com/Azure/go-autorest/autorest/adal v0.8.1/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= github.com/Azure/go-autorest/autorest/adal v0.8.2 h1:O1X4oexUxnZCaEUGsvMnr8ZGj8HI37tNezwY4npRqA0= github.com/Azure/go-autorest/autorest/adal v0.8.2/go.mod h1:ZjhuQClTqx435SRJ2iMlOxPYt3d2C/T/7TiQCVZSn3Q= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2 h1:iM6UAvjR97ZIeR93qTcwpKNMpV+/FTWjwEbuPD495Tk= -github.com/Azure/go-autorest/autorest/azure/auth v0.4.2/go.mod h1:90gmfKdlmKgfjUpnCEpOJzsUEjrWDSLwHIG73tSXddM= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1 h1:LXl088ZQlP0SBppGFsRZonW6hSvwgL5gRByMbvUbx8U= github.com/Azure/go-autorest/autorest/azure/cli v0.3.1/go.mod h1:ZG5p860J94/0kI9mNJVoIoLgXcirM2gF5i2kWloofxw= github.com/Azure/go-autorest/autorest/date v0.1.0/go.mod h1:plvfp3oPSKwf2DNjlBjWF/7vwR+cUD/ELuzDCXwHUVA= diff --git a/server/server.go b/server/server.go index ecac067f3..2f83f9e30 100644 --- a/server/server.go +++ b/server/server.go @@ -45,10 +45,10 @@ import ( ) // New returns a new GRPC server. -func New() *grpc.Server { +func New(ctx context.Context) *grpc.Server { s := grpc.NewServer( grpc.ChainUnaryInterceptor( - unaryMeta, + unaryMeta(ctx), unary, ), grpc.StreamInterceptor(stream), @@ -74,36 +74,34 @@ func stream(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, return grpc_prometheus.StreamServerInterceptor(srv, ss, info, handler) } -func unaryMeta(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { - md, ok := metadata.FromIncomingContext(ctx) - if !ok { +func unaryMeta(clictx context.Context) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { + md, ok := metadata.FromIncomingContext(ctx) + if !ok { + return handler(ctx, req) + } + + key, ok := md[apicontext.Key] + if !ok { + return handler(ctx, req) + } + + if len(key) == 1 { + s := store.ContextStore(clictx) + ctx = store.WithContextStore(ctx, s) + ctx = apicontext.WithCurrentContext(ctx, key[0]) + + c, err := client.New(ctx) + if err != nil { + return nil, err + } + + ctx, err = proxy.WithClient(ctx, c) + if err != nil { + return nil, err + } + } + return handler(ctx, req) } - - key, ok := md[apicontext.Key] - if !ok { - return handler(ctx, req) - } - - if len(key) == 1 { - s, err := store.New() - if err != nil { - return nil, err - } - - ctx = store.WithContextStore(ctx, s) - ctx = apicontext.WithCurrentContext(ctx, key[0]) - - c, err := client.New(ctx) - if err != nil { - return nil, err - } - - ctx, err = proxy.WithClient(ctx, c) - if err != nil { - return nil, err - } - } - - return handler(ctx, req) } diff --git a/tests/aci-e2e/e2e-aci.go b/tests/aci-e2e/e2e-aci_test.go similarity index 79% rename from tests/aci-e2e/e2e-aci.go rename to tests/aci-e2e/e2e-aci_test.go index 7eece0a7a..f18463c0f 100644 --- a/tests/aci-e2e/e2e-aci.go +++ b/tests/aci-e2e/e2e-aci_test.go @@ -6,6 +6,7 @@ import ( "log" "net/url" "strings" + "testing" "github.com/Azure/azure-sdk-for-go/profiles/2019-03-01/resources/mgmt/resources" "github.com/Azure/go-autorest/autorest/to" @@ -14,6 +15,7 @@ import ( "github.com/Azure/azure-storage-file-go/azfile" . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" "github.com/docker/api/azure" "github.com/docker/api/context/store" @@ -29,51 +31,60 @@ const ( testContainerName = "testcontainername" ) -func main() { - SetupTest() +var ( + subscriptionID string +) +type E2eACISuite struct { + Suite +} + +func (s *E2eACISuite) TestContextHelp() { It("ensures context command includes azure-login and aci-create", func() { - output := NewDockerCommand("context", "create", "--help").ExecOrDie() + output := s.NewDockerCommand("context", "create", "--help").ExecOrDie() Expect(output).To(ContainSubstring("docker context create CONTEXT BACKEND [OPTIONS] [flags]")) Expect(output).To(ContainSubstring("--aci-location")) Expect(output).To(ContainSubstring("--aci-subscription-id")) Expect(output).To(ContainSubstring("--aci-resource-group")) }) +} +func (s *E2eACISuite) TestContextDefault() { It("should be initialized with default context", func() { - _, err := NewCommand("docker", "context", "rm", "-f", contextName).Exec() + _, err := s.NewCommand("docker", "context", "rm", "-f", contextName).Exec() if err == nil { log.Println("Cleaning existing test context") } - NewCommand("docker", "context", "use", "default").ExecOrDie() - output := NewCommand("docker", "context", "ls").ExecOrDie() + s.NewCommand("docker", "context", "use", "default").ExecOrDie() + output := s.NewCommand("docker", "context", "ls").ExecOrDie() Expect(output).To(Not(ContainSubstring(contextName))) Expect(output).To(ContainSubstring("default *")) }) +} - var subscriptionID string +func (s *E2eACISuite) TestACIBackend() { It("creates a new aci context for tests", func() { setupTestResourceGroup(resourceGroupName) var err error subscriptionID, err = azure.GetSubscriptionID(context.TODO()) Expect(err).To(BeNil()) - NewDockerCommand("context", "create", contextName, "aci", "--aci-subscription-id", subscriptionID, "--aci-resource-group", resourceGroupName, "--aci-location", location).ExecOrDie() + s.NewDockerCommand("context", "create", contextName, "aci", "--aci-subscription-id", subscriptionID, "--aci-resource-group", resourceGroupName, "--aci-location", location).ExecOrDie() // Expect(output).To(ContainSubstring("ACI context acitest created")) }) defer deleteResourceGroup(resourceGroupName) It("uses the aci context", func() { - currentContext := NewCommand("docker", "context", "use", contextName).ExecOrDie() + currentContext := s.NewCommand("docker", "context", "use", contextName).ExecOrDie() Expect(currentContext).To(ContainSubstring(contextName)) - output := NewCommand("docker", "context", "ls").ExecOrDie() + output := s.NewCommand("docker", "context", "ls").ExecOrDie() Expect(output).To(ContainSubstring("acitest *")) }) It("ensures no container is running initially", func() { - output := NewDockerCommand("ps").ExecOrDie() + output := s.NewDockerCommand("ps").ExecOrDie() Expect(len(Lines(output))).To(Equal(1)) }) @@ -91,13 +102,13 @@ func main() { uploadFile(credential, u.String(), testFileName, testFileContent) mountTarget := "/usr/share/nginx/html" - output := NewDockerCommand("run", "nginx", + output := s.NewDockerCommand("run", "nginx", "-v", fmt.Sprintf("%s:%s@%s:%s", testStorageAccountName, firstKey, testShareName, mountTarget), "-p", "80:80", "--name", testContainerName).ExecOrDie() Expect(output).To(Equal(testContainerName + "\n")) - output = NewDockerCommand("ps").ExecOrDie() + output = s.NewDockerCommand("ps").ExecOrDie() lines := Lines(output) Expect(len(lines)).To(Equal(2)) @@ -108,19 +119,19 @@ func main() { Expect(exposedIP).To(ContainSubstring(":80->80/tcp")) publishedURL := strings.ReplaceAll(exposedIP, "->80/tcp", "") - output = NewCommand("curl", publishedURL).ExecOrDie() + output = s.NewCommand("curl", publishedURL).ExecOrDie() Expect(output).To(ContainSubstring(testFileContent)) }) It("removes container nginx", func() { - output := NewDockerCommand("rm", testContainerName).ExecOrDie() + output := s.NewDockerCommand("rm", testContainerName).ExecOrDie() Expect(Lines(output)[0]).To(Equal(testContainerName)) }) It("deploys a compose app", func() { - NewDockerCommand("compose", "up", "-f", "./tests/composefiles/aci-demo/aci_demo_port.yaml", "--name", "acidemo").ExecOrDie() + s.NewDockerCommand("compose", "up", "-f", "../composefiles/aci-demo/aci_demo_port.yaml", "--name", "acidemo").ExecOrDie() // Expect(output).To(ContainSubstring("Successfully deployed")) - output := NewDockerCommand("ps").ExecOrDie() + output := s.NewDockerCommand("ps").ExecOrDie() Lines := Lines(output) Expect(len(Lines)).To(Equal(4)) webChecked := false @@ -134,9 +145,9 @@ func main() { Expect(exposedIP).To(ContainSubstring(":80->80/tcp")) url := strings.ReplaceAll(exposedIP, "->80/tcp", "") - output = NewCommand("curl", url).ExecOrDie() + output = s.NewCommand("curl", url).ExecOrDie() Expect(output).To(ContainSubstring("Docker Compose demo")) - output = NewCommand("curl", url+"/words/noun").ExecOrDie() + output = s.NewCommand("curl", url+"/words/noun").ExecOrDie() Expect(output).To(ContainSubstring("\"word\":")) } } @@ -145,20 +156,20 @@ func main() { }) It("get logs from web service", func() { - output := NewDockerCommand("logs", "acidemo_web").ExecOrDie() + output := s.NewDockerCommand("logs", "acidemo_web").ExecOrDie() Expect(output).To(ContainSubstring("Listening on port 80")) }) It("shutdown compose app", func() { - NewDockerCommand("compose", "down", "-f", "./tests/composefiles/aci-demo/aci_demo_port.yaml", "--name", "acidemo").ExecOrDie() + s.NewDockerCommand("compose", "down", "-f", "../composefiles/aci-demo/aci_demo_port.yaml", "--name", "acidemo").ExecOrDie() }) It("switches back to default context", func() { - output := NewCommand("docker", "context", "use", "default").ExecOrDie() + output := s.NewCommand("docker", "context", "use", "default").ExecOrDie() Expect(output).To(ContainSubstring("default")) }) It("deletes test context", func() { - output := NewCommand("docker", "context", "rm", contextName).ExecOrDie() + output := s.NewCommand("docker", "context", "rm", contextName).ExecOrDie() Expect(output).To(ContainSubstring(contextName)) }) } @@ -212,6 +223,10 @@ func uploadFile(credential azfile.SharedKeyCredential, baseURL, fileName, fileCo Expect(err).To(BeNil()) } +func TestE2eACI(t *testing.T) { + suite.Run(t, new(E2eACISuite)) +} + func setupTestResourceGroup(groupName string) { log.Println("Creating resource group " + resourceGroupName) ctx := context.TODO() diff --git a/tests/e2e/e2e.go b/tests/e2e/e2e_test.go similarity index 51% rename from tests/e2e/e2e.go rename to tests/e2e/e2e_test.go index d69bd7258..89b780884 100644 --- a/tests/e2e/e2e.go +++ b/tests/e2e/e2e_test.go @@ -1,81 +1,85 @@ package main import ( + "fmt" "os" "os/exec" + "testing" "time" . "github.com/onsi/gomega" + "github.com/stretchr/testify/suite" . "github.com/docker/api/tests/framework" ) -func main() { - SetupTest() +type E2eSuite struct { + Suite +} +func (s *E2eSuite) TestContextHelp() { It("ensures context command includes azure-login and aci-create", func() { - output := NewDockerCommand("context", "create", "--help").ExecOrDie() + output := s.NewDockerCommand("context", "create", "--help").ExecOrDie() Expect(output).To(ContainSubstring("docker context create CONTEXT BACKEND [OPTIONS] [flags]")) Expect(output).To(ContainSubstring("--aci-location")) Expect(output).To(ContainSubstring("--aci-subscription-id")) Expect(output).To(ContainSubstring("--aci-resource-group")) }) +} +func (s *E2eSuite) TestContextDefault() { It("should be initialized with default context", func() { - NewDockerCommand("context", "use", "default").ExecOrDie() - output := NewDockerCommand("context", "show").ExecOrDie() + s.NewDockerCommand("context", "use", "default").ExecOrDie() + output := s.NewDockerCommand("context", "show").ExecOrDie() Expect(output).To(ContainSubstring("default")) - output = NewCommand("docker", "context", "ls").ExecOrDie() + output = s.NewCommand("docker", "context", "ls").ExecOrDie() Expect(output).To(Not(ContainSubstring("test-example"))) Expect(output).To(ContainSubstring("default *")) }) +} +func (s *E2eSuite) TestLegacy() { It("should list all legacy commands", func() { - output := NewDockerCommand("--help").ExecOrDie() + output := s.NewDockerCommand("--help").ExecOrDie() Expect(output).To(ContainSubstring("swarm")) }) It("should execute legacy commands", func() { - output, _ := NewDockerCommand("swarm", "join").Exec() + output, _ := s.NewDockerCommand("swarm", "join").Exec() Expect(output).To(ContainSubstring("\"docker swarm join\" requires exactly 1 argument.")) }) - It("should run local container in less than 5 secs", func() { - NewDockerCommand("pull", "hello-world").ExecOrDie() - output := NewDockerCommand("run", "hello-world").WithTimeout(time.NewTimer(5 * time.Second).C).ExecOrDie() + It("should run local container in less than 10 secs", func() { + s.NewDockerCommand("pull", "hello-world").ExecOrDie() + output := s.NewDockerCommand("run", "--rm", "hello-world").WithTimeout(time.NewTimer(10 * time.Second).C).ExecOrDie() Expect(output).To(ContainSubstring("Hello from Docker!")) }) +} - It("should list local container", func() { - output := NewDockerCommand("ps", "-a").ExecOrDie() - Expect(output).To(ContainSubstring("hello-world")) - }) - +func (s *E2eSuite) TestMockBackend() { It("creates a new test context to hardcoded example backend", func() { - NewDockerCommand("context", "create", "test-example", "example").ExecOrDie() + s.NewDockerCommand("context", "create", "test-example", "example").ExecOrDie() // Expect(output).To(ContainSubstring("test-example context acitest created")) }) - defer NewDockerCommand("context", "rm", "test-example").ExecOrDie() - defer NewDockerCommand("context", "use", "default").ExecOrDie() It("uses the test context", func() { - currentContext := NewDockerCommand("context", "use", "test-example").ExecOrDie() + currentContext := s.NewDockerCommand("context", "use", "test-example").ExecOrDie() Expect(currentContext).To(ContainSubstring("test-example")) - output := NewDockerCommand("context", "ls").ExecOrDie() + output := s.NewDockerCommand("context", "ls").ExecOrDie() Expect(output).To(ContainSubstring("test-example *")) - output = NewDockerCommand("context", "show").ExecOrDie() + output = s.NewDockerCommand("context", "show").ExecOrDie() Expect(output).To(ContainSubstring("test-example")) }) It("can run ps command", func() { - output := NewDockerCommand("ps").ExecOrDie() + output := s.NewDockerCommand("ps").ExecOrDie() lines := Lines(output) Expect(len(lines)).To(Equal(3)) Expect(lines[2]).To(ContainSubstring("1234 alpine")) }) It("can run quiet ps command", func() { - output := NewDockerCommand("ps", "-q").ExecOrDie() + output := s.NewDockerCommand("ps", "-q").ExecOrDie() lines := Lines(output) Expect(len(lines)).To(Equal(2)) Expect(lines[0]).To(Equal("id")) @@ -83,7 +87,7 @@ func main() { }) It("can run ps command with all ", func() { - output := NewDockerCommand("ps", "-q", "--all").ExecOrDie() + output := s.NewDockerCommand("ps", "-q", "--all").ExecOrDie() lines := Lines(output) Expect(len(lines)).To(Equal(3)) Expect(lines[0]).To(Equal("id")) @@ -92,28 +96,42 @@ func main() { }) It("can run 'run' command", func() { - output := NewDockerCommand("run", "nginx", "-p", "80:80").ExecOrDie() + output := s.NewDockerCommand("run", "nginx", "-p", "80:80").ExecOrDie() Expect(output).To(ContainSubstring("Running container \"nginx\" with name")) }) +} +func (s *E2eSuite) TestAPIServer() { + _, err := exec.LookPath("yarn") + if err != nil || os.Getenv("SKIP_NODE") != "" { + s.T().Skip("skipping, yarn not installed") + } It("can run 'serve' command", func() { - server, err := startCliServer() - Expect(err).To(BeNil()) - defer killCliServer(server) + cName := "test-example" + s.NewDockerCommand("context", "create", cName, "example").ExecOrDie() - NewCommand("yarn", "install").WithinDirectory("tests/node-client").ExecOrDie() - output := NewCommand("yarn", "run", "start", "test-example").WithinDirectory("tests/node-client").ExecOrDie() + sPath := fmt.Sprintf("unix:///%s/docker.sock", s.ConfigDir) + server, err := serveAPI(s.ConfigDir, sPath) + Expect(err).To(BeNil()) + defer killProcess(server) + + s.NewCommand("yarn", "install").WithinDirectory("../node-client").ExecOrDie() + output := s.NewCommand("yarn", "run", "start", cName, sPath).WithinDirectory("../node-client").ExecOrDie() Expect(output).To(ContainSubstring("nginx")) }) } -func killCliServer(process *os.Process) { +func TestE2e(t *testing.T) { + suite.Run(t, new(E2eSuite)) +} + +func killProcess(process *os.Process) { err := process.Kill() Expect(err).To(BeNil()) } -func startCliServer() (*os.Process, error) { - cmd := exec.Command("./bin/docker", "serve", "--address", "unix:///tmp/backend.sock") +func serveAPI(configDir string, address string) (*os.Process, error) { + cmd := exec.Command("../../bin/docker", "--config", configDir, "serve", "--address", address) err := cmd.Start() if err != nil { return nil, err diff --git a/tests/framework/exec.go b/tests/framework/exec.go index d84a83128..11fb836f8 100644 --- a/tests/framework/exec.go +++ b/tests/framework/exec.go @@ -4,13 +4,18 @@ import ( "bytes" "fmt" "io" + "io/ioutil" + "os" "os/exec" + "path/filepath" "runtime" "strings" "time" "github.com/onsi/gomega" log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" ) func (b CmdContext) makeCmd() *exec.Cmd { @@ -35,25 +40,79 @@ type RetriesContext struct { interval time.Duration } +// Suite is used to store context information for e2e tests +type Suite struct { + suite.Suite + ConfigDir string + BinDir string +} + +// SetupSuite is run before running any tests +func (s *Suite) SetupSuite() { + d, _ := ioutil.TempDir("", "") + s.BinDir = d + gomega.RegisterFailHandler(func(message string, callerSkip ...int) { + log.Error(message) + cp := filepath.Join(s.ConfigDir, "config.json") + d, _ := ioutil.ReadFile(cp) + fmt.Printf("Contents of %s:\n%s\n\nContents of config dir:\n", cp, string(d)) + out, _ := s.NewCommand("find", s.ConfigDir).Exec() + fmt.Println(out) + s.T().Fail() + }) + s.linkClassicDocker() +} + +// TearDownSuite is run after all tests +func (s *Suite) TearDownSuite() { + _ = os.RemoveAll(s.BinDir) +} + +func (s *Suite) linkClassicDocker() { + p, err := exec.LookPath("docker") + gomega.Expect(err).To(gomega.BeNil()) + err = os.Symlink(p, filepath.Join(s.BinDir, "docker-classic")) + gomega.Expect(err).To(gomega.BeNil()) + err = os.Setenv("PATH", fmt.Sprintf("%s:%s", s.BinDir, os.Getenv("PATH"))) + gomega.Expect(err).To(gomega.BeNil()) +} + +// BeforeTest is run before each test +func (s *Suite) BeforeTest(suite, test string) { + d, _ := ioutil.TempDir("", "") + s.ConfigDir = d +} + +// AfterTest is run after each test +func (s *Suite) AfterTest(suite, test string) { + err := os.RemoveAll(s.ConfigDir) + require.NoError(s.T(), err) +} + // NewCommand creates a command context. -func NewCommand(command string, args ...string) *CmdContext { +func (s *Suite) NewCommand(command string, args ...string) *CmdContext { + var envs []string + if s.ConfigDir != "" { + envs = append(os.Environ(), fmt.Sprintf("DOCKER_CONFIG=%s", s.ConfigDir)) + } return &CmdContext{ command: command, args: args, + envs: envs, retries: RetriesContext{interval: time.Second}, } } func dockerExecutable() string { if runtime.GOOS == "windows" { - return "./bin/docker.exe" + return "../../bin/docker.exe" } - return "./bin/docker" + return "../../bin/docker" } // NewDockerCommand creates a docker builder. -func NewDockerCommand(args ...string) *CmdContext { - return NewCommand(dockerExecutable(), args...) +func (s *Suite) NewDockerCommand(args ...string) *CmdContext { + return s.NewCommand(dockerExecutable(), args...) } // WithinDirectory tells Docker the cwd. diff --git a/tests/framework/helper.go b/tests/framework/helper.go index 45a461fec..1d6e27c9d 100644 --- a/tests/framework/helper.go +++ b/tests/framework/helper.go @@ -1,14 +1,10 @@ package framework import ( - "fmt" "log" - "os" "strings" "github.com/robpike/filter" - - "github.com/onsi/gomega" ) func nonEmptyString(s string) bool { @@ -30,28 +26,3 @@ func It(description string, test func()) { test() log.Print("Passed: ", description) } - -func gomegaFailHandler(message string, callerSkip ...int) { - log.Fatal(message) -} - -//SetupTest Init gomega fail handler -func SetupTest() { - gomega.RegisterFailHandler(gomegaFailHandler) - - linkClassicDocker() -} - -func linkClassicDocker() { - dockerOriginal := strings.TrimSuffix(NewCommand("which", "docker").ExecOrDie(), "\n") - _, err := NewCommand("rm", "-r", "./bin/tests").Exec() - if err == nil { - fmt.Println("Removing existing /bin/tests folder before running tests") - } - _, err = NewCommand("mkdir", "-p", "./bin/tests").Exec() - gomega.Expect(err).To(gomega.BeNil()) - NewCommand("ln", "-s", dockerOriginal, "./bin/tests/docker-classic").ExecOrDie() - newPath := "./bin/tests:" + os.Getenv("PATH") - err = os.Setenv("PATH", newPath) - gomega.Expect(err).To(gomega.BeNil()) -} diff --git a/tests/node-client/index.ts b/tests/node-client/index.ts index 0a85c1199..eee27ce4f 100644 --- a/tests/node-client/index.ts +++ b/tests/node-client/index.ts @@ -3,8 +3,10 @@ import * as continersPb from "./grpc/containers_grpc_pb"; import { IContainersClient } from './grpc/containers_grpc_pb'; import { ListRequest, ListResponse } from "./grpc/containers_pb"; +let address = process.argv[3] || "unix:///tmp/backend.sock"; + const ContainersServiceClient = grpc.makeClientConstructor(continersPb["com.docker.api.containers.v1.Containers"], "ContainersClient"); -const client = new ContainersServiceClient("unix:///tmp/backend.sock", grpc.credentials.createInsecure()) as unknown as IContainersClient; +const client = new ContainersServiceClient(address, grpc.credentials.createInsecure()) as unknown as IContainersClient; let backend = process.argv[2] || "moby"; const meta = new grpc.Metadata();