Răsfoiți Sursa

Return a default implementation when nil

Some backends can decide not to implement a whole set of APIs (compose
for example), we now return a default implementation that returns a
`errdefs.ErrNotImplemented` for each action making it easy for the cli
to print a helpful error message. We also remove any possible nil
panics.

Signed-off-by: Djordje Lukic <[email protected]>
Djordje Lukic 5 ani în urmă
părinte
comite
635ecd7b99

+ 3 - 22
cli/cmd/compose/compose.go

@@ -20,13 +20,10 @@ import (
 	"context"
 
 	"github.com/compose-spec/compose-go/cli"
-	"github.com/pkg/errors"
 
 	"github.com/spf13/cobra"
 
 	"github.com/docker/api/client"
-	apicontext "github.com/docker/api/context"
-	"github.com/docker/api/context/store"
 	"github.com/docker/api/errdefs"
 )
 
@@ -67,26 +64,10 @@ func Command() *cobra.Command {
 }
 
 func checkComposeSupport(ctx context.Context) error {
-	c, err := client.New(ctx)
-	if err == nil {
-		composeService := c.ComposeService()
-		if composeService == nil {
-			return errors.New("compose not implemented in current context")
-		}
-		return nil
-	}
+	_, err := client.New(ctx)
 	if errdefs.IsNotFoundError(err) {
-		currentContext := apicontext.CurrentContext(ctx)
-		s := store.ContextStore(ctx)
-		cc, err := s.Get(currentContext)
-		if err != nil {
-			return err
-		}
-		if cc.Type() == store.AwsContextType {
-			return errors.Errorf(`%q context type has been renamed. Recreate the context by running: 
-$ docker context create %s <name>`, cc.Type(), store.EcsContextType)
-		}
-		return errors.Wrapf(errdefs.ErrNotImplemented, "compose command not supported on context type %q", cc.Type())
+		return errdefs.ErrNotImplemented
 	}
+
 	return err
 }

+ 1 - 7
cli/cmd/compose/down.go

@@ -18,7 +18,6 @@ package compose
 
 import (
 	"context"
-	"errors"
 
 	"github.com/spf13/cobra"
 
@@ -47,16 +46,11 @@ func runDown(ctx context.Context, opts composeOptions) error {
 		return err
 	}
 
-	composeService := c.ComposeService()
-	if composeService == nil {
-		return errors.New("compose not implemented in current context")
-	}
-
 	return progress.Run(ctx, func(ctx context.Context) error {
 		options, err := opts.toProjectOptions()
 		if err != nil {
 			return err
 		}
-		return composeService.Down(ctx, options)
+		return c.ComposeService().Down(ctx, options)
 	})
 }

+ 1 - 7
cli/cmd/compose/logs.go

@@ -18,7 +18,6 @@ package compose
 
 import (
 	"context"
-	"errors"
 	"os"
 
 	"github.com/spf13/cobra"
@@ -47,14 +46,9 @@ func runLogs(ctx context.Context, opts composeOptions) error {
 		return err
 	}
 
-	composeService := c.ComposeService()
-	if composeService == nil {
-		return errors.New("compose not implemented in current context")
-	}
-
 	options, err := opts.toProjectOptions()
 	if err != nil {
 		return err
 	}
-	return composeService.Logs(ctx, options, os.Stdout)
+	return c.ComposeService().Logs(ctx, options, os.Stdout)
 }

+ 1 - 7
cli/cmd/compose/ps.go

