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 }