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  }