diff --git a/cmd/garmctl/main.go b/cmd/garmctl/main.go new file mode 100644 index 00000000..6a84703c --- /dev/null +++ b/cmd/garmctl/main.go @@ -0,0 +1,9 @@ +package main + +import "github.com/mercedes-benz/garm-operator/pkg/command" + +func main() { + if err := command.RootCommand.Execute(); err != nil { + panic(err) + } +} diff --git a/go.mod b/go.mod index 262b4e93..5e99f4bf 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( github.com/cloudbase/garm-provider-common v0.1.0 github.com/go-openapi/runtime v0.26.0 github.com/go-playground/validator/v10 v10.16.0 + github.com/jedib0t/go-pretty/v6 v6.4.6 github.com/knadh/koanf/parsers/yaml v0.1.0 github.com/knadh/koanf/providers/env v0.1.0 github.com/knadh/koanf/providers/file v0.1.0 @@ -15,6 +16,7 @@ require ( github.com/knadh/koanf/v2 v2.0.1 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.16.0 + github.com/spf13/cobra v1.6.1 github.com/spf13/pflag v1.0.5 go.uber.org/mock v0.2.0 gopkg.in/yaml.v2 v2.4.0 @@ -61,11 +63,13 @@ require ( github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.3.0 // indirect github.com/imdario/mergo v0.3.6 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/leodido/go-urn v1.2.4 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-runewidth v0.0.14 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect @@ -78,6 +82,7 @@ require ( github.com/prometheus/client_model v0.4.0 // indirect github.com/prometheus/common v0.42.0 // indirect github.com/prometheus/procfs v0.10.1 // indirect + github.com/rivo/uniseg v0.4.4 // indirect go.mongodb.org/mongo-driver v1.11.3 // indirect go.opentelemetry.io/otel v1.14.0 // indirect go.opentelemetry.io/otel/trace v1.14.0 // indirect diff --git a/go.sum b/go.sum index 57acecd6..fcb45716 100644 --- a/go.sum +++ b/go.sum @@ -21,6 +21,7 @@ github.com/cloudbase/garm-provider-common v0.1.0/go.mod h1:igxJRT3OlykERYc6ssdRQ github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I= github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -164,6 +165,11 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28= github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jedib0t/go-pretty/v6 v6.4.6 h1:v6aG9h6Uby3IusSSEjHaZNXpHFhzqMmjXcPq1Rjl9Jw= +github.com/jedib0t/go-pretty/v6 v6.4.6/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -207,6 +213,9 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU= +github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= @@ -239,6 +248,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -251,15 +261,21 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= +github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= +github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= @@ -274,6 +290,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= @@ -367,6 +384,7 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/main b/main new file mode 100755 index 00000000..b912eeb3 Binary files /dev/null and b/main differ diff --git a/pkg/command/describe.go b/pkg/command/describe.go new file mode 100644 index 00000000..f33535c4 --- /dev/null +++ b/pkg/command/describe.go @@ -0,0 +1,14 @@ +package command + +import "github.com/spf13/cobra" + +var describeCmd = &cobra.Command{ + Use: "pools", + Aliases: []string{"pool", "p"}, + Short: "analyze and prepare captured traces", + Long: "analyze and prepare previous captured traces for further processing", +} + +func init() { + RootCommand.AddCommand(describeCmd) +} diff --git a/pkg/command/kubeclient.go b/pkg/command/kubeclient.go new file mode 100644 index 00000000..fe0dcb2f --- /dev/null +++ b/pkg/command/kubeclient.go @@ -0,0 +1,44 @@ +package command + +import ( + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + + "k8s.io/client-go/kubernetes/scheme" + + garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" +) + +func generateKubeClient() (*dynamic.DynamicClient, error) { + + config, err := clientcmd.BuildConfigFromFlags("", opt.kubeConfig) + if err != nil { + return nil, err + } + + return dynamic.NewForConfig(config) + // cliSet, err := dynamic.NewForConfig(config) + //if err != nil { + // return nil, err + //} + // + //// add the garm-operator CRD scheme + //garmoperatorv1alpha1.AddToScheme(scheme.Scheme) + // + //return kubernetes.NewForConfig(config) +} + +func newRestClient() (*rest.RESTClient, error) { + config, err := clientcmd.BuildConfigFromFlags("", opt.kubeConfig) + if err != nil { + return nil, err + } + + config.APIPath = "/apis" + config.ContentConfig.GroupVersion = &garmoperatorv1alpha1.GroupVersion + config.NegotiatedSerializer = scheme.Codecs.WithoutConversion() + config.UserAgent = rest.DefaultKubernetesUserAgent() + + return rest.RESTClientFor(config) +} diff --git a/pkg/command/overview.go b/pkg/command/overview.go new file mode 100644 index 00000000..5586d61e --- /dev/null +++ b/pkg/command/overview.go @@ -0,0 +1,128 @@ +package command + +import ( + "context" + "os" + "slices" + + "github.com/spf13/cobra" + + "github.com/jedib0t/go-pretty/v6/table" + + garmoperatorv1alpha1 "github.com/mercedes-benz/garm-operator/api/v1alpha1" +) + +var overviewCmd = &cobra.Command{ + Use: "overview", + Aliases: []string{"o"}, + Short: "analyze and prepare captured traces", + Long: "analyze and prepare previous captured traces for further processing", + RunE: generateOverview, +} + +func init() { + describeCmd.AddCommand(overviewCmd) + + describeCmd.PersistentFlags().StringSliceVar(&opt.sortBy, "sort-by", []string{"region", "flavor"}, "sorted") + describeCmd.PersistentFlags().BoolVar(&opt.markdown, "markdown", false, "output as markdown") +} + +type providerSummary struct { + name string + flavors []flavor +} + +type flavor struct { + name string + maxRunner uint + minRunner uint +} + +func generateOverview(cmd *cobra.Command, args []string) error { + + kubeClient, err := newRestClient() + if err != nil { + return err + } + + poolList := &garmoperatorv1alpha1.PoolList{} + pools := kubeClient.Get(). + Namespace("garm-infra-stage-prod"). + Resource("pools"). + Do(context.Background()) + + pools.Into(poolList) + + summary := []providerSummary{} + + for _, pool := range poolList.Items { + + providerName := pool.Spec.ProviderName + maxRunners := pool.Spec.MaxRunners + minIdleRunners := pool.Spec.MinIdleRunners + + providerIndex := slices.IndexFunc(summary, func(provider providerSummary) bool { return provider.name == providerName }) + if providerIndex == -1 { + summary = append(summary, providerSummary{ + name: providerName, + flavors: []flavor{ + { + name: pool.Spec.Flavor, + maxRunner: pool.Spec.MaxRunners, + minRunner: pool.Spec.MinIdleRunners, + }, + }, + }) + } else { + + flavorIndex := slices.IndexFunc(summary[providerIndex].flavors, func(flavor flavor) bool { return flavor.name == pool.Spec.Flavor }) + if flavorIndex == -1 { + summary[providerIndex].flavors = append(summary[providerIndex].flavors, flavor{ + name: pool.Spec.Flavor, + maxRunner: pool.Spec.MaxRunners, + minRunner: pool.Spec.MinIdleRunners, + }) + } else { + + summary[providerIndex].flavors[flavorIndex].maxRunner += maxRunners // calculate the runners on top + summary[providerIndex].flavors[flavorIndex].minRunner += minIdleRunners + } + } + + } + + printOverview(summary) + + return nil +} + +func printOverview(summary []providerSummary) { + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"region", "flavor", "max runners", "Min Idle Runners"}) + + for _, s := range summary { + for _, f := range s.flavors { + t.AppendRow([]interface{}{s.name, f.name, f.maxRunner, f.minRunner}) + } + } + + if len(opt.sortBy) > 0 { + for _, sortOption := range opt.sortBy { + t.SortBy([]table.SortBy{ + {Name: sortOption, Mode: table.Asc}, + }) + } + + } + + t.SetStyle(table.StyleColoredBlackOnRedWhite) + + if opt.markdown { + t.RenderMarkdown() + } else { + t.Render() + } + +} diff --git a/pkg/command/root.go b/pkg/command/root.go new file mode 100644 index 00000000..e50c4e3f --- /dev/null +++ b/pkg/command/root.go @@ -0,0 +1,39 @@ +package command + +import ( + "github.com/spf13/cobra" +) + +type options struct { + kubeConfig string + kubeConfigContext string + namespace string + sortBy []string + markdown bool +} + +var opt = &options{} + +var RootCommand = &cobra.Command{ + Use: "kubectl-garm", + Short: "opinionated cli plugin to visualize pools and other garm resources", + Long: "this is the long help - define later", +} + +func Root() *cobra.Command { + return RootCommand +} + +func init() { + + RootCommand.PersistentFlags().StringVar(&opt.kubeConfig, "kubeconfig", "", "path to the kubeconfig file to use for CLI requests") + RootCommand.PersistentFlags().StringVar(&opt.kubeConfigContext, "context", "", "name of the kubeconfig context to use") + RootCommand.PersistentFlags().StringVarP(&opt.namespace, "namespace", "n", "", "namespace to use for CLI requests") + + RootCommand.AddGroup( + &cobra.Group{ + ID: "pools", + Title: "all the pools stuff", + }, + ) +}