github.com/justincormack/cli@v0.0.0-20201215022714-831ebeae9675/cli/command/context/create.go (about)

     1  package context
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"text/tabwriter"
     7  
     8  	"github.com/docker/cli/cli"
     9  	"github.com/docker/cli/cli/command"
    10  	"github.com/docker/cli/cli/context/docker"
    11  	"github.com/docker/cli/cli/context/kubernetes"
    12  	"github.com/docker/cli/cli/context/store"
    13  	"github.com/pkg/errors"
    14  	"github.com/spf13/cobra"
    15  )
    16  
    17  // CreateOptions are the options used for creating a context
    18  type CreateOptions struct {
    19  	Name                     string
    20  	Description              string
    21  	DefaultStackOrchestrator string
    22  	Docker                   map[string]string
    23  	Kubernetes               map[string]string
    24  	From                     string
    25  }
    26  
    27  func longCreateDescription() string {
    28  	buf := bytes.NewBuffer(nil)
    29  	buf.WriteString("Create a context\n\nDocker endpoint config:\n\n")
    30  	tw := tabwriter.NewWriter(buf, 20, 1, 3, ' ', 0)
    31  	fmt.Fprintln(tw, "NAME\tDESCRIPTION")
    32  	for _, d := range dockerConfigKeysDescriptions {
    33  		fmt.Fprintf(tw, "%s\t%s\n", d.name, d.description)
    34  	}
    35  	tw.Flush()
    36  	buf.WriteString("\nKubernetes endpoint config:\n\n")
    37  	tw = tabwriter.NewWriter(buf, 20, 1, 3, ' ', 0)
    38  	fmt.Fprintln(tw, "NAME\tDESCRIPTION")
    39  	for _, d := range kubernetesConfigKeysDescriptions {
    40  		fmt.Fprintf(tw, "%s\t%s\n", d.name, d.description)
    41  	}
    42  	tw.Flush()
    43  	buf.WriteString("\nExample:\n\n$ docker context create my-context --description \"some description\" --docker \"host=tcp://myserver:2376,ca=~/ca-file,cert=~/cert-file,key=~/key-file\"\n")
    44  	return buf.String()
    45  }
    46  
    47  func newCreateCommand(dockerCli command.Cli) *cobra.Command {
    48  	opts := &CreateOptions{}
    49  	cmd := &cobra.Command{
    50  		Use:   "create [OPTIONS] CONTEXT",
    51  		Short: "Create a context",
    52  		Args:  cli.ExactArgs(1),
    53  		RunE: func(cmd *cobra.Command, args []string) error {
    54  			opts.Name = args[0]
    55  			return RunCreate(dockerCli, opts)
    56  		},
    57  		Long: longCreateDescription(),
    58  	}
    59  	flags := cmd.Flags()
    60  	flags.StringVar(&opts.Description, "description", "", "Description of the context")
    61  	flags.StringVar(
    62  		&opts.DefaultStackOrchestrator,
    63  		"default-stack-orchestrator", "",
    64  		"Default orchestrator for stack operations to use with this context (swarm|kubernetes|all)")
    65  	flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint")
    66  	flags.StringToStringVar(&opts.Kubernetes, "kubernetes", nil, "set the kubernetes endpoint")
    67  	flags.StringVar(&opts.From, "from", "", "create context from a named context")
    68  	return cmd
    69  }
    70  
    71  // RunCreate creates a Docker context
    72  func RunCreate(cli command.Cli, o *CreateOptions) error {
    73  	s := cli.ContextStore()
    74  	if err := checkContextNameForCreation(s, o.Name); err != nil {
    75  		return err
    76  	}
    77  	stackOrchestrator, err := command.NormalizeOrchestrator(o.DefaultStackOrchestrator)
    78  	if err != nil {
    79  		return errors.Wrap(err, "unable to parse default-stack-orchestrator")
    80  	}
    81  	switch {
    82  	case o.From == "" && o.Docker == nil && o.Kubernetes == nil:
    83  		err = createFromExistingContext(s, cli.CurrentContext(), stackOrchestrator, o)
    84  	case o.From != "":
    85  		err = createFromExistingContext(s, o.From, stackOrchestrator, o)
    86  	default:
    87  		err = createNewContext(o, stackOrchestrator, cli, s)
    88  	}
    89  	if err == nil {
    90  		fmt.Fprintln(cli.Out(), o.Name)
    91  		fmt.Fprintf(cli.Err(), "Successfully created context %q\n", o.Name)
    92  	}
    93  	return err
    94  }
    95  
    96  func createNewContext(o *CreateOptions, stackOrchestrator command.Orchestrator, cli command.Cli, s store.Writer) error {
    97  	if o.Docker == nil {
    98  		return errors.New("docker endpoint configuration is required")
    99  	}
   100  	contextMetadata := newContextMetadata(stackOrchestrator, o)
   101  	contextTLSData := store.ContextTLSData{
   102  		Endpoints: make(map[string]store.EndpointTLSData),
   103  	}
   104  	dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(cli, o.Docker)
   105  	if err != nil {
   106  		return errors.Wrap(err, "unable to create docker endpoint config")
   107  	}
   108  	contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP
   109  	if dockerTLS != nil {
   110  		contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerTLS
   111  	}
   112  	if o.Kubernetes != nil {
   113  		kubernetesEP, kubernetesTLS, err := getKubernetesEndpointMetadataAndTLS(cli, o.Kubernetes)
   114  		if err != nil {
   115  			return errors.Wrap(err, "unable to create kubernetes endpoint config")
   116  		}
   117  		if kubernetesEP == nil && stackOrchestrator.HasKubernetes() {
   118  			return errors.Errorf("cannot specify orchestrator %q without configuring a Kubernetes endpoint", stackOrchestrator)
   119  		}
   120  		if kubernetesEP != nil {
   121  			contextMetadata.Endpoints[kubernetes.KubernetesEndpoint] = kubernetesEP
   122  		}
   123  		if kubernetesTLS != nil {
   124  			contextTLSData.Endpoints[kubernetes.KubernetesEndpoint] = *kubernetesTLS
   125  		}
   126  	}
   127  	if err := validateEndpointsAndOrchestrator(contextMetadata); err != nil {
   128  		return err
   129  	}
   130  	if err := s.CreateOrUpdate(contextMetadata); err != nil {
   131  		return err
   132  	}
   133  	if err := s.ResetTLSMaterial(o.Name, &contextTLSData); err != nil {
   134  		return err
   135  	}
   136  	return nil
   137  }
   138  
   139  func checkContextNameForCreation(s store.Reader, name string) error {
   140  	if err := validateContextName(name); err != nil {
   141  		return err
   142  	}
   143  	if _, err := s.GetMetadata(name); !store.IsErrContextDoesNotExist(err) {
   144  		if err != nil {
   145  			return errors.Wrap(err, "error while getting existing contexts")
   146  		}
   147  		return errors.Errorf("context %q already exists", name)
   148  	}
   149  	return nil
   150  }
   151  
   152  func createFromExistingContext(s store.ReaderWriter, fromContextName string, stackOrchestrator command.Orchestrator, o *CreateOptions) error {
   153  	if len(o.Docker) != 0 || len(o.Kubernetes) != 0 {
   154  		return errors.New("cannot use --docker or --kubernetes flags when --from is set")
   155  	}
   156  	reader := store.Export(fromContextName, &descriptionAndOrchestratorStoreDecorator{
   157  		Reader:       s,
   158  		description:  o.Description,
   159  		orchestrator: stackOrchestrator,
   160  	})
   161  	defer reader.Close()
   162  	return store.Import(o.Name, s, reader)
   163  }
   164  
   165  type descriptionAndOrchestratorStoreDecorator struct {
   166  	store.Reader
   167  	description  string
   168  	orchestrator command.Orchestrator
   169  }
   170  
   171  func (d *descriptionAndOrchestratorStoreDecorator) GetMetadata(name string) (store.Metadata, error) {
   172  	c, err := d.Reader.GetMetadata(name)
   173  	if err != nil {
   174  		return c, err
   175  	}
   176  	typedContext, err := command.GetDockerContext(c)
   177  	if err != nil {
   178  		return c, err
   179  	}
   180  	if d.description != "" {
   181  		typedContext.Description = d.description
   182  	}
   183  	if d.orchestrator != command.Orchestrator("") {
   184  		typedContext.StackOrchestrator = d.orchestrator
   185  	}
   186  	c.Metadata = typedContext
   187  	return c, nil
   188  }
   189  
   190  func newContextMetadata(stackOrchestrator command.Orchestrator, o *CreateOptions) store.Metadata {
   191  	return store.Metadata{
   192  		Endpoints: make(map[string]interface{}),
   193  		Metadata: command.DockerContext{
   194  			Description:       o.Description,
   195  			StackOrchestrator: stackOrchestrator,
   196  		},
   197  		Name: o.Name,
   198  	}
   199  }