package client import ( "errors" "fmt" "net/http" "os" dCliCommand "github.com/docker/cli/cli/command" dCliConfig "github.com/docker/cli/cli/config" dContext "github.com/docker/cli/cli/context" "github.com/docker/cli/cli/context/docker" dCliContextStore "github.com/docker/cli/cli/context/store" dClient "github.com/docker/docker/client" "github.com/moby/term" "github.com/sirupsen/logrus" ) func NewClientWithContext(contextName string) (*dClient.Client, error) { context, err := GetContext(contextName) if err != nil { return nil, err } ctxEndpoint, err := GetContextEndpoint(context) if err != nil { return nil, err } helper := newConnectionHelper(ctxEndpoint) httpClient := &http.Client{ // No tls // No proxy Transport: &http.Transport{ DialContext: helper.Dialer, }, } var clientOpts []dClient.Opt clientOpts = append(clientOpts, dClient.WithHTTPClient(httpClient), dClient.WithHost(helper.Host), dClient.WithDialContext(helper.Dialer), ) // FIXME: Maybe don't have this variable here and load it beforehand version := os.Getenv("DOCKER_API_VERSION") if version != "" { clientOpts = append(clientOpts, dClient.WithVersion(version)) } else { clientOpts = append(clientOpts, dClient.WithAPIVersionNegotiation()) } cl, err := dClient.NewClientWithOpts(clientOpts...) if err != nil { logrus.Fatalf("unable to create Docker client: %s", err) } return cl, nil } func CreateContext(contextName string, user string, port string) error { host := contextName if user != "" { host = fmt.Sprintf("%s@%s", user, host) } if port != "" { host = fmt.Sprintf("%s:%s", host, port) } host = fmt.Sprintf("ssh://%s", host) if err := createNewContext(contextName, host); err != nil { return err } return nil } func createNewContext(name string, host string) error { s := NewDefaultDockerContextStore() contextMetadata := newContextMetadata(name) contextTLSData := dCliContextStore.ContextTLSData{ Endpoints: make(map[string]dCliContextStore.EndpointTLSData), } dockerEP, dockerTLS, err := getDockerEndpointMetadataAndTLS(host) if err != nil { return err } contextMetadata.Endpoints[docker.DockerEndpoint] = dockerEP if dockerTLS != nil { contextTLSData.Endpoints[docker.DockerEndpoint] = *dockerTLS } if err := s.CreateOrUpdate(contextMetadata); err != nil { return err } if err := s.ResetTLSMaterial(name, &contextTLSData); err != nil { return err } return nil } func getDockerEndpoint(host string) (docker.Endpoint, error) { skipTLSVerify := false ep := docker.Endpoint{ EndpointMeta: docker.EndpointMeta{ Host: host, SkipTLSVerify: skipTLSVerify, }, } // try to resolve a docker client, validating the configuration opts, err := ep.ClientOpts() if err != nil { return docker.Endpoint{}, err } if _, err := dClient.NewClientWithOpts(opts...); err != nil { return docker.Endpoint{}, err } return ep, nil } func newContextMetadata(name string) dCliContextStore.Metadata { return dCliContextStore.Metadata{ Endpoints: make(map[string]interface{}), Name: name, } } func getDockerEndpointMetadataAndTLS(host string) (docker.EndpointMeta, *dCliContextStore.EndpointTLSData, error) { ep, err := getDockerEndpoint(host) if err != nil { return docker.EndpointMeta{}, nil, err } return ep.EndpointMeta, ep.TLSData.ToStoreTLSData(), nil } func GetContext(contextName string) (dCliContextStore.Metadata, error) { ctx, err := NewDefaultDockerContextStore().GetMetadata(contextName) if err != nil { return dCliContextStore.Metadata{}, err } return ctx, nil } func GetContextEndpoint(ctx dCliContextStore.Metadata) (string, error) { // safe to use docker key hardcoded since abra doesn't use k8s... yet... endpointmeta, ok := ctx.Endpoints["docker"].(dContext.EndpointMetaBase) if !ok { err := errors.New("context lacks Docker endpoint") return "", err } return endpointmeta.Host, nil } func NewDefaultDockerContextStore() *dCliCommand.ContextStoreWithDefault { // Grabbing the stderr from Docker commands // Much easier to fit this into the code we are using to replicate docker cli commands _, _, stderr := term.StdStreams() // TODO: Look into custom docker configs in case users want that dockerConfig := dCliConfig.LoadDefaultConfigFile(stderr) contextDir := dCliConfig.ContextStoreDir() storeConfig := dCliCommand.DefaultContextStoreConfig() store := newContextStore(contextDir, storeConfig) dockerContextStore := &dCliCommand.ContextStoreWithDefault{ Store: store, Resolver: func() (*dCliCommand.DefaultContext, error) { // nil for the Opts because it works without it and its a cli thing return dCliCommand.ResolveDefaultContext(nil, dockerConfig, storeConfig, stderr) }, } return dockerContextStore } func newContextStore(dir string, config dCliContextStore.Config) dCliContextStore.Store { return dCliContextStore.New(dir, config) }