github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli/command/context/create.go (about) 1 // FIXME(thaJeztah): remove once we are a module; the go:build directive prevents go from downgrading language version to go1.16: 2 //go:build go1.19 3 4 package context 5 6 import ( 7 "bytes" 8 "fmt" 9 10 "github.com/docker/docker/errdefs" 11 "github.com/khulnasoft/cli/cli" 12 "github.com/khulnasoft/cli/cli/command" 13 "github.com/khulnasoft/cli/cli/command/completion" 14 "github.com/khulnasoft/cli/cli/command/formatter/tabwriter" 15 "github.com/khulnasoft/cli/cli/context/docker" 16 "github.com/khulnasoft/cli/cli/context/store" 17 "github.com/pkg/errors" 18 "github.com/spf13/cobra" 19 ) 20 21 // CreateOptions are the options used for creating a context 22 type CreateOptions struct { 23 Name string 24 Description string 25 Docker map[string]string 26 From string 27 } 28 29 func longCreateDescription() string { 30 buf := bytes.NewBuffer(nil) 31 buf.WriteString("Create a context\n\nDocker endpoint config:\n\n") 32 tw := tabwriter.NewWriter(buf, 20, 1, 3, ' ', 0) 33 fmt.Fprintln(tw, "NAME\tDESCRIPTION") 34 for _, d := range dockerConfigKeysDescriptions { 35 fmt.Fprintf(tw, "%s\t%s\n", d.name, d.description) 36 } 37 tw.Flush() 38 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") 39 return buf.String() 40 } 41 42 func newCreateCommand(dockerCLI command.Cli) *cobra.Command { 43 opts := &CreateOptions{} 44 cmd := &cobra.Command{ 45 Use: "create [OPTIONS] CONTEXT", 46 Short: "Create a context", 47 Args: cli.ExactArgs(1), 48 RunE: func(cmd *cobra.Command, args []string) error { 49 opts.Name = args[0] 50 return RunCreate(dockerCLI, opts) 51 }, 52 Long: longCreateDescription(), 53 ValidArgsFunction: completion.NoComplete, 54 } 55 flags := cmd.Flags() 56 flags.StringVar(&opts.Description, "description", "", "Description of the context") 57 flags.StringToStringVar(&opts.Docker, "docker", nil, "set the docker endpoint") 58 flags.StringVar(&opts.From, "from", "", "create context from a named context") 59 return cmd 60 } 61 62 // RunCreate creates a Docker context 63 func RunCreate(dockerCLI command.Cli, o *CreateOptions) error { 64 s := dockerCLI.ContextStore() 65 err := checkContextNameForCreation(s, o.Name) 66 if err != nil { 67 return err 68 } 69 switch { 70 case o.From == "" && o.Docker == nil: 71 err = createFromExistingContext(s, dockerCLI.CurrentContext(), o) 72 case o.From != "": 73 err = createFromExistingContext(s, o.From, o) 74 default: 75 err = createNewContext(s, o) 76 } 77 if err == nil { 78 fmt.Fprintln(dockerCLI.Out(), o.Name) 79 fmt.Fprintf(dockerCLI.Err(), "Successfully created context %q\n", o.Name) 80 } 81 return err 82 } 83 84 func createNewContext(contextStore store.ReaderWriter, o *CreateOptions) error { 85 if o.Docker == nil { 86 return errors.New("docker endpoint configuration is required") 87 } 88 dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(contextStore, o.Docker) 89 if err != nil { 90 return errors.Wrap(err, "unable to create docker endpoint config") 91 } 92 contextMetadata := store.Metadata{ 93 Endpoints: map[string]any{ 94 docker.DockerEndpoint: dockerEP, 95 }, 96 Metadata: command.DockerContext{ 97 Description: o.Description, 98 }, 99 Name: o.Name, 100 } 101 contextTLSData := store.ContextTLSData{} 102 if dockerTLS != nil { 103 contextTLSData.Endpoints = map[string]store.EndpointTLSData{ 104 docker.DockerEndpoint: *dockerTLS, 105 } 106 } 107 if err := validateEndpoints(contextMetadata); err != nil { 108 return err 109 } 110 if err := contextStore.CreateOrUpdate(contextMetadata); err != nil { 111 return err 112 } 113 return contextStore.ResetTLSMaterial(o.Name, &contextTLSData) 114 } 115 116 func checkContextNameForCreation(s store.Reader, name string) error { 117 if err := store.ValidateContextName(name); err != nil { 118 return err 119 } 120 if _, err := s.GetMetadata(name); !errdefs.IsNotFound(err) { 121 if err != nil { 122 return errors.Wrap(err, "error while getting existing contexts") 123 } 124 return errors.Errorf("context %q already exists", name) 125 } 126 return nil 127 } 128 129 func createFromExistingContext(s store.ReaderWriter, fromContextName string, o *CreateOptions) error { 130 if len(o.Docker) != 0 { 131 return errors.New("cannot use --docker flag when --from is set") 132 } 133 reader := store.Export(fromContextName, &descriptionDecorator{ 134 Reader: s, 135 description: o.Description, 136 }) 137 defer reader.Close() 138 return store.Import(o.Name, s, reader) 139 } 140 141 type descriptionDecorator struct { 142 store.Reader 143 description string 144 } 145 146 func (d *descriptionDecorator) GetMetadata(name string) (store.Metadata, error) { 147 c, err := d.Reader.GetMetadata(name) 148 if err != nil { 149 return c, err 150 } 151 typedContext, err := command.GetDockerContext(c) 152 if err != nil { 153 return c, err 154 } 155 if d.description != "" { 156 typedContext.Description = d.description 157 } 158 c.Metadata = typedContext 159 return c, nil 160 }