@@ -18,7 +18,6 @@ package compose
 
 import (
 	"context"
-	"errors"
 	"fmt"
 	"io"
 	"os"
@@ -51,16 +50,11 @@ func runPs(ctx context.Context, opts composeOptions) error {
 		return err
 	}
 
-	composeService := c.ComposeService()
-	if composeService == nil {
-		return errors.New("compose not implemented in current context")
-	}
-
 	options, err := opts.toProjectOptions()
 	if err != nil {
 		return err
 	}
-	serviceList, err := composeService.Ps(ctx, options)
+	serviceList, err := c.ComposeService().Ps(ctx, options)
 	if err != nil {
 		return err
 	}

+ 1 - 7
cli/cmd/compose/up.go

@@ -18,7 +18,6 @@ package compose
 
 import (
 	"context"
-	"errors"
 
 	"github.com/spf13/cobra"
 
@@ -49,16 +48,11 @@ func runUp(ctx context.Context, opts composeOptions) error {
 		return err
 	}
 
-	composeService := c.ComposeService()
-	if composeService == nil {
-		return errors.New("compose not implemented in current context")
-	}
-
 	return progress.Run(ctx, func(ctx context.Context) error {
 		options, err := opts.toProjectOptions()
 		if err != nil {
 			return err
 		}
-		return composeService.Up(ctx, options)
+		return c.ComposeService().Up(ctx, options)
 	})
 }

+ 19 - 3
cli/main.go

@@ -175,6 +175,11 @@ func main() {
 		ctype = cc.Type()
 	}
 
+	if ctype == store.AwsContextType {
+		exit(root, currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running: 
+$ docker context create %s <name>`, cc.Type(), store.EcsContextType))
+	}
+
 	metrics.Track(ctype, os.Args[1:], root.PersistentFlags())
 
 	ctx = apicontext.WithCurrentContext(ctx, currentContext)
@@ -189,21 +194,32 @@ func main() {
 		// Context should always be handled by new CLI
 		requiredCmd, _, _ := root.Find(os.Args[1:])
 		if requiredCmd != nil && isOwnCommand(requiredCmd) {
-			exit(err)
+			exit(root, currentContext, err)
 		}
 		mobycli.ExecIfDefaultCtxType(ctx)
 
 		checkIfUnknownCommandExistInDefaultContext(err, currentContext)
 
-		exit(err)
+		exit(root, currentContext, err)
 	}
 }
 
-func exit(err error) {
+func exit(cmd *cobra.Command, ctx string, err error) {
 	if errors.Is(err, errdefs.ErrLoginRequired) {
 		fmt.Fprintln(os.Stderr, err)
 		os.Exit(errdefs.ExitCodeLoginRequired)
 	}
+	if errors.Is(err, errdefs.ErrNotImplemented) {
+		cmd, _, _ := cmd.Traverse(os.Args[1:])
+		name := cmd.Name()
+		parent := cmd.Parent()
+		if parent != nil && parent.Parent() != nil {
+			name = parent.Name() + " " + name
+		}
+		fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s)\n", name, ctx)
+		os.Exit(1)
+	}
+
 	fatal(err)
 }
 

+ 16 - 5
client/client.go

@@ -19,14 +19,13 @@ package client
 import (
 	"context"
 
-	"github.com/docker/api/secrets"
-
 	"github.com/docker/api/backend"
 	"github.com/docker/api/compose"
 	"github.com/docker/api/containers"
 	apicontext "github.com/docker/api/context"
 	"github.com/docker/api/context/cloud"
 	"github.com/docker/api/context/store"
+	"github.com/docker/api/secrets"
 )
 
 // New returns a backend client associated with current context
@@ -63,15 +62,27 @@ type Client struct {
 
 // ContainerService returns the backend service for the current context
 func (c *Client) ContainerService() containers.Service {
-	return c.bs.ContainerService()
+	if cs := c.bs.ContainerService(); cs != nil {
+		return cs
+	}
+
+	return &containerService{}
 }
 
 // ComposeService returns the backend service for the current context
 func (c *Client) ComposeService() compose.Service {
-	return c.bs.ComposeService()
+	if cs := c.bs.ComposeService(); cs != nil {
+		return cs
+	}
+
+	return &composeService{}
 }
 
 // SecretsService returns the backend service for the current context
 func (c *Client) SecretsService() secrets.Service {
-	return c.bs.SecretsService()
+	if ss := c.bs.SecretsService(); ss != nil {
+		return ss
+	}
+
+	return &secretsService{}
 }

+ 55 - 0
client/compose.go

@@ -0,0 +1,55 @@
+/*
+   Copyright 2020 Docker, Inc.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package client
+
+import (
+	"context"
+	"io"
+
+	"github.com/compose-spec/compose-go/cli"
+
+	"github.com/docker/api/compose"
+	"github.com/docker/api/errdefs"
+)
+
+type composeService struct {
+}
+
+// Up executes the equivalent to a `compose up`
+func (c *composeService) Up(context.Context, *cli.ProjectOptions) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Down executes the equivalent to a `compose down`
+func (c *composeService) Down(context.Context, *cli.ProjectOptions) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Logs executes the equivalent to a `compose logs`
+func (c *composeService) Logs(context.Context, *cli.ProjectOptions, io.Writer) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Ps executes the equivalent to a `compose ps`
+func (c *composeService) Ps(context.Context, *cli.ProjectOptions) ([]compose.ServiceStatus, error) {
+	return nil, errdefs.ErrNotImplemented
+}
+
+// Convert translate compose model into backend's native format
+func (c *composeService) Convert(context.Context, *cli.ProjectOptions) ([]byte, error) {
+	return nil, errdefs.ErrNotImplemented
+}

+ 67 - 0
client/containers.go

@@ -0,0 +1,67 @@
+/*
+   Copyright 2020 Docker, Inc.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package client
+
+import (
+	"context"
+
+	"github.com/docker/api/containers"
+	"github.com/docker/api/errdefs"
+)
+
+type containerService struct {
+}
+
+// List returns all the containers
+func (c *containerService) List(context.Context, bool) ([]containers.Container, error) {
+	return nil, errdefs.ErrNotImplemented
+}
+
+// Start starts a stopped container
+func (c *containerService) Start(context.Context, string) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Stop stops the running container
+func (c *containerService) Stop(context.Context, string, *uint32) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Run creates and starts a container
+func (c *containerService) Run(context.Context, containers.ContainerConfig) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Exec executes a command inside a running container
+func (c *containerService) Exec(context.Context, string, containers.ExecRequest) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Logs returns all the logs of a container
+func (c *containerService) Logs(context.Context, string, containers.LogsRequest) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Delete removes containers
+func (c *containerService) Delete(context.Context, string, containers.DeleteRequest) error {
+	return errdefs.ErrNotImplemented
+}
+
+// Inspect get a specific container
+func (c *containerService) Inspect(context.Context, string) (containers.Container, error) {
+	return containers.Container{}, errdefs.ErrNotImplemented
+}

+ 43 - 0
client/secrets.go

@@ -0,0 +1,43 @@
+/*
+   Copyright 2020 Docker, Inc.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package client
+
+import (
+	"context"
+
+	"github.com/docker/api/errdefs"
+	"github.com/docker/api/secrets"
+)
+
+type secretsService struct {
+}
+
+func (s *secretsService) CreateSecret(context.Context, secrets.Secret) (string, error) {
+	return "", errdefs.ErrNotImplemented
+}
+
+func (s *secretsService) InspectSecret(context.Context, string) (secrets.Secret, error) {
+	return secrets.Secret{}, errdefs.ErrNotImplemented
+}
+
+func (s *secretsService) ListSecrets(context.Context) ([]secrets.Secret, error) {
+	return nil, errdefs.ErrNotImplemented
+}
+
+func (s *secretsService) DeleteSecret(context.Context, string, bool) error {
+	return errdefs.ErrNotImplemented
+}

+ 1 - 1
tests/e2e/e2e_test.go

@@ -54,7 +54,7 @@ func TestComposeNotImplemented(t *testing.T) {
 	res = c.RunDockerCmd("compose", "up")
 	res.Assert(t, icmd.Expected{
 		ExitCode: 1,
-		Err:      `compose command not supported on context type "moby": not implemented`,
+		Err:      `Command "compose up" not available in current context (default)`,
 	})
 }