diff --git a/Makefile b/Makefile index 837fb88..f18224f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -VERSION ?= 0.3.0 +VERSION ?= 0.7.0 COMMIT := $(shell git rev-parse --short HEAD) DATE := $(shell date +%Y%m%d) @@ -83,7 +83,7 @@ $(LOCALBIN): GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint ENVTEST ?= $(LOCALBIN)/setup-envtest -GOLINT_VERSION ?= 1.56.2 +GOLINT_VERSION ?= 1.61.0 .PHONY: envtest envtest: $(ENVTEST) ## Download envtest-setup locally if necessary. diff --git a/cmd/kubectl-k8ssandra/tasks/tasks.go b/cmd/kubectl-k8ssandra/tasks/tasks.go new file mode 100644 index 0000000..2113c60 --- /dev/null +++ b/cmd/kubectl-k8ssandra/tasks/tasks.go @@ -0,0 +1,8 @@ +package tasks + +/* + Task operations. + + tasks list [--cluster || --datacenter] + tasks create [--datacenter || --cluster] +*/ diff --git a/cmd/kubectl-k8ssandra/users/add.go b/cmd/kubectl-k8ssandra/users/add.go index 10994ef..2303b44 100644 --- a/cmd/kubectl-k8ssandra/users/add.go +++ b/cmd/kubectl-k8ssandra/users/add.go @@ -17,12 +17,15 @@ var ( # Add new users to CassandraDatacenter %[1]s add [] + # Add new user example to CassandraDatacenter dc2 with password prompting + %[1]s add --dc dc1 --username example --superuser + # Add new superusers to CassandraDatacenter dc1 from a path /tmp/users.txt %[1]s add --dc dc1 --path /tmp/users.txt --superuser ` errNoDcDc = fmt.Errorf("target CassandraDatacenter is required") errDoubleDefinition = fmt.Errorf("either --path or --username is allowed, not both") - errMissingUsername = fmt.Errorf("if --password is set, --username is required") + errMissingUsername = fmt.Errorf("--username is required") ) type addOptions struct { @@ -73,7 +76,7 @@ func NewAddCmd(streams genericclioptions.IOStreams) *cobra.Command { fl := cmd.Flags() fl.StringVar(&o.secretPath, "path", "", "path to users data") fl.StringVar(&o.datacenter, "dc", "", "target datacenter") - fl.BoolVar(&o.superuser, "superuser", true, "create users as superusers") + fl.BoolVar(&o.superuser, "superuser", true, "create users as superusers") // TODO Set default to false fl.StringVarP(&o.username, "username", "u", "", "username to add") fl.StringVarP(&o.password, "password", "p", "", "password to set for the user") o.configFlags.AddFlags(fl) @@ -155,5 +158,5 @@ func (c *addOptions) Run() error { } } - return users.AddNewUser(ctx, kubeClient, c.datacenter, c.username, c.password, c.superuser) + return users.Add(ctx, kubeClient, c.datacenter, c.username, c.password, c.superuser) } diff --git a/cmd/kubectl-k8ssandra/users/delete.go b/cmd/kubectl-k8ssandra/users/delete.go new file mode 100644 index 0000000..28d7dc2 --- /dev/null +++ b/cmd/kubectl-k8ssandra/users/delete.go @@ -0,0 +1,114 @@ +package users + +import ( + "context" + "fmt" + + "github.com/k8ssandra/k8ssandra-client/pkg/kubernetes" + "github.com/k8ssandra/k8ssandra-client/pkg/users" + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +var ( + userDeleteExample = ` + # Delete users from CassandraDatacenter + %[1]s delete [] + + # Delete user tryme from CassandraDatacenter dc1 + %[1]s delete --dc dc1 --username tryme + ` +) + +type deleteOptions struct { + configFlags *genericclioptions.ConfigFlags + genericclioptions.IOStreams + namespace string + datacenter string + + // For manual entering from CLI + username string +} + +func newDeleteOptions(streams genericclioptions.IOStreams) *deleteOptions { + return &deleteOptions{ + configFlags: genericclioptions.NewConfigFlags(true), + IOStreams: streams, + } +} + +// NewCmd provides a cobra command wrapping newAddOptions +func NewDeleteCmd(streams genericclioptions.IOStreams) *cobra.Command { + o := newDeleteOptions(streams) + + cmd := &cobra.Command{ + Use: "delete [flags]", + Short: "Delete user from CassandraDatacenter installation", + Example: fmt.Sprintf(userDeleteExample, "kubectl k8ssandra users"), + RunE: func(c *cobra.Command, args []string) error { + if err := o.Complete(c, args); err != nil { + return err + } + if err := o.Validate(); err != nil { + return err + } + if err := o.Run(); err != nil { + return err + } + + return nil + }, + } + + fl := cmd.Flags() + fl.StringVar(&o.datacenter, "dc", "", "target datacenter") + fl.StringVarP(&o.username, "username", "u", "", "username to add") + o.configFlags.AddFlags(fl) + return cmd +} + +// Complete parses the arguments and necessary flags to options +func (c *deleteOptions) Complete(cmd *cobra.Command, args []string) error { + var err error + + c.namespace, _, err = c.configFlags.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + if c.username == "" && len(args) > 0 { + c.username = args[0] + } + + return nil +} + +// Validate ensures that all required arguments and flag values are provided +func (c *deleteOptions) Validate() error { + if c.datacenter == "" { + return errNoDcDc + } + + if c.username == "" { + return errMissingUsername + } + + return nil +} + +// Run processes the input, creates a connection to Kubernetes and processes a secret to add the users +func (c *deleteOptions) Run() error { + restConfig, err := c.configFlags.ToRESTConfig() + if err != nil { + return err + } + + kubeClient, err := kubernetes.GetClientInNamespace(restConfig, c.namespace) + if err != nil { + return err + } + + ctx := context.Background() + + return users.Delete(ctx, kubeClient, c.datacenter, c.username) +} diff --git a/cmd/kubectl-k8ssandra/users/list.go b/cmd/kubectl-k8ssandra/users/list.go new file mode 100644 index 0000000..17d9ed4 --- /dev/null +++ b/cmd/kubectl-k8ssandra/users/list.go @@ -0,0 +1,151 @@ +package users + +import ( + "context" + "errors" + "fmt" + + "github.com/charmbracelet/bubbles/table" + "github.com/charmbracelet/lipgloss" + "github.com/k8ssandra/k8ssandra-client/pkg/users" + "github.com/spf13/cobra" + "k8s.io/cli-runtime/pkg/genericclioptions" +) + +var ( + userListExample = ` + # List users of CassandraDatacenter or K8ssandraCluster + %[1]s list [] + + # List users of CassandraDatacenter dc1 + %[1]s list --dc dc1 + + # List users of K8ssandraCluster cluster1 + %[1]s list --cluster cluster1 + ` + + errNoDcOrCluster = errors.New("either cluster or datacenter target is required") +) + +type listOptions struct { + configFlags *genericclioptions.ConfigFlags + genericclioptions.IOStreams + namespace string + datacenter string + cluster string +} + +func newListOptions(streams genericclioptions.IOStreams) *listOptions { + return &listOptions{ + configFlags: genericclioptions.NewConfigFlags(true), + IOStreams: streams, + } +} + +// NewCmd provides a cobra command wrapping newAddOptions +func NewListCmd(streams genericclioptions.IOStreams) *cobra.Command { + o := newListOptions(streams) + + cmd := &cobra.Command{ + Use: "list [flags]", + Short: "List users of CassandraDatacenter or K8ssandraCluster installation", + Example: fmt.Sprintf(userListExample, "kubectl k8ssandra users"), + RunE: func(c *cobra.Command, args []string) error { + if err := o.Complete(c, args); err != nil { + return err + } + if err := o.Validate(); err != nil { + return err + } + if err := o.Run(); err != nil { + return err + } + + return nil + }, + } + + fl := cmd.Flags() + fl.StringVar(&o.datacenter, "dc", "", "target datacenter") + // fl.StringVar(&o.cluster, "cluster", "", "target cluster") + o.configFlags.AddFlags(fl) + return cmd +} + +// Complete parses the arguments and necessary flags to options +func (c *listOptions) Complete(cmd *cobra.Command, args []string) error { + var err error + + c.namespace, _, err = c.configFlags.ToRawKubeConfigLoader().Namespace() + if err != nil { + return err + } + + return nil +} + +// Validate ensures that all required arguments and flag values are provided +func (c *listOptions) Validate() error { + if c.datacenter == "" && c.cluster == "" { + return errNoDcOrCluster + } + + return nil +} + +// Run processes the input, creates a connection to Kubernetes and processes a secret to add the users +func (c *listOptions) Run() error { + restConfig, err := c.configFlags.ToRESTConfig() + if err != nil { + return err + } + + ctx := context.Background() + + users, err := users.List(ctx, restConfig, c.namespace, c.datacenter) + if err != nil { + return err + } + + columns := []table.Column{ + {Title: "Name", Width: 30}, + {Title: "Superuser", Width: 10}, + {Title: "Login", Width: 7}, + {Title: "Options", Width: 10}, + {Title: "Datacenters", Width: 10}, + } + + rows := make([]table.Row, 0, len(users)) + + for _, user := range users { + row := table.Row{ + user.Name, + user.Super, + user.Login, + user.Options, + user.Datacenters, + } + rows = append(rows, row) + } + + t := table.New( + table.WithColumns(columns), + table.WithRows(rows), + ) + + s := table.DefaultStyles() + s.Header = s.Header. + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")). + BorderBottom(true). + Bold(false) + s.Selected = s.Selected. + Foreground(lipgloss.Color("229")). + Background(lipgloss.Color("57")). + Bold(false) + t.SetStyles(s) + + fmt.Print(t.View()) + + return nil +} diff --git a/cmd/kubectl-k8ssandra/users/users.go b/cmd/kubectl-k8ssandra/users/users.go index e673600..badb61d 100644 --- a/cmd/kubectl-k8ssandra/users/users.go +++ b/cmd/kubectl-k8ssandra/users/users.go @@ -5,6 +5,11 @@ import ( "k8s.io/cli-runtime/pkg/genericclioptions" ) +/* + users list + users delete (does mgmt-api have this ability?) +*/ + type ClientOptions struct { configFlags *genericclioptions.ConfigFlags genericclioptions.IOStreams @@ -28,6 +33,8 @@ func NewCmd(streams genericclioptions.IOStreams) *cobra.Command { // Add subcommands cmd.AddCommand(NewAddCmd(streams)) + cmd.AddCommand(NewDeleteCmd(streams)) + cmd.AddCommand(NewListCmd(streams)) o.configFlags.AddFlags(cmd.Flags()) return cmd diff --git a/go.mod b/go.mod index 8d2d1be..5e905ae 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/k8ssandra/k8ssandra-client go 1.22 require ( + github.com/Jeffail/gabs/v2 v2.7.0 github.com/adutra/goalesce v0.0.0-20221124153206-5643f911003d github.com/burmanm/definitions-parser v0.0.0-20230720114634-62c738b72e61 github.com/charmbracelet/bubbles v0.16.1 @@ -10,7 +11,7 @@ require ( github.com/charmbracelet/lipgloss v0.10.0 github.com/charmbracelet/log v0.4.0 github.com/google/uuid v1.3.0 - github.com/k8ssandra/cass-operator v1.20.0 + github.com/k8ssandra/cass-operator v1.22.5-0.20241112082935-4022c5c34c31 github.com/k8ssandra/k8ssandra-operator v1.16.1-0.20240524164338-5e45f078d7f3 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.18.0 @@ -18,6 +19,7 @@ require ( github.com/spf13/cobra v1.8.0 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.9.0 + gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.14.2 k8s.io/api v0.29.2 @@ -26,14 +28,15 @@ require ( k8s.io/cli-runtime v0.29.0 k8s.io/client-go v0.29.2 k8s.io/kubectl v0.29.0 + k8s.io/utils v0.0.0-20230726121419-3b25d923346b sigs.k8s.io/controller-runtime v0.17.5 + sigs.k8s.io/kind v0.22.0 ) require ( github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect github.com/BurntSushi/toml v1.3.2 // indirect - github.com/Jeffail/gabs/v2 v2.7.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect @@ -148,33 +151,32 @@ require ( go.opentelemetry.io/otel/metric v1.19.0 // indirect go.opentelemetry.io/otel/trace v1.19.0 // indirect go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect - golang.org/x/crypto v0.17.0 // indirect + golang.org/x/crypto v0.23.0 // indirect golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect - golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.19.0 // indirect + golang.org/x/mod v0.17.0 // indirect + golang.org/x/net v0.25.0 // indirect golang.org/x/oauth2 v0.12.0 // indirect golang.org/x/sync v0.5.0 // indirect - golang.org/x/sys v0.16.0 // indirect - golang.org/x/term v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect + golang.org/x/sys v0.20.0 // indirect + golang.org/x/term v0.20.0 // indirect + golang.org/x/text v0.15.0 // indirect golang.org/x/time v0.3.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect google.golang.org/grpc v1.58.3 // indirect - google.golang.org/protobuf v1.31.0 // indirect + google.golang.org/protobuf v1.33.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/apiserver v0.29.2 // indirect k8s.io/component-base v0.29.2 // indirect k8s.io/klog/v2 v2.110.1 // indirect k8s.io/kube-openapi v0.0.0-20231010175941-2dd684a91f00 // indirect - k8s.io/utils v0.0.0-20230726121419-3b25d923346b // indirect oras.land/oras-go v1.2.4 // indirect sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect - sigs.k8s.io/kind v0.22.0 // indirect sigs.k8s.io/kustomize/api v0.13.5-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/kustomize/kyaml v0.14.3-0.20230601165947-6ce0bf390ce3 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect sigs.k8s.io/yaml v1.4.0 // indirect ) + +replace github.com/k8ssandra/cass-operator => github.com/burmanm/cass-operator v1.6.1-0.20241114160936-d4b04bd19848 diff --git a/go.sum b/go.sum index de1abee..52bfdc9 100644 --- a/go.sum +++ b/go.sum @@ -54,6 +54,8 @@ github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembj github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o= github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= +github.com/burmanm/cass-operator v1.6.1-0.20241114160936-d4b04bd19848 h1:Lo3o5iW6OC6lUk+p4Qv3BLGbJXM11f13KOXqmsGFJm8= +github.com/burmanm/cass-operator v1.6.1-0.20241114160936-d4b04bd19848/go.mod h1:KtROSvoTwB6eFzcRjOmTUAGkbDHnNtK4ayk4KDllHy4= github.com/burmanm/definitions-parser v0.0.0-20230720114634-62c738b72e61 h1:JgGOw1bU6nCB3wlORn+1stQOGXlyig2djSvCk5rjoNg= github.com/burmanm/definitions-parser v0.0.0-20230720114634-62c738b72e61/go.mod h1:pJnSicezmEGVRxj0BdymVxthDZK6TGMrNFjbwSbrFJQ= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= @@ -165,7 +167,8 @@ github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfC github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= -github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/gobuffalo/logger v1.0.6 h1:nnZNpxYo0zx+Aj9RfMPBm+x9zAU2OayFh/xrAWi34HU= github.com/gobuffalo/logger v1.0.6/go.mod h1:J31TBEHR1QLV2683OXTAItYIg8pv2JMHnF/quuAbMjs= github.com/gobuffalo/packd v1.0.1 h1:U2wXfRr4E9DH8IdsDLlRFwTZTK7hLfq9qT/QHXGVe/0= @@ -212,8 +215,8 @@ github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/pprof v0.0.0-20230502171905-255e3b9b56de h1:6bMcLOeKoNo0+mTOb1ee3McF6CCKGixjLR3EDQY1Jik= -github.com/google/pprof v0.0.0-20230502171905-255e3b9b56de/go.mod h1:79YE0hCXdHag9sBkw2o+N/YnZtTkXi0UT9Nnixa5eYk= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6 h1:k7nVchz72niMH6YLQNvHSdIE7iqsQxK1P41mySCvssg= +github.com/google/pprof v0.0.0-20240424215950-a892ee059fd6/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2 h1:SJ+NtwL6QaZ21U+IrK7d0gGgpjGGvd2kz+FzTHVzdqI= github.com/google/safetext v0.0.0-20220905092116-b49f7bc46da2/go.mod h1:Tv1PlzqC9t8wNnpPdctvtSUOPUUg4SHeE6vR1Ir2hmg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= @@ -260,8 +263,6 @@ github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/k8ssandra/cass-operator v1.20.0 h1:gbLAJL7bF0+PCwjeFULpBnqbXAxYfs5wOIKKaKARC+s= -github.com/k8ssandra/cass-operator v1.20.0/go.mod h1:zXhK7q5+ymAYCuOvhwpLyNnjJAnQDGqwFc/j8nQQN6M= github.com/k8ssandra/k8ssandra-operator v1.16.1-0.20240524164338-5e45f078d7f3 h1:dd84RK9QVqPqnbcJ3VLIQH695UDoB+BikKCRTMlfqHg= github.com/k8ssandra/k8ssandra-operator v1.16.1-0.20240524164338-5e45f078d7f3/go.mod h1:X8HrVfDKfHy/FK3TpJWa7lwuFz4mZe5VqvIZDb0Gkyg= github.com/karrick/godirwalk v1.16.1 h1:DynhcF+bztK8gooS0+NDJFrdNZjJ3gzVzC545UNA9iw= @@ -362,10 +363,10 @@ github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRW github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/onsi/ginkgo/v2 v2.14.0 h1:vSmGj2Z5YPb9JwCWT6z6ihcUvDhuXLc3sJiqd3jMKAY= -github.com/onsi/ginkgo/v2 v2.14.0/go.mod h1:JkUdW7JkN0V6rFvsHcJ478egV3XH9NxpD27Hal/PhZw= -github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= -github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= +github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= +github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= +github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= +github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc5 h1:Ygwkfw9bpDvs+c9E34SdgGOj41dX/cbdlwvlWt0pnFI= @@ -487,8 +488,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI= golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo= @@ -498,8 +499,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -514,8 +515,8 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c= -golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= @@ -548,21 +549,21 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= -golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= +golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -574,8 +575,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA= -golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0= +golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw= +golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -606,8 +607,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= -google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= +google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/pkg/cassdcutil/fetcher.go b/pkg/cassdcutil/fetcher.go index 683be92..647369e 100644 --- a/pkg/cassdcutil/fetcher.go +++ b/pkg/cassdcutil/fetcher.go @@ -64,3 +64,27 @@ func (c *CassManager) CassandraDatacenterPods(ctx context.Context, cassdc *cassd err := c.client.List(ctx, podList, client.InNamespace(cassdc.Namespace), client.MatchingLabels(map[string]string{cassdcapi.DatacenterLabel: cassdc.Name})) return podList, err } + +func (c *CassManager) FirstRunningDatacenterPod(ctx context.Context, cassdc *cassdcapi.CassandraDatacenter) (*corev1.Pod, error) { + podList, err := c.CassandraDatacenterPods(ctx, cassdc) + if err != nil { + return nil, err + } + + for _, pod := range podList.Items { + status := pod.Status + if status.Phase == corev1.PodRunning { + statuses := status.ContainerStatuses + for _, status := range statuses { + if status.Name != "cassandra" { + continue + } + if status.Ready { + return &pod, nil + } + } + } + } + + return nil, fmt.Errorf("no running pods found for datacenter %s", cassdc.Name) +} diff --git a/pkg/kubernetes/portforward.go b/pkg/kubernetes/portforward.go new file mode 100644 index 0000000..8939aa1 --- /dev/null +++ b/pkg/kubernetes/portforward.go @@ -0,0 +1,75 @@ +package kubernetes + +import ( + "bytes" + "fmt" + "net/http" + "time" + + "github.com/k8ssandra/cass-operator/pkg/httphelper" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/portforward" + "k8s.io/client-go/transport/spdy" +) + +func PortForwardMgmtPort(config *rest.Config, pod *corev1.Pod) (string, error) { + _, targetPort, err := httphelper.BuildPodHostFromPod(pod) + if err != nil { + return "", err + } + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return "", err + } + + restClient := clientset.CoreV1().RESTClient() + req := restClient.Post(). + Resource("pods"). + Namespace(pod.Namespace). + Name(pod.Name). + SubResource("portforward") + + transport, upgrader, err := spdy.RoundTripperFor(config) + if err != nil { + return "", err + } + + stopChan := make(chan struct{}, 1) + readyChan := make(chan struct{}) + out := new(bytes.Buffer) + errOut := new(bytes.Buffer) + + // Use a random local port and connect to the mgmt-api port + ports := []string{fmt.Sprintf("0:%d", targetPort)} + dialer := spdy.NewDialer(upgrader, &http.Client{Transport: transport}, "POST", req.URL()) + + fw, err := portforward.New(dialer, ports, stopChan, readyChan, out, errOut) + if err != nil { + return "", err + } + + go func() { + if err := fw.ForwardPorts(); err != nil { + fmt.Printf("ForwardPorts error: %v\n", err) + } + }() + + select { + case <-readyChan: + localPort := extractLocalPort(out.String()) + return fmt.Sprintf("localhost:%s", localPort), nil + case <-time.After(10 * time.Second): + return "", fmt.Errorf("timeout waiting for port forwarding to be ready") + } +} + +func extractLocalPort(output string) string { + // Example output: "Forwarding from 127.0.0.1:random_port -> 8080" + // Extract the random_port from the output + var localPort string + fmt.Sscanf(output, "Forwarding from 127.0.0.1:%s -> 8080", &localPort) + return localPort +} diff --git a/pkg/mgmtapi/client.go b/pkg/mgmtapi/client.go index 38b0227..0bda30e 100644 --- a/pkg/mgmtapi/client.go +++ b/pkg/mgmtapi/client.go @@ -2,13 +2,20 @@ package mgmtapi import ( "context" + "net" + "net/http" "github.com/k8ssandra/cass-operator/pkg/httphelper" "github.com/k8ssandra/k8ssandra-client/pkg/cassdcutil" + "github.com/k8ssandra/k8ssandra-client/pkg/kubernetes" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" ) +// TODO We need to detect if we're running inside Kubernetes or not and wrap the client if necessary + // NewManagementClient returns a new instance for management-api go-client func NewManagementClient(ctx context.Context, client client.Client, namespace, datacenter string) (httphelper.NodeMgmtClient, error) { manager := cassdcutil.NewManager(client) @@ -17,5 +24,38 @@ func NewManagementClient(ctx context.Context, client client.Client, namespace, d return httphelper.NodeMgmtClient{}, err } - return httphelper.NewMgmtClient(ctx, client, dc) + return httphelper.NewMgmtClient(ctx, client, dc, nil) +} + +func NewForwardedManagementClient(ctx context.Context, restConfig *rest.Config, namespace, datacenter string) (httphelper.NodeMgmtClient, *corev1.Pod, error) { + client, err := kubernetes.GetClientInNamespace(restConfig, namespace) + if err != nil { + return httphelper.NodeMgmtClient{}, nil, err + } + + manager := cassdcutil.NewManager(client) + + dc, err := manager.CassandraDatacenter(ctx, datacenter, namespace) + if err != nil { + return httphelper.NodeMgmtClient{}, nil, err + } + + pod, err := manager.FirstRunningDatacenterPod(ctx, dc) + if err != nil { + return httphelper.NodeMgmtClient{}, pod, err + } + + forwardAddr, err := kubernetes.PortForwardMgmtPort(restConfig, pod) + if err != nil { + return httphelper.NodeMgmtClient{}, pod, err + } + + customTransport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return net.Dial(network, forwardAddr) + }, + } + + mgmtClient, err := httphelper.NewMgmtClient(ctx, client, dc, customTransport) + return mgmtClient, pod, err } diff --git a/pkg/users/users.go b/pkg/users/users.go index 279cebb..44b07f5 100644 --- a/pkg/users/users.go +++ b/pkg/users/users.go @@ -3,10 +3,12 @@ package users import ( "context" + "github.com/k8ssandra/cass-operator/pkg/httphelper" "github.com/k8ssandra/k8ssandra-client/pkg/cassdcutil" "github.com/k8ssandra/k8ssandra-client/pkg/kubernetes" "github.com/k8ssandra/k8ssandra-client/pkg/mgmtapi" "github.com/k8ssandra/k8ssandra-client/pkg/secrets" + "k8s.io/client-go/rest" corev1 "k8s.io/api/core/v1" ) @@ -49,10 +51,12 @@ func targetPod(ctx context.Context, c kubernetes.NamespacedClient, datacenter st return nil, err } + // TODO Check that there's a pod up + return &podList.Items[0], nil } -func AddNewUser(ctx context.Context, c kubernetes.NamespacedClient, datacenter string, username string, password string, superuser bool) error { +func Add(ctx context.Context, c kubernetes.NamespacedClient, datacenter string, username string, password string, superuser bool) error { mgmtClient, err := mgmtapi.NewManagementClient(ctx, c, c.Namespace, datacenter) if err != nil { return err @@ -69,3 +73,36 @@ func AddNewUser(ctx context.Context, c kubernetes.NamespacedClient, datacenter s return nil } + +func Delete(ctx context.Context, c kubernetes.NamespacedClient, datacenter string, username string) error { + mgmtClient, err := mgmtapi.NewManagementClient(ctx, c, c.Namespace, datacenter) + if err != nil { + return err + } + + pod, err := targetPod(ctx, c, datacenter) + if err != nil { + return err + } + + if err := mgmtClient.CallDropRoleEndpoint(pod, username); err != nil { + return err + } + + return nil +} + +func List(ctx context.Context, restConfig *rest.Config, namespace, datacenter string) ([]httphelper.User, error) { + mgmtClient, pod, err := mgmtapi.NewForwardedManagementClient(ctx, restConfig, namespace, datacenter) + if err != nil { + return nil, err + } + + users, err := mgmtClient.CallListRolesEndpoint(pod) + if err != nil { + return nil, err + } + + return users, err + +}