diff --git a/cmd/main.go b/cmd/main.go index 986b32722..026ebee34 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -29,9 +29,13 @@ package main import ( "fmt" + "io" "os" + "os/exec" "path/filepath" + "sort" + "github.com/docker/api/context" "github.com/sirupsen/logrus" "github.com/urfave/cli" ) @@ -57,19 +61,67 @@ func main() { Name: "debug", Usage: "enable debug output in the logs", }, + context.ConfigFlag, + context.ContextFlag, } + + // Make a copy of the default HelpPrinter function + originalHelpPrinter := cli.HelpPrinter + // Change the HelpPrinter function to shell out to the Moby CLI help + // when the current context is pointing to Docker engine + // else we use the copy of the original HelpPrinter + cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { + ctx, err := context.GetContext() + if err != nil { + logrus.Fatal(err) + } + if ctx.Metadata.Type == "Moby" { + shellOutToDefaultEngine() + } else { + originalHelpPrinter(w, templ, data) + } + } + app.Before = func(clix *cli.Context) error { if clix.GlobalBool("debug") { logrus.SetLevel(logrus.DebugLevel) } + ctx, err := context.GetContext() + if err != nil { + logrus.Fatal(err) + } + if ctx.Metadata.Type == "Moby" { + shellOutToDefaultEngine() + } + // TODO select backend based on context.Metadata.Type return nil } app.Commands = []cli.Command{ contextCommand, exampleCommand, } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + if err := app.Run(os.Args); err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) } } + +func shellOutToDefaultEngine() { + cmd := exec.Command("/Applications/Docker.app/Contents/Resources/bin/docker", os.Args[1:]...) + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + if err != nil { + if exiterr, ok := err.(*exec.ExitError); ok { + os.Exit(exiterr.ExitCode()) + } + os.Exit(1) + } + } + os.Exit(0) +} diff --git a/context/config.go b/context/config.go new file mode 100644 index 000000000..0a858e24c --- /dev/null +++ b/context/config.go @@ -0,0 +1,38 @@ +package context + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" +) + +func LoadConfigFile() (*ConfigFile, error) { + filename := filepath.Join(ConfigDir, ConfigFileName) + configFile := &ConfigFile{ + Filename: filename, + } + + if _, err := os.Stat(filename); err == nil { + file, err := os.Open(filename) + if err != nil { + return nil, fmt.Errorf("can't read %s: %w", filename, err) + } + defer file.Close() + err = json.NewDecoder(file).Decode(&configFile) + if err != nil { + err = fmt.Errorf("can't read %s: %w", filename, err) + } + return configFile, err + } else if !os.IsNotExist(err) { + // if file is there but we can't stat it for any reason other + // than it doesn't exist then stop + return nil, fmt.Errorf("can't read %s: %w", filename, err) + } + return configFile, nil +} + +type ConfigFile struct { + Filename string `json:"-"` // Note: for internal use only + CurrentContext string `json:"currentContext,omitempty"` +} diff --git a/context/flags.go b/context/flags.go new file mode 100644 index 000000000..0456704f7 --- /dev/null +++ b/context/flags.go @@ -0,0 +1,38 @@ +package context + +import ( + "path/filepath" + + "github.com/mitchellh/go-homedir" + "github.com/urfave/cli" +) + +const ( + // ConfigFileName is the name of config file + ConfigFileName = "config.json" + configFileDir = ".docker" +) + +var ( + ConfigDir string + ContextName string + ConfigFlag = cli.StringFlag{ + Name: "config", + Usage: "Location of client config files `DIRECTORY`", + EnvVar: "DOCKER_CONFIG", + Value: filepath.Join(home(), configFileDir), + Destination: &ConfigDir, + } + + ContextFlag = cli.StringFlag{ + Name: "context, c", + Usage: "Name of the context `CONTEXT` to use to connect to the daemon (overrides DOCKER_HOST env var and default context set with \"docker context use\")", + EnvVar: "DOCKER_CONTEXT", + Destination: &ContextName, + } +) + +func home() string { + home, _ := homedir.Dir() + return home +} diff --git a/context/store.go b/context/store.go new file mode 100644 index 000000000..8c298fd28 --- /dev/null +++ b/context/store.go @@ -0,0 +1,65 @@ +package context + +import ( + "encoding/json" + "io/ioutil" + "path/filepath" + + "github.com/opencontainers/go-digest" +) + +const ( + contextsDir = "contexts" + metadataDir = "meta" + metaFile = "meta.json" +) + +// ContextStoreDir returns the directory the docker contexts are stored in +func ContextStoreDir() string { + return filepath.Join(ConfigDir, contextsDir) +} + +type Metadata struct { + Name string `json:",omitempty"` + Metadata TypeContext `json:",omitempty"` + Endpoints map[string]interface{} `json:",omitempty"` +} + +type TypeContext struct { + Type string +} + +func GetContext() (*Metadata, error) { + config, err := LoadConfigFile() + if err != nil { + return nil, err + } + r := &Metadata{ + Endpoints: make(map[string]interface{}), + } + + if ContextName == "" { + ContextName = config.CurrentContext + } + if ContextName == "" || ContextName == "default" { + r.Metadata.Type = "Moby" + return r, nil + } + + meta := filepath.Join(ConfigDir, contextsDir, metadataDir, contextdirOf(ContextName), metaFile) + bytes, err := ioutil.ReadFile(meta) + if err != nil { + return nil, err + } + + if err := json.Unmarshal(bytes, r); err != nil { + return r, err + } + + r.Name = ContextName + return r, nil +} + +func contextdirOf(name string) string { + return digest.FromString(name).Encoded() +}