Skip to content

Commit

Permalink
Fast Context Switch: commands
Browse files Browse the repository at this point in the history
Signed-off-by: Simon Ferquel <[email protected]>
  • Loading branch information
simonferquel committed Nov 9, 2018
1 parent f27f51f commit a08fd72
Show file tree
Hide file tree
Showing 11 changed files with 617 additions and 0 deletions.
4 changes: 4 additions & 0 deletions cli/command/commands/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/docker/cli/cli/command/checkpoint"
"github.com/docker/cli/cli/command/config"
"github.com/docker/cli/cli/command/container"
"github.com/docker/cli/cli/command/context"
"github.com/docker/cli/cli/command/engine"
"github.com/docker/cli/cli/command/image"
"github.com/docker/cli/cli/command/manifest"
Expand Down Expand Up @@ -86,6 +87,9 @@ func AddCommands(cmd *cobra.Command, dockerCli command.Cli) {
// volume
volume.NewVolumeCommand(dockerCli),

// context
context.NewContextCommand(dockerCli),

// legacy commands may be hidden
hide(system.NewEventsCommand(dockerCli)),
hide(system.NewInfoCommand(dockerCli)),
Expand Down
28 changes: 28 additions & 0 deletions cli/command/context/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package context

import (
"github.com/spf13/cobra"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
)

// NewContextCommand returns the context cli subcommand
func NewContextCommand(dockerCli command.Cli) *cobra.Command {
cmd := &cobra.Command{
Use: "context",
Aliases: []string{"ctx"},
Short: "Manage contexts",
Args: cli.NoArgs,
RunE: command.ShowHelp(dockerCli.Err()),
}
cmd.AddCommand(
newCreateCommand(dockerCli),
newListCommand(dockerCli),
newUseCommand(dockerCli),
newExportCommand(dockerCli),
newImportCommand(dockerCli),
newRemoveCommand(dockerCli),
)
return cmd
}
222 changes: 222 additions & 0 deletions cli/command/context/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package context

import (
"io/ioutil"
"os"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/context/docker"
"github.com/docker/cli/cli/context/kubernetes"
"github.com/docker/cli/cli/context/store"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

type createOptions struct {
name string
description string // --description string: set the description
defaultStackOrchestrator string
docker dockerEndpointOptions
kubernetes kubernetedEndpointOptions
}

func (o *createOptions) addFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.description, "description", "", "set the description of the context")
flags.StringVar(&o.defaultStackOrchestrator, "default-stack-orchestrator", "", "set the default orchestrator for stack operations if different to the default one, to use with this context (swarm|kubernetes|all)")
o.docker.addFlags(flags)
o.kubernetes.addFlags(flags)
}

type dockerEndpointOptions struct {
host string //--moby-host string: set moby endpoint host (same format as DOCKER_HOST)
apiVersion string
ca string //--moby-ca string: path to a CA file
cert string //--moby-cert string: path to a client cert file
key string //--moby-key string: path to a client key file
skipTLSVerify bool
}

func (o *dockerEndpointOptions) addFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.host, "docker-host", "", "required: specify the docker endpoint on wich to connect")
flags.StringVar(&o.apiVersion, "docker-api-version", "", "override negociated api version")
flags.StringVar(&o.ca, "docker-tls-ca", "", "path to the ca file to validate docker endpoint")
flags.StringVar(&o.cert, "docker-tls-cert", "", "path to the cert file to authenticate to the docker endpoint")
flags.StringVar(&o.key, "docker-tls-key", "", "path to the key file to authenticate to the docker endpoint")
flags.BoolVar(&o.skipTLSVerify, "docker-tls-skip-verify", false, "skip tls verify when connecting to the docker endpoint")
}

func (o *dockerEndpointOptions) toEndpoint(contextName string) (docker.Endpoint, error) {
var (
ca, key, cert []byte
err error
tlsData *docker.TLSData
)
if o.ca != "" {
if ca, err = ioutil.ReadFile(o.ca); err != nil {
return docker.Endpoint{}, err
}
}
if o.cert != "" {
if cert, err = ioutil.ReadFile(o.cert); err != nil {
return docker.Endpoint{}, err
}
}
if o.key != "" {
if key, err = ioutil.ReadFile(o.key); err != nil {
return docker.Endpoint{}, err
}
}
if ca != nil || key != nil || cert != nil {
tlsData = &docker.TLSData{
CA: ca,
Cert: cert,
Key: key,
}
}
return docker.Endpoint{
EndpointMeta: docker.EndpointMeta{
APIVersion: o.apiVersion,
ContextName: contextName,
Host: o.host,
SkipTLSVerify: o.skipTLSVerify,
},
TLSData: tlsData,
}, nil
}

type kubernetedEndpointOptions struct {
server string //--kubernetes-server string: kubernetes api server address
ca string //--kubernetes-ca string: path to a CA file
cert string //--kubernetes-cert string: path to a client cert file
key string //--kubernetes-key string: path to a client key file
skipTLSVerify bool
defaultNamespace string
kubeconfigFile string //--kubernetes-kubeconfig-file string: path to a kubernetes cli config file
kubeconfigContext string //--kubernetes-kubeconfig-context string: name of the kubernetes cli config file context to use
}

func (o *kubernetedEndpointOptions) addFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.server, "kubernetes-host", "", "required: specify the docker endpoint on wich to connect")
flags.StringVar(&o.ca, "kubernetes-tls-ca", "", "path to the ca file to validate kubernetes endpoint")
flags.StringVar(&o.cert, "kubernetes-tls-cert", "", "path to the cert file to authenticate to the kubernetes endpoint")
flags.StringVar(&o.key, "kubernetes-tls-key", "", "path to the key file to authenticate to the kubernetes endpoint")
flags.BoolVar(&o.skipTLSVerify, "kubernetes-tls-skip-verify", false, "skip tls verify when connecting to the kubernetes endpoint")
flags.StringVar(&o.defaultNamespace, "kubernetes-default-namespace", "default", "override default namespace when connecting to kubernetes endpoint")
flags.StringVar(&o.kubeconfigFile, "kubernetes-kubeconfig", "", "path to an existing kubeconfig file")
flags.StringVar(&o.kubeconfigContext, "kubernetes-kubeconfig-context", "", `context to use in the kubeconfig file referenced in "kubernetes-kubeconfig"`)
}

func (o *kubernetedEndpointOptions) toEndpoint(contextName string) (*kubernetes.Endpoint, error) {
if o.kubeconfigFile != "" {
ep, err := kubernetes.FromKubeConfig(contextName, o.kubeconfigFile, o.kubeconfigContext, o.defaultNamespace)
if err != nil {
return nil, err
}
return &ep, nil
}
if o.server != "" {
var (
ca, key, cert []byte
err error
tlsData *kubernetes.TLSData
)
if o.ca != "" {
if ca, err = ioutil.ReadFile(o.ca); err != nil {
return nil, err
}
}
if o.cert != "" {
if cert, err = ioutil.ReadFile(o.cert); err != nil {
return nil, err
}
}
if o.key != "" {
if key, err = ioutil.ReadFile(o.key); err != nil {
return nil, err
}
}
if ca != nil || key != nil || cert != nil {
tlsData = &kubernetes.TLSData{
CA: ca,
Cert: cert,
Key: key,
}
}
return &kubernetes.Endpoint{
EndpointMeta: kubernetes.EndpointMeta{
ContextName: contextName,
DefaultNamespace: o.defaultNamespace,
Server: o.server,
SkipTLSVerify: o.skipTLSVerify,
},
TLSData: tlsData,
}, nil
}
return nil, nil
}

func loadFileIfNotEmpty(path string) ([]byte, error) {
if path == "" {
return nil, nil
}
return ioutil.ReadFile(path)
}

func (o *createOptions) process(s store.Store) error {
if _, err := s.GetContextMetadata(o.name); !os.IsNotExist(err) {
if err != nil {
return errors.Wrap(err, "error while getting existing contexts")
}
return errors.Errorf("context %q already exists", o.name)
}
stackOrchestrator, err := command.NormalizeOrchestrator(o.defaultStackOrchestrator)
if err != nil {
return errors.Wrap(err, "unable to parse default-stack-orchestrator")
}
dockerEP, err := o.docker.toEndpoint(o.name)
if err != nil {
return errors.Wrap(err, "unable to create docker endpoint config")
}
if err := docker.Save(s, dockerEP); err != nil {
return errors.Wrap(err, "unable to save docker endpoint config")
}
kubernetesEP, err := o.kubernetes.toEndpoint(o.name)
if err != nil {
return errors.Wrap(err, "unable to create kubernetes endpoint config")
}
if kubernetesEP != nil {
if err := kubernetes.Save(s, *kubernetesEP); err != nil {
return errors.Wrap(err, "unable to save kubernetes endpoint config")
}
}

// at this point, the context should exist with endpoints configuration
ctx, err := s.GetContextMetadata(o.name)
if err != nil {
return errors.Wrap(err, "error while getting context")
}
command.SetContextMetadata(&ctx, command.ContextMetadata{
Description: o.description,
StackOrchestrator: stackOrchestrator,
})

return s.CreateOrUpdateContext(o.name, ctx)
}

func newCreateCommand(dockerCli command.Cli) *cobra.Command {
opts := &createOptions{}
cmd := &cobra.Command{
Use: "create <name> [options]",
Short: "create a context",
Args: cli.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
opts.name = args[0]
return opts.process(dockerCli.ContextStore())
},
}

opts.addFlags(cmd.Flags())
return cmd
}
81 changes: 81 additions & 0 deletions cli/command/context/export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package context

import (
"fmt"
"io"
"os"

"github.com/docker/cli/cli"
"github.com/docker/cli/cli/command"
"github.com/docker/cli/cli/context/kubernetes"
"github.com/docker/cli/cli/context/store"
"github.com/spf13/cobra"
"k8s.io/client-go/tools/clientcmd"
)

type exportOptions struct {
kubeconfig bool
contextName string
dest string
}

func newExportCommand(dockerCli command.Cli) *cobra.Command {
opts := &exportOptions{}
cmd := &cobra.Command{
Use: "export <context> [output file]",
Short: "Export a context",
Args: cli.RequiresRangeArgs(1, 2),
RunE: func(cmd *cobra.Command, args []string) error {
opts.contextName = args[0]
if len(args) == 2 {
opts.dest = args[1]
} else {
opts.dest = opts.contextName
if opts.kubeconfig {
opts.dest += ".kubeconfig"
} else {
opts.dest += ".dockercontext"
}
}
return runExport(dockerCli, opts)
},
}

flags := cmd.Flags()
flags.BoolVarP(&opts.kubeconfig, "kubeconfig", "k", false, "export as a kubeconfig file")
return cmd
}
func runExport(dockerCli command.Cli, opts *exportOptions) error {
ctxMeta, err := dockerCli.ContextStore().GetContextMetadata(opts.contextName)
if err != nil {
return err
}
if !opts.kubeconfig {
reader := store.Export(opts.contextName, dockerCli.ContextStore())
defer reader.Close()
f, err := os.OpenFile(opts.dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, reader)
return err
}
kubernetesEndpointMeta := kubernetes.Parse(opts.contextName, ctxMeta)
if kubernetesEndpointMeta == nil {
return fmt.Errorf("context %q has no kubernetes endpoint", opts.contextName)
}
kubernetesEndpoint, err := kubernetesEndpointMeta.WithTLSData(dockerCli.ContextStore())
if err != nil {
return err
}
kubeConfig, err := kubernetesEndpoint.KubernetesConfig()
if err != nil {
return err
}
rawCfg, err := kubeConfig.RawConfig()
if err != nil {
return err
}
return clientcmd.WriteToFile(rawCfg, opts.dest)
}
Loading

0 comments on commit a08fd72

Please sign in to comment.