From f939dd4d47aae3a851a854733bd65d0e8b9b2448 Mon Sep 17 00:00:00 2001
From: Guillaume Tardif <guillaume.tardif@docker.com>
Date: Fri, 5 Jun 2020 17:30:27 +0200
Subject: [PATCH] Display friendly message if unknown command is available in
 default context

---
 cli/dockerclassic/exec.go | 18 +++++++++++++++++-
 cli/main.go               | 18 +++++++++++++++++-
 tests/e2e/e2e_test.go     |  7 +++++++
 3 files changed, 41 insertions(+), 2 deletions(-)

diff --git a/cli/dockerclassic/exec.go b/cli/dockerclassic/exec.go
index 9bd72c29b..50411b458 100644
--- a/cli/dockerclassic/exec.go
+++ b/cli/dockerclassic/exec.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"strings"
 
 	"github.com/spf13/cobra"
 
@@ -12,6 +13,9 @@ import (
 	"github.com/docker/api/context/store"
 )
 
+// ClassicCliName name of the classic cli binary
+const ClassicCliName = "docker-classic"
+
 // Exec delegates to docker-classic
 func Exec(ctx context.Context) {
 	currentContext := apicontext.CurrentContext(ctx)
@@ -21,7 +25,7 @@ func Exec(ctx context.Context) {
 	// Only run original docker command if the current context is not
 	// ours.
 	if err != nil {
-		cmd := exec.CommandContext(ctx, "docker-classic", os.Args[1:]...)
+		cmd := exec.CommandContext(ctx, ClassicCliName, os.Args[1:]...)
 		cmd.Stdin = os.Stdin
 		cmd.Stdout = os.Stdout
 		cmd.Stderr = os.Stderr
@@ -41,3 +45,15 @@ func ExecCmd(command *cobra.Command) error {
 	Exec(command.Context())
 	return nil
 }
+
+// IsDefaultContextCommand checks if the command exists in the classic cli (issues a shellout --help)
+func IsDefaultContextCommand(dockerCommand string) bool {
+	cmd := exec.Command(ClassicCliName, dockerCommand, "--help")
+	b, e := cmd.CombinedOutput()
+	if e != nil {
+		fmt.Println(e)
+	}
+	output := string(b)
+	contains := strings.Contains(output, "Usage:\tdocker "+dockerCommand)
+	return contains
+}
diff --git a/cli/main.go b/cli/main.go
index 586b4bae4..5a2d88d55 100644
--- a/cli/main.go
+++ b/cli/main.go
@@ -34,6 +34,7 @@ import (
 	"os"
 	"os/signal"
 	"path/filepath"
+	"regexp"
 	"syscall"
 	"time"
 
@@ -171,11 +172,26 @@ func main() {
 			os.Exit(1)
 		}
 		dockerclassic.Exec(ctx)
-		fmt.Println(err)
+
+		checkIfUnknownCommandExistInDefaultContext(err, currentContext)
+		fmt.Fprintln(os.Stderr, err)
 		os.Exit(1)
 	}
 }
 
+func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string) {
+	re := regexp.MustCompile(`unknown command "([^"]*)"`)
+	submatch := re.FindSubmatch([]byte(err.Error()))
+	if len(submatch) == 2 {
+		dockerCommand := string(submatch[1])
+
+		if dockerclassic.IsDefaultContextCommand(dockerCommand) {
+			fmt.Fprintf(os.Stderr, "Command \"%s\" not available in current context (%s), you can use the \"default\" context to run this command\n", dockerCommand, currentContext)
+			os.Exit(1)
+		}
+	}
+}
+
 func newSigContext() (context.Context, func()) {
 	ctx, cancel := context.WithCancel(context.Background())
 	s := make(chan os.Signal)
diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go
index 54e39aac4..c85858637 100644
--- a/tests/e2e/e2e_test.go
+++ b/tests/e2e/e2e_test.go
@@ -162,6 +162,13 @@ func (s *E2eSuite) TestLeaveLegacyErrorMessagesUnchanged() {
 	Expect(err).NotTo(BeNil())
 }
 
+func (s *E2eSuite) TestDisplayFriendlyErrorMessageForLegacyCommands() {
+	s.NewDockerCommand("context", "create", "test-example", "example").ExecOrDie()
+	output, err := s.NewDockerCommand("--context", "test-example", "images").Exec()
+	Expect(output).To(Equal("Command \"images\" not available in current context (test-example), you can use the \"default\" context to run this command\n"))
+	Expect(err).NotTo(BeNil())
+}
+
 func (s *E2eSuite) TestMockBackend() {
 	It("creates a new test context to hardcoded example backend", func() {
 		s.NewDockerCommand("context", "create", "test-example", "example").ExecOrDie()