From 39760f3e16eb99f39ff8f2744331d0517198d7ff Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Fri, 28 Apr 2023 16:12:08 -0500 Subject: [PATCH 01/38] Adds 'scout' command and 'resources' sub command. - Prints all current resource allocations for each service (CPU, Memory, Storage) to the terminal --- cmd/src/scout.go | 43 ++++++++++++ cmd/src/scout_resources.go | 93 +++++++++++++++++++++++++ go.mod | 8 +++ go.sum | 16 +++++ internal/scout/README.md | 0 internal/scout/resources/resources.go | 97 +++++++++++++++++++++++++++ internal/scout/scout.go | 15 +++++ 7 files changed, 272 insertions(+) create mode 100644 cmd/src/scout.go create mode 100644 cmd/src/scout_resources.go create mode 100644 internal/scout/README.md create mode 100644 internal/scout/resources/resources.go create mode 100644 internal/scout/scout.go diff --git a/cmd/src/scout.go b/cmd/src/scout.go new file mode 100644 index 0000000000..4942ba2820 --- /dev/null +++ b/cmd/src/scout.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "fmt" +) + +var scoutCommands commander + +func init() { + usage := `'src scout' is a tool that provides monitoring for Sourcegraph resource allocations + + EXPERIMENTAL: 'scout' is an experimental command in the 'src' tool + + Usage: + + src scout command [command options] + + The commands are: + + resources print all known sourcegraph resources + estimate reccommend resource allocation for one or many services + usage get CPU, memory and current disk usage + watch track resource usage in real time + + Use "src scout [command] -h" for more information about a command. + ` + + flagSet := flag.NewFlagSet("scout", flag.ExitOnError) + handler := func(args []string) error { + scoutCommands.run(flagSet, "src scout", usage, args) + return nil + } + + commands = append(commands, &command{ + flagSet: flagSet, + aliases: []string{"scout"}, + handler: handler, + usageFunc: func() { + fmt.Println(usage) + }, + }) +} diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go new file mode 100644 index 0000000000..2301486ed5 --- /dev/null +++ b/cmd/src/scout_resources.go @@ -0,0 +1,93 @@ +package main + +import ( + "context" + "errors" + "flag" + "fmt" + "path/filepath" + + // "github.com/docker/docker/client" + // "github.com/sourcegraph/src-cli/internal/scout" + "github.com/sourcegraph/src-cli/internal/scout/resources" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" +) + +func init() { + usage := `'src scout resources' is a tool that provides an overview of resource usage + across an instance of Sourcegraph. + + Examples + List pods and resource allocations in a Kubernetes deployment: + $ src scout resources + + List containers and resource allocations in a Docker deployment: + $ src scout resources --docker + + Add namespace if using namespace in a Kubernetes cluster + $ src scout resources --namespace sg + ` + + flagSet := flag.NewFlagSet("resources", flag.ExitOnError) + usageFunc := func() { + fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src scout %s':\n", flagSet.Name()) + flagSet.PrintDefaults() + fmt.Println(usage) + } + + var ( + kubeConfig *string + namespace = flagSet.String("namespace", "", "(optional) specify the kubernetes namespace to use") + docker = flagSet.Bool("docker", false, "(optional) using docker deployment") + ) + + if home := homedir.HomeDir(); home != "" { + kubeConfig = flagSet.String( + "kubeconfig", + filepath.Join(home, ".kube", "config"), + "(optional) absolute path to the kubeconfig file", + ) + } else { + kubeConfig = flagSet.String("kubeconfig", "", "absolute path to the kubeconfig file") + } + + handler := func(args []string) error { + if err := flagSet.Parse(args); err != nil { + return err + } + + config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig) + if err != nil { + // todo: switch out for sourcegraph error package + return errors.New(fmt.Sprintf("%v: failed to load kubernetes config", err)) + } + + clientSet, err := kubernetes.NewForConfig(config) + if err != nil { + // todo: switch out for sourcegraph error package + return errors.New(fmt.Sprintf("%v: failed to load kubernetes config", err)) + } + + var options []resources.Option + + if *namespace != "" { + options = append(options, resources.WithNamespace(*namespace)) + } + + if *docker { + options = append(options, resources.UsesDocker()) + // return ResourcesDocker() + } + + + return resources.ResourcesK8s(context.Background(), clientSet, config, options...) + } + + scoutCommands = append(scoutCommands, &command{ + flagSet: flagSet, + handler: handler, + usageFunc: usageFunc, + }) +} diff --git a/go.mod b/go.mod index 263376a6a4..6224d854e7 100644 --- a/go.mod +++ b/go.mod @@ -11,6 +11,7 @@ require ( github.com/creack/goselect v0.1.2 github.com/derision-test/glock v0.0.0-20210316032053-f5b74334bb29 github.com/dineshappavoo/basex v0.0.0-20170425072625-481a6f6dc663 + github.com/docker/docker v23.0.5+incompatible github.com/dustin/go-humanize v1.0.1 github.com/gobwas/glob v0.2.3 github.com/google/go-cmp v0.5.9 @@ -49,6 +50,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver v1.5.0 // indirect github.com/Masterminds/sprig v2.22.0+incompatible // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect github.com/alecthomas/chroma v0.10.0 // indirect github.com/aws/aws-sdk-go-v2 v1.17.5 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.13.13 // indirect @@ -70,6 +72,9 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/docker/distribution v2.8.1+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/emicklei/go-restful/v3 v3.9.0 // indirect github.com/envoyproxy/protoc-gen-validate v0.9.1 // indirect github.com/fatih/color v1.13.0 // indirect @@ -115,12 +120,15 @@ require ( github.com/moby/term v0.0.0-20220808134915-39b0c02b01ae // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/morikuni/aec v1.0.0 // indirect github.com/muesli/reflow v0.3.0 // indirect github.com/muesli/termenv v0.12.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-proto-validators v0.3.2 // indirect github.com/nightlyone/lockfile v1.0.0 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pkg/profile v1.6.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/go.sum b/go.sum index 37e963bd89..4d32c74809 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3Q github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Masterminds/sprig v2.22.0+incompatible h1:z4yfnGrZ7netVz+0EDJ0Wi+5VZCSYp4Z0m2dk6cEM60= github.com/Masterminds/sprig v2.22.0+incompatible/go.mod h1:y6hNFY5UBTIWBxnzTeuNhlNS5hqE0NB0E6fgfo2Br3o= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbfjek= @@ -114,6 +116,14 @@ github.com/dineshappavoo/basex v0.0.0-20170425072625-481a6f6dc663/go.mod h1:Kad2 github.com/dlclark/regexp2 v1.4.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/distribution v2.8.1+incompatible h1:Q50tZOPR6T/hjNsyc9g8/syEs6bk8XXApsHjKukMl68= +github.com/docker/distribution v2.8.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v23.0.5+incompatible h1:DaxtlTJjFSnLOXVNUBU1+6kXGz2lpDoEAH6QoxaSg8k= +github.com/docker/docker v23.0.5+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -377,6 +387,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= @@ -410,6 +422,10 @@ github.com/onsi/ginkgo/v2 v2.4.0 h1:+Ig9nvqgS5OBSACXNk15PLdp0U9XPYROt9CFzVdFGIs= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= github.com/onsi/gomega v1.23.0 h1:/oxKu9c2HVap+F3PfKort2Hw5DEU+HGlW8n+tguWsys= +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.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= +github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= diff --git a/internal/scout/README.md b/internal/scout/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go new file mode 100644 index 0000000000..e0fd71aafd --- /dev/null +++ b/internal/scout/resources/resources.go @@ -0,0 +1,97 @@ +package resources + +import ( + "context" + "fmt" + + _ "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "github.com/sourcegraph/src-cli/internal/scout" + "github.com/sourcegraph/sourcegraph/lib/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +type Option = func(config *Config) + +type Config struct { + kubeConfig *string + namespace string + docker bool + k8sClient *kubernetes.Clientset + dockerClient *client.Client +} + +func WithNamespace(namespace string) Option { + return func(config *Config) { + config.namespace = namespace + } +} + +func UsesDocker() Option { + return func(config *Config) { + config.docker = true + } +} + +func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConfig *rest.Config, opts ...Option) error { + cfg := &Config{ + namespace: "default", + docker: false, + k8sClient: clientSet, + dockerClient: nil, + } + + for _, opt := range opts { + opt(cfg) + } + + // Get a PodInterface for all namespaces (use an empty string "" as the namespace). + podInterface := clientSet.CoreV1().Pods(cfg.namespace) + + // List all pods. + podList, err := podInterface.List(ctx, metav1.ListOptions{}) + if err != nil { + return errors.Wrap(err, "Error listing pods: ") + } + + // Iterate over the list of pods and print their names and namespaces. + for _, pod := range podList.Items { + if pod.GetNamespace() == cfg.namespace { + if len(pod.Spec.Containers) > 1 { + fmt.Printf("%s %s:\n", scout.EmojiFingerPointRight, pod.Name) + for _, container := range pod.Spec.Containers { + cpuLimits := container.Resources.Limits.Cpu() + cpuRequests := container.Resources.Requests.Cpu() + memLimits := container.Resources.Limits.Memory() + memRequests := container.Resources.Requests.Memory() + fmt.Printf( + "\t%s: \n\t\tCPU: (%v, %v), \n\t\tMemory: (%v, %v)\n", + container.Name, + cpuLimits, + cpuRequests, + memLimits, + memRequests, + ) + } + } else if len(pod.Spec.Containers) == 1 { + fmt.Printf("%s %s: ", scout.EmojiFingerPointRight, pod.Name) + c := pod.Spec.Containers[0] + cpuLimits := c.Resources.Limits.Cpu() + cpuRequests := c.Resources.Requests.Cpu() + memLimits := c.Resources.Limits.Memory() + memRequests := c.Resources.Requests.Memory() + fmt.Printf( + "\n\tCPU: (%v, %v), \n\tLimits: (%v, %v)\n", + cpuLimits, + cpuRequests, + memLimits, + memRequests, + ) + } + } + } + + return nil +} diff --git a/internal/scout/scout.go b/internal/scout/scout.go new file mode 100644 index 0000000000..c7cff2f34b --- /dev/null +++ b/internal/scout/scout.go @@ -0,0 +1,15 @@ +package scout + +type Status string + +var ( + WarningSign = "⚠️ " + EmojiFingerPointRight = "👉" +) + +const Warning Status = "Warning" + +type result struct { + Status Status + Message string +} From 718560a56f944582ffd54d9bf906887f1f2e9afa Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Fri, 28 Apr 2023 16:34:59 -0500 Subject: [PATCH 02/38] This commit changes output pattern from unformatted lines to a table --- internal/scout/resources/resources.go | 60 ++++++++++----------------- 1 file changed, 23 insertions(+), 37 deletions(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index e0fd71aafd..521d689043 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -3,11 +3,12 @@ package resources import ( "context" "fmt" + "os" + "text/tabwriter" _ "github.com/docker/docker/api/types" "github.com/docker/docker/client" - "github.com/sourcegraph/src-cli/internal/scout" - "github.com/sourcegraph/sourcegraph/lib/errors" + "github.com/sourcegraph/sourcegraph/lib/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -47,51 +48,36 @@ func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConf opt(cfg) } - // Get a PodInterface for all namespaces (use an empty string "" as the namespace). podInterface := clientSet.CoreV1().Pods(cfg.namespace) - - // List all pods. podList, err := podInterface.List(ctx, metav1.ListOptions{}) if err != nil { return errors.Wrap(err, "Error listing pods: ") } - // Iterate over the list of pods and print their names and namespaces. + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "POD\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tDISK") + for _, pod := range podList.Items { if pod.GetNamespace() == cfg.namespace { - if len(pod.Spec.Containers) > 1 { - fmt.Printf("%s %s:\n", scout.EmojiFingerPointRight, pod.Name) - for _, container := range pod.Spec.Containers { - cpuLimits := container.Resources.Limits.Cpu() - cpuRequests := container.Resources.Requests.Cpu() - memLimits := container.Resources.Limits.Memory() - memRequests := container.Resources.Requests.Memory() - fmt.Printf( - "\t%s: \n\t\tCPU: (%v, %v), \n\t\tMemory: (%v, %v)\n", - container.Name, - cpuLimits, - cpuRequests, - memLimits, - memRequests, - ) - } - } else if len(pod.Spec.Containers) == 1 { - fmt.Printf("%s %s: ", scout.EmojiFingerPointRight, pod.Name) - c := pod.Spec.Containers[0] - cpuLimits := c.Resources.Limits.Cpu() - cpuRequests := c.Resources.Requests.Cpu() - memLimits := c.Resources.Limits.Memory() - memRequests := c.Resources.Requests.Memory() - fmt.Printf( - "\n\tCPU: (%v, %v), \n\tLimits: (%v, %v)\n", - cpuLimits, - cpuRequests, - memLimits, - memRequests, - ) + for _, container := range pod.Spec.Containers { + cpuLimits := container.Resources.Limits.Cpu() + cpuRequests := container.Resources.Requests.Cpu() + memLimits := container.Resources.Limits.Memory() + memRequests := container.Resources.Requests.Memory() + fmt.Fprintf( + w, + "%s\t%s\t%s\t%s\t%s\t\n", + container.Name, + cpuLimits, + cpuRequests, + memLimits, + memRequests, + ) } } - } + } + + w.Flush() return nil } From 5340cff788e849c63b9bba00d336780abde08366 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 1 May 2023 10:59:41 -0500 Subject: [PATCH 03/38] adds unit test for ResroucesK8s --- internal/scout/resources/resources.go | 1 - internal/scout/resources/resources_test.go | 49 ++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 internal/scout/resources/resources_test.go diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 521d689043..b6ac002dcc 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -6,7 +6,6 @@ import ( "os" "text/tabwriter" - _ "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/sourcegraph/sourcegraph/lib/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resources/resources_test.go new file mode 100644 index 0000000000..ee3191e4d8 --- /dev/null +++ b/internal/scout/resources/resources_test.go @@ -0,0 +1,49 @@ +package resources + +import ( + "context" + "testing" + + "github.com/sourcegraph/sourcegraph/lib/errors" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" +) + +func TestResourcesK8s(t *testing.T) { + ctx := context.Background() + + config, err := clientcmd.BuildConfigFromFlags("", "/Users/sourcegraph/.kube/config") + if err != nil { + t.Fatal(errors.Wrap(err, "Error getting in cluster config")) + } + + k8sClientSet, err := kubernetes.NewForConfig(config) + if err != nil { + t.Fatal(errors.Wrap(err, "Error creating kubernetes clientset")) + } + + // Create some test pods to list + pod1 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod1", + Namespace: "test", + }, + } + + pod2 := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod2", + Namespace: "other", + }, + } + + k8sClientSet.CoreV1().Pods("test").Create(ctx, pod1, metav1.CreateOptions{}) + k8sClientSet.CoreV1().Pods("other").Create(ctx, pod2, metav1.CreateOptions{}) + + err = ResourcesK8s(ctx, k8sClientSet, nil, WithNamespace("test")) + if err != nil { + t.Fatal(errors.Wrap(err, "Error calling ResourcesK8s")) + } +} From 0bb62747b7ad47edfa3a710d5abd999beeb57c2a Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 1 May 2023 11:01:44 -0500 Subject: [PATCH 04/38] formatting/retabbing --- cmd/src/scout.go | 30 +++++++++++++++--------------- cmd/src/scout_resources.go | 8 ++++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/cmd/src/scout.go b/cmd/src/scout.go index 4942ba2820..07c1c3ed0e 100644 --- a/cmd/src/scout.go +++ b/cmd/src/scout.go @@ -8,7 +8,7 @@ import ( var scoutCommands commander func init() { - usage := `'src scout' is a tool that provides monitoring for Sourcegraph resource allocations + usage := `'src scout' is a tool that provides monitoring for Sourcegraph resource allocations EXPERIMENTAL: 'scout' is an experimental command in the 'src' tool @@ -26,18 +26,18 @@ func init() { Use "src scout [command] -h" for more information about a command. ` - flagSet := flag.NewFlagSet("scout", flag.ExitOnError) - handler := func(args []string) error { - scoutCommands.run(flagSet, "src scout", usage, args) - return nil - } - - commands = append(commands, &command{ - flagSet: flagSet, - aliases: []string{"scout"}, - handler: handler, - usageFunc: func() { - fmt.Println(usage) - }, - }) + flagSet := flag.NewFlagSet("scout", flag.ExitOnError) + handler := func(args []string) error { + scoutCommands.run(flagSet, "src scout", usage, args) + return nil + } + + commands = append(commands, &command{ + flagSet: flagSet, + aliases: []string{"scout"}, + handler: handler, + usageFunc: func() { + fmt.Println(usage) + }, + }) } diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go index 2301486ed5..33985a6ebc 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resources.go @@ -60,13 +60,13 @@ func init() { config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig) if err != nil { - // todo: switch out for sourcegraph error package + // todo: switch out for sourcegraph error package return errors.New(fmt.Sprintf("%v: failed to load kubernetes config", err)) } clientSet, err := kubernetes.NewForConfig(config) if err != nil { - // todo: switch out for sourcegraph error package + // todo: switch out for sourcegraph error package return errors.New(fmt.Sprintf("%v: failed to load kubernetes config", err)) } @@ -78,9 +78,9 @@ func init() { if *docker { options = append(options, resources.UsesDocker()) + // @TODO: // return ResourcesDocker() - } - + } return resources.ResourcesK8s(context.Background(), clientSet, config, options...) } From 2bd386d2e15ae995ff1c9083a29a944dc3ba20d9 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 08:17:11 -0500 Subject: [PATCH 05/38] Adds --docker flag to print resources of docker deployment - Previously, you could only run `scout` on a k8s deployment - Now, by specifying the `--docker` flag, you can run against a docker deployment - Added unit tests for ResourcesK8s and ResourcesDocker --- cmd/src/scout_resources.go | 18 ++-- internal/scout/resources/resources.go | 74 ++++++++++++++- internal/scout/resources/resources_test.go | 103 +++++++++++++++++++++ 3 files changed, 187 insertions(+), 8 deletions(-) diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go index 33985a6ebc..3bf25dc3db 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resources.go @@ -2,17 +2,17 @@ package main import ( "context" - "errors" "flag" "fmt" "path/filepath" - // "github.com/docker/docker/client" - // "github.com/sourcegraph/src-cli/internal/scout" - "github.com/sourcegraph/src-cli/internal/scout/resources" + "github.com/docker/docker/client" + "github.com/sourcegraph/sourcegraph/lib/errors" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" + + "github.com/sourcegraph/src-cli/internal/scout/resources" ) func init() { @@ -41,6 +41,8 @@ func init() { kubeConfig *string namespace = flagSet.String("namespace", "", "(optional) specify the kubernetes namespace to use") docker = flagSet.Bool("docker", false, "(optional) using docker deployment") + // TODO: option for getting resource allocation of the Node + // nodes = flagSet.Bool("node", false, "(optional) view resources for node(s)") ) if home := homedir.HomeDir(); home != "" { @@ -78,8 +80,12 @@ func init() { if *docker { options = append(options, resources.UsesDocker()) - // @TODO: - // return ResourcesDocker() + dockerClient, err := client.NewClientWithOpts(client.FromEnv) + if err != nil { + return errors.Wrap(err, "Error creating docker client: ") + } + + return resources.ResourcesDocker(context.Background(), dockerClient) } return resources.ResourcesK8s(context.Background(), clientSet, config, options...) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index b6ac002dcc..8af152dd35 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -6,6 +6,7 @@ import ( "os" "text/tabwriter" + "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/sourcegraph/sourcegraph/lib/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -35,6 +36,7 @@ func UsesDocker() Option { } } +// ResourcesK8s prints the CPU, memory, and storage resource limits, requests and capacity for Kubernetes pods. func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConfig *rest.Config, opts ...Option) error { cfg := &Config{ namespace: "default", @@ -54,7 +56,7 @@ func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConf } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "POD\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tDISK") + fmt.Fprintln(w, "CONTAINER\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY") for _, pod := range podList.Items { if pod.GetNamespace() == cfg.namespace { @@ -63,20 +65,88 @@ func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConf cpuRequests := container.Resources.Requests.Cpu() memLimits := container.Resources.Limits.Memory() memRequests := container.Resources.Requests.Memory() + + var capacity string + for _, volumeMount := range container.VolumeMounts { + for _, volume := range pod.Spec.Volumes { + fmt.Println(pod.Spec.Volumes) + if volume.Name == volumeMount.Name && volume.PersistentVolumeClaim != nil { + pvc, err := clientSet.CoreV1().PersistentVolumeClaims(cfg.namespace).Get( + ctx, + volume.PersistentVolumeClaim.ClaimName, + metav1.GetOptions{}, + ) + + if err != nil { + return errors.Wrapf( + err, + "Error getting PVC %s", + volume.PersistentVolumeClaim.ClaimName, + ) + } + + capacity = pvc.Status.Capacity.Storage().String() + break + } + } + } + fmt.Fprintf( w, - "%s\t%s\t%s\t%s\t%s\t\n", + "%s\t%s\t%s\t%s\t%s\t%s\t\n", container.Name, cpuLimits, cpuRequests, memLimits, memRequests, + capacity, ) } } } w.Flush() + return nil +} + +// DockerClientInterface defines the interface for interacting with the Docker API. +type DockerClientInterface interface { + ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) + ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) + Close() error +} + +// ResourcesDocker prints the CPU and memory resource limits and requests for running Docker containers. +func ResourcesDocker(ctx context.Context, dockerClient DockerClientInterface) error { + containers, err := dockerClient.ContainerList(ctx, types.ContainerListOptions{}) + if err != nil { + return fmt.Errorf("Error listing Docker containers: %v", err) + } + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "Container\tCPU Limits\tCPU Requests\tMem Limits\tMem Requests") + + for _, container := range containers { + containerInfo, err := dockerClient.ContainerInspect(ctx, container.ID) + if err != nil { + return fmt.Errorf("Error inspecting container %s: %v", container.ID, err) + } + + cpuLimits := containerInfo.HostConfig.NanoCPUs + cpuRequests := containerInfo.HostConfig.CPUPeriod + memLimits := containerInfo.HostConfig.Memory + memRequests := containerInfo.HostConfig.MemoryReservation + fmt.Fprintf( + w, + "%s\t%d\t%d\t%d\t%d\n", + containerInfo.Name, + cpuLimits, + cpuRequests, + memLimits, + memRequests, + ) + } + + w.Flush() return nil } diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resources/resources_test.go index ee3191e4d8..67137b0e6b 100644 --- a/internal/scout/resources/resources_test.go +++ b/internal/scout/resources/resources_test.go @@ -2,9 +2,18 @@ package resources import ( "context" + "fmt" + "os" + "strings" "testing" + "text/tabwriter" + "io/ioutil" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/sourcegraph/sourcegraph/lib/errors" + "github.com/stretchr/testify/mock" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -47,3 +56,97 @@ func TestResourcesK8s(t *testing.T) { t.Fatal(errors.Wrap(err, "Error calling ResourcesK8s")) } } + +type DockerClientMock struct { + mock.Mock +} + +func (m *DockerClientMock) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) { + args := m.Called(ctx, options) + return args.Get(0).([]types.Container), args.Error(1) +} + +func (m *DockerClientMock) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) { + args := m.Called(ctx, containerID) + return args.Get(0).(types.ContainerJSON), args.Error(1) +} + +func (m *DockerClientMock) Close() error { + args := m.Called() + return args.Error(0) +} + +func TestResourcesDocker(t *testing.T) { + dockerClient := new(DockerClientMock) + + dockerClient.On("ContainerList", mock.Anything, mock.Anything).Return([]types.Container{ + {ID: "container1"}, + {ID: "container2"}, + }, nil) + + dockerClient.On("ContainerInspect", mock.Anything, "container1").Return(types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + Name: "container1", + HostConfig: &container.HostConfig{ + Resources: container.Resources{ + NanoCPUs: 2000000000, + CPUPeriod: 100000, + Memory: 536870912, + MemoryReservation: 268435456, + }, + }, + }, + }, nil) + + dockerClient.On("ContainerInspect", mock.Anything, "container2").Return(types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + Name: "container2", + HostConfig: &container.HostConfig{ + Resources: container.Resources{ + NanoCPUs: 1000000000, + CPUPeriod: 50000, + Memory: 268435456, + MemoryReservation: 134217728, + }, + }, + }, + }, nil) + + dockerClient.On("Close").Return(nil) + + var expectedOutput strings.Builder + expectedW := tabwriter.NewWriter(&expectedOutput, 0, 0, 2, ' ', 0) + + fmt.Fprintln(expectedW, "Container\tCPU Limits\tCPU Requests\tMem Limits\tMem Requests") + fmt.Fprintf(expectedW, "container1\t2000000000\t100000\t536870912\t268435456\n") + fmt.Fprintf(expectedW, "container2\t1000000000\t50000\t268435456\t134217728\n") + expectedW.Flush() + + oldStdout := os.Stdout + r, w, _ := os.Pipe() + os.Stdout = w + + err := ResourcesDocker(context.Background(), dockerClient) + if err != nil { + t.Fatalf("ResourcesDocker returned an error: %v", err) + } + + err = dockerClient.Close() + if err != nil { + t.Fatalf("Error closing docker client: %v", err) + } + + w.Close() + os.Stdout = oldStdout + + output, err := ioutil.ReadAll(r) + if err != nil { + t.Fatalf("Error reading from pipe: %v", err) + } + + if string(output) != expectedOutput.String() { + t.Errorf("Expected output:\n%s\nActual output:\n%s", expectedOutput.String(), output) + } + + dockerClient.AssertExpectations(t) +} From 6dfdfda40178879c4af7f4c82503b293ac7871bd Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 08:27:23 -0500 Subject: [PATCH 06/38] edits docs to reflect which sub commands are currently available to use --- cmd/src/scout.go | 9 +++++---- cmd/src/scout_resources.go | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/cmd/src/scout.go b/cmd/src/scout.go index 07c1c3ed0e..f7e82330ad 100644 --- a/cmd/src/scout.go +++ b/cmd/src/scout.go @@ -10,7 +10,8 @@ var scoutCommands commander func init() { usage := `'src scout' is a tool that provides monitoring for Sourcegraph resource allocations - EXPERIMENTAL: 'scout' is an experimental command in the 'src' tool + EXPERIMENTAL: 'scout' is an experimental command in the 'src' tool. To use, you must + point your .kube config to your Sourcegraph instance. Usage: @@ -19,9 +20,9 @@ func init() { The commands are: resources print all known sourcegraph resources - estimate reccommend resource allocation for one or many services - usage get CPU, memory and current disk usage - watch track resource usage in real time + estimate (coming soon) reccommend resource allocation for one or many services + usage (coming soon) get CPU, memory and current disk usage + spy (coming soon) track resource usage in real time Use "src scout [command] -h" for more information about a command. ` diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go index 3bf25dc3db..1cb91b9d25 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resources.go @@ -17,7 +17,7 @@ import ( func init() { usage := `'src scout resources' is a tool that provides an overview of resource usage - across an instance of Sourcegraph. + across an instance of Sourcegraph. Part of the EXPERIMENTAL "src scout" tool. Examples List pods and resource allocations in a Kubernetes deployment: From 5831881c0cd7b4184447e853a94a75566ca990d1 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 10:16:44 -0500 Subject: [PATCH 07/38] refactor --- internal/scout/resources/resources.go | 110 +++++++++++++++------ internal/scout/resources/resources_test.go | 27 ++++- 2 files changed, 101 insertions(+), 36 deletions(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 8af152dd35..eb4a7bd881 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -3,12 +3,14 @@ package resources import ( "context" "fmt" + "math" "os" "text/tabwriter" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/sourcegraph/sourcegraph/lib/errors" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/rest" @@ -36,7 +38,7 @@ func UsesDocker() Option { } } -// ResourcesK8s prints the CPU, memory, and storage resource limits, requests and capacity for Kubernetes pods. +// ResourcesK8s prints the CPU and memory resource limits and requests for all pods in the given namespace. func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConfig *rest.Config, opts ...Option) error { cfg := &Config{ namespace: "default", @@ -49,7 +51,11 @@ func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConf opt(cfg) } - podInterface := clientSet.CoreV1().Pods(cfg.namespace) + return listPodResources(ctx, cfg) +} + +func listPodResources(ctx context.Context, cfg *Config) error { + podInterface := cfg.k8sClient.CoreV1().Pods(cfg.namespace) podList, err := podInterface.List(ctx, metav1.ListOptions{}) if err != nil { return errors.Wrap(err, "Error listing pods: ") @@ -66,29 +72,9 @@ func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConf memLimits := container.Resources.Limits.Memory() memRequests := container.Resources.Requests.Memory() - var capacity string - for _, volumeMount := range container.VolumeMounts { - for _, volume := range pod.Spec.Volumes { - fmt.Println(pod.Spec.Volumes) - if volume.Name == volumeMount.Name && volume.PersistentVolumeClaim != nil { - pvc, err := clientSet.CoreV1().PersistentVolumeClaims(cfg.namespace).Get( - ctx, - volume.PersistentVolumeClaim.ClaimName, - metav1.GetOptions{}, - ) - - if err != nil { - return errors.Wrapf( - err, - "Error getting PVC %s", - volume.PersistentVolumeClaim.ClaimName, - ) - } - - capacity = pvc.Status.Capacity.Storage().String() - break - } - } + capacity, err := getPVCCapacity(ctx, cfg, container, pod) + if err != nil { + return err } fmt.Fprintf( @@ -109,6 +95,33 @@ func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConf return nil } +func getPVCCapacity(ctx context.Context, cfg *Config, container v1.Container, pod v1.Pod) (string, error) { + var capacity string + for _, volumeMount := range container.VolumeMounts { + for _, volume := range pod.Spec.Volumes { + if volume.Name == volumeMount.Name && volume.PersistentVolumeClaim != nil { + pvc, err := cfg.k8sClient.CoreV1().PersistentVolumeClaims(cfg.namespace).Get( + ctx, + volume.PersistentVolumeClaim.ClaimName, + metav1.GetOptions{}, + ) + + if err != nil { + return "", errors.Wrapf( + err, + "Error getting PVC %s", + volume.PersistentVolumeClaim.ClaimName, + ) + } + + capacity = pvc.Status.Capacity.Storage().String() + break + } + } + } + return capacity, nil +} + // DockerClientInterface defines the interface for interacting with the Docker API. type DockerClientInterface interface { ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) @@ -124,7 +137,7 @@ func ResourcesDocker(ctx context.Context, dockerClient DockerClientInterface) er } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - fmt.Fprintln(w, "Container\tCPU Limits\tCPU Requests\tMem Limits\tMem Requests") + fmt.Fprintln(w, "Container\tCPU Limits\tCPU Period\tCPU Quota\tMem Limits\tMem Requests") for _, container := range containers { containerInfo, err := dockerClient.ContainerInspect(ctx, container.ID) @@ -133,20 +146,53 @@ func ResourcesDocker(ctx context.Context, dockerClient DockerClientInterface) er } cpuLimits := containerInfo.HostConfig.NanoCPUs - cpuRequests := containerInfo.HostConfig.CPUPeriod + cpuPeriod := containerInfo.HostConfig.CPUPeriod + cpuQuota := containerInfo.HostConfig.CPUQuota memLimits := containerInfo.HostConfig.Memory memRequests := containerInfo.HostConfig.MemoryReservation + + limUnit, limVal, err := getMemUnits(memLimits) + if err != nil { + return err + } + + reqUnit, reqVal, err := getMemUnits(memRequests) + if err != nil { + return err + } + fmt.Fprintf( w, - "%s\t%d\t%d\t%d\t%d\n", + "%s\t%d\t%v\t%v\t%v\t%v\n", containerInfo.Name, - cpuLimits, - cpuRequests, - memLimits, - memRequests, + cpuLimits/1e9, + fmt.Sprintf("%d MS", cpuPeriod/1000), + fmt.Sprintf(`%v%%`, math.Ceil((float64(cpuQuota)/float64(cpuPeriod))*100)), + fmt.Sprintf("%d %s", limVal, limUnit), + fmt.Sprintf("%d %s", reqVal, reqUnit), ) } w.Flush() return nil } + +// getMemUnits converts a byte value to the appropriate memory unit. +func getMemUnits(valToConvert int64) (string, int64, error) { + if valToConvert < 0 { + return "", valToConvert, fmt.Errorf("Invalid memory value: %d", valToConvert) + } + + var memUnit string + if valToConvert < 1000000 { + memUnit = "KB" + } else if valToConvert < 1000000000 { + memUnit = "MB" + valToConvert = valToConvert / 1000000 + } else { + memUnit = "GB" + valToConvert = valToConvert / 1000000000 + } + + return memUnit, valToConvert, nil +} diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resources/resources_test.go index 67137b0e6b..4f969a9cdf 100644 --- a/internal/scout/resources/resources_test.go +++ b/internal/scout/resources/resources_test.go @@ -82,6 +82,7 @@ func TestResourcesDocker(t *testing.T) { dockerClient.On("ContainerList", mock.Anything, mock.Anything).Return([]types.Container{ {ID: "container1"}, {ID: "container2"}, + {ID: "container3"}, }, nil) dockerClient.On("ContainerInspect", mock.Anything, "container1").Return(types.ContainerJSON{ @@ -91,7 +92,8 @@ func TestResourcesDocker(t *testing.T) { Resources: container.Resources{ NanoCPUs: 2000000000, CPUPeriod: 100000, - Memory: 536870912, + CPUQuota: 50000, + Memory: 1536870912, MemoryReservation: 268435456, }, }, @@ -105,6 +107,7 @@ func TestResourcesDocker(t *testing.T) { Resources: container.Resources{ NanoCPUs: 1000000000, CPUPeriod: 50000, + CPUQuota: 25000, Memory: 268435456, MemoryReservation: 134217728, }, @@ -112,14 +115,30 @@ func TestResourcesDocker(t *testing.T) { }, }, nil) + dockerClient.On("ContainerInspect", mock.Anything, "container3").Return(types.ContainerJSON{ + ContainerJSONBase: &types.ContainerJSONBase{ + Name: "container3", + HostConfig: &container.HostConfig{ + Resources: container.Resources{ + NanoCPUs: 4000000000, + CPUPeriod: 150000, + CPUQuota: 65000, + Memory: 5268435456, + MemoryReservation: 4134217728, + }, + }, + }, + }, nil) + dockerClient.On("Close").Return(nil) var expectedOutput strings.Builder expectedW := tabwriter.NewWriter(&expectedOutput, 0, 0, 2, ' ', 0) - fmt.Fprintln(expectedW, "Container\tCPU Limits\tCPU Requests\tMem Limits\tMem Requests") - fmt.Fprintf(expectedW, "container1\t2000000000\t100000\t536870912\t268435456\n") - fmt.Fprintf(expectedW, "container2\t1000000000\t50000\t268435456\t134217728\n") + fmt.Fprintln(expectedW, "Container\tCPU Limits\tCPU Period\tCPU Quota\tMem Limits\tMem Requests") + fmt.Fprintf(expectedW, "container1\t2\t100 MS\t50%%\t1 GB\t268 MB\n") + fmt.Fprintf(expectedW, "container2\t1\t50 MS\t50%%\t268 MB\t134 MB\n") + fmt.Fprintf(expectedW, "container3\t4\t150 MS\t44%%\t5 GB\t4 GB\n") expectedW.Flush() oldStdout := os.Stdout From a66ed4c56ee6d295c575c0bae6252d3a06e04c89 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 10:33:42 -0500 Subject: [PATCH 08/38] fix build issues --- cmd/src/scout_resources.go | 1 - internal/scout/resources/resources.go | 7 ------- internal/scout/resources/resources_test.go | 5 ++--- internal/scout/scout.go | 7 +------ 4 files changed, 3 insertions(+), 17 deletions(-) diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go index 1cb91b9d25..625a6a9031 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resources.go @@ -79,7 +79,6 @@ func init() { } if *docker { - options = append(options, resources.UsesDocker()) dockerClient, err := client.NewClientWithOpts(client.FromEnv) if err != nil { return errors.Wrap(err, "Error creating docker client: ") diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index eb4a7bd881..861def11ed 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -19,7 +19,6 @@ import ( type Option = func(config *Config) type Config struct { - kubeConfig *string namespace string docker bool k8sClient *kubernetes.Clientset @@ -32,12 +31,6 @@ func WithNamespace(namespace string) Option { } } -func UsesDocker() Option { - return func(config *Config) { - config.docker = true - } -} - // ResourcesK8s prints the CPU and memory resource limits and requests for all pods in the given namespace. func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConfig *rest.Config, opts ...Option) error { cfg := &Config{ diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resources/resources_test.go index 4f969a9cdf..7b56d580cb 100644 --- a/internal/scout/resources/resources_test.go +++ b/internal/scout/resources/resources_test.go @@ -3,13 +3,12 @@ package resources import ( "context" "fmt" + "io" "os" "strings" "testing" "text/tabwriter" - "io/ioutil" - "github.com/docker/docker/api/types" "github.com/docker/docker/api/types/container" "github.com/sourcegraph/sourcegraph/lib/errors" @@ -158,7 +157,7 @@ func TestResourcesDocker(t *testing.T) { w.Close() os.Stdout = oldStdout - output, err := ioutil.ReadAll(r) + output, err := io.ReadAll(r) if err != nil { t.Fatalf("Error reading from pipe: %v", err) } diff --git a/internal/scout/scout.go b/internal/scout/scout.go index c7cff2f34b..8242b06289 100644 --- a/internal/scout/scout.go +++ b/internal/scout/scout.go @@ -3,13 +3,8 @@ package scout type Status string var ( - WarningSign = "⚠️ " + WarningSign = "⚠️ " EmojiFingerPointRight = "👉" ) const Warning Status = "Warning" - -type result struct { - Status Status - Message string -} From bd01abcc8ae434ef7cb45dfc92d9ee38c0839d66 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 11:04:21 -0500 Subject: [PATCH 09/38] replace hard coded file path with path generated by homedir package --- internal/scout/resources/resources_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resources/resources_test.go index 7b56d580cb..3298e65878 100644 --- a/internal/scout/resources/resources_test.go +++ b/internal/scout/resources/resources_test.go @@ -17,12 +17,13 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" + "k8s.io/client-go/util/homedir" ) func TestResourcesK8s(t *testing.T) { ctx := context.Background() - config, err := clientcmd.BuildConfigFromFlags("", "/Users/sourcegraph/.kube/config") + config, err := clientcmd.BuildConfigFromFlags("", homedir.HomeDir()+"/.kube/config") if err != nil { t.Fatal(errors.Wrap(err, "Error getting in cluster config")) } From 3263141b33b6e2d916562fbe7e7f1b24c53b3702 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:53:45 -0500 Subject: [PATCH 10/38] Update cmd/src/scout_resources.go Co-authored-by: Jacob Pleiness --- cmd/src/scout_resources.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go index 625a6a9031..9a4c781130 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resources.go @@ -62,7 +62,6 @@ func init() { config, err := clientcmd.BuildConfigFromFlags("", *kubeConfig) if err != nil { - // todo: switch out for sourcegraph error package return errors.New(fmt.Sprintf("%v: failed to load kubernetes config", err)) } From 94642d9ad048c376f7027dec6af5f23e11f0a8f9 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:53:52 -0500 Subject: [PATCH 11/38] Update cmd/src/scout_resources.go Co-authored-by: Jacob Pleiness --- cmd/src/scout_resources.go | 1 - 1 file changed, 1 deletion(-) diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go index 9a4c781130..94d1e8d82e 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resources.go @@ -67,7 +67,6 @@ func init() { clientSet, err := kubernetes.NewForConfig(config) if err != nil { - // todo: switch out for sourcegraph error package return errors.New(fmt.Sprintf("%v: failed to load kubernetes config", err)) } From 94bcd3097c64d8c8ff4af7c4380673633526b96b Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:54:00 -0500 Subject: [PATCH 12/38] Update internal/scout/resources/resources.go Co-authored-by: Jacob Pleiness --- internal/scout/resources/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 861def11ed..b798f36040 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -32,7 +32,7 @@ func WithNamespace(namespace string) Option { } // ResourcesK8s prints the CPU and memory resource limits and requests for all pods in the given namespace. -func ResourcesK8s(ctx context.Context, clientSet *kubernetes.Clientset, restConfig *rest.Config, opts ...Option) error { +func K8s(ctx context.Context, clientSet *kubernetes.Clientset, restConfig *rest.Config, opts ...Option) error { cfg := &Config{ namespace: "default", docker: false, From 3b95e210d02b9d063ab4a1cb35b76ef8374aeee3 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:54:26 -0500 Subject: [PATCH 13/38] Update internal/scout/resources/resources.go Co-authored-by: Jacob Pleiness --- internal/scout/resources/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index b798f36040..d2e8d9cb4d 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -123,7 +123,7 @@ type DockerClientInterface interface { } // ResourcesDocker prints the CPU and memory resource limits and requests for running Docker containers. -func ResourcesDocker(ctx context.Context, dockerClient DockerClientInterface) error { +func Docker(ctx context.Context, dockerClient DockerClientInterface) error { containers, err := dockerClient.ContainerList(ctx, types.ContainerListOptions{}) if err != nil { return fmt.Errorf("Error listing Docker containers: %v", err) From a1768d459525d99f8189394d3686975169b51cb3 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:54:40 -0500 Subject: [PATCH 14/38] Update internal/scout/resources/resources.go Co-authored-by: Jacob Pleiness --- internal/scout/resources/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index d2e8d9cb4d..9263ea47cd 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -126,7 +126,7 @@ type DockerClientInterface interface { func Docker(ctx context.Context, dockerClient DockerClientInterface) error { containers, err := dockerClient.ContainerList(ctx, types.ContainerListOptions{}) if err != nil { - return fmt.Errorf("Error listing Docker containers: %v", err) + return fmt.Errorf("error listing docker containers: %v", err) } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) From 0a9d3c1e0f17bec69f037dcb0399dcce864bb834 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:55:16 -0500 Subject: [PATCH 15/38] don't capitalize error messages Co-authored-by: Jacob Pleiness --- internal/scout/resources/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 9263ea47cd..359ddae390 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -173,7 +173,7 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { // getMemUnits converts a byte value to the appropriate memory unit. func getMemUnits(valToConvert int64) (string, int64, error) { if valToConvert < 0 { - return "", valToConvert, fmt.Errorf("Invalid memory value: %d", valToConvert) + return "", valToConvert, fmt.Errorf("invalid memory value: %d", valToConvert) } var memUnit string From 2780bcce4b94a93687e80847506cd55517ef2664 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:55:45 -0500 Subject: [PATCH 16/38] remove capitalization on error message Co-authored-by: Jacob Pleiness --- internal/scout/resources/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 359ddae390..60c0ed1de1 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -102,7 +102,7 @@ func getPVCCapacity(ctx context.Context, cfg *Config, container v1.Container, po if err != nil { return "", errors.Wrapf( err, - "Error getting PVC %s", + "error getting PVC %s", volume.PersistentVolumeClaim.ClaimName, ) } From 36f2b7afb49c962077270d39d7630014755169d1 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 12:56:04 -0500 Subject: [PATCH 17/38] capitalization Co-authored-by: Jacob Pleiness --- internal/scout/resources/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 60c0ed1de1..c748b0108d 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -51,7 +51,7 @@ func listPodResources(ctx context.Context, cfg *Config) error { podInterface := cfg.k8sClient.CoreV1().Pods(cfg.namespace) podList, err := podInterface.List(ctx, metav1.ListOptions{}) if err != nil { - return errors.Wrap(err, "Error listing pods: ") + return errors.Wrap(err, "error listing pods: ") } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) From 31638783aa08cb06ecdc68e2b2784e9bb24f72a6 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 13:24:05 -0500 Subject: [PATCH 18/38] add defer functions for closing tabwriter --- internal/scout/resources/resources.go | 10 ++++++++-- internal/scout/scout.go | 10 ---------- 2 files changed, 8 insertions(+), 12 deletions(-) delete mode 100644 internal/scout/scout.go diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index c748b0108d..7a2d6115fc 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -55,6 +55,10 @@ func listPodResources(ctx context.Context, cfg *Config) error { } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer func () { + _ = w.Flush() + }() + fmt.Fprintln(w, "CONTAINER\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY") for _, pod := range podList.Items { @@ -84,7 +88,6 @@ func listPodResources(ctx context.Context, cfg *Config) error { } } - w.Flush() return nil } @@ -130,6 +133,10 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + defer func () { + _ = w.Flush() + }() + fmt.Fprintln(w, "Container\tCPU Limits\tCPU Period\tCPU Quota\tMem Limits\tMem Requests") for _, container := range containers { @@ -166,7 +173,6 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { ) } - w.Flush() return nil } diff --git a/internal/scout/scout.go b/internal/scout/scout.go deleted file mode 100644 index 8242b06289..0000000000 --- a/internal/scout/scout.go +++ /dev/null @@ -1,10 +0,0 @@ -package scout - -type Status string - -var ( - WarningSign = "⚠️ " - EmojiFingerPointRight = "👉" -) - -const Warning Status = "Warning" From 3698d1f884adcadad579324df56b7d61f20f136d Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 13:24:57 -0500 Subject: [PATCH 19/38] capitalization Co-authored-by: Jacob Pleiness --- internal/scout/resources/resources.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 7a2d6115fc..6c2ae70b6c 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -142,7 +142,7 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { for _, container := range containers { containerInfo, err := dockerClient.ContainerInspect(ctx, container.ID) if err != nil { - return fmt.Errorf("Error inspecting container %s: %v", container.ID, err) + return fmt.Errorf("error inspecting container %s: %v", container.ID, err) } cpuLimits := containerInfo.HostConfig.NanoCPUs From faf2878b4f1087d2393b96024a0fd26e8431f05d Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 13:38:25 -0500 Subject: [PATCH 20/38] change function invocations to reflect new function names and retab --- cmd/src/scout_resources.go | 4 ++-- internal/scout/resources/resources.go | 12 ++++++------ internal/scout/resources/resources_test.go | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resources.go index 94d1e8d82e..c350f144f0 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resources.go @@ -82,10 +82,10 @@ func init() { return errors.Wrap(err, "Error creating docker client: ") } - return resources.ResourcesDocker(context.Background(), dockerClient) + return resources.Docker(context.Background(), dockerClient) } - return resources.ResourcesK8s(context.Background(), clientSet, config, options...) + return resources.K8s(context.Background(), clientSet, config, options...) } scoutCommands = append(scoutCommands, &command{ diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 6c2ae70b6c..47fd5edaeb 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -55,9 +55,9 @@ func listPodResources(ctx context.Context, cfg *Config) error { } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - defer func () { - _ = w.Flush() - }() + defer func() { + _ = w.Flush() + }() fmt.Fprintln(w, "CONTAINER\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY") @@ -133,9 +133,9 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { } w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - defer func () { - _ = w.Flush() - }() + defer func() { + _ = w.Flush() + }() fmt.Fprintln(w, "Container\tCPU Limits\tCPU Period\tCPU Quota\tMem Limits\tMem Requests") diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resources/resources_test.go index 3298e65878..c592dc58fb 100644 --- a/internal/scout/resources/resources_test.go +++ b/internal/scout/resources/resources_test.go @@ -51,7 +51,7 @@ func TestResourcesK8s(t *testing.T) { k8sClientSet.CoreV1().Pods("test").Create(ctx, pod1, metav1.CreateOptions{}) k8sClientSet.CoreV1().Pods("other").Create(ctx, pod2, metav1.CreateOptions{}) - err = ResourcesK8s(ctx, k8sClientSet, nil, WithNamespace("test")) + err = K8s(ctx, k8sClientSet, nil, WithNamespace("test")) if err != nil { t.Fatal(errors.Wrap(err, "Error calling ResourcesK8s")) } @@ -145,7 +145,7 @@ func TestResourcesDocker(t *testing.T) { r, w, _ := os.Pipe() os.Stdout = w - err := ResourcesDocker(context.Background(), dockerClient) + err := Docker(context.Background(), dockerClient) if err != nil { t.Fatalf("ResourcesDocker returned an error: %v", err) } From 60b8c73db32ed50ad88392634c57d48bf10ea5fb Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 15:18:42 -0500 Subject: [PATCH 21/38] reformats output for --docker flag --- internal/scout/resources/resources.go | 22 ++++++++++------------ internal/scout/resources/resources_test.go | 17 +++++++---------- 2 files changed, 17 insertions(+), 22 deletions(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 47fd5edaeb..1f22fe6827 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -3,7 +3,6 @@ package resources import ( "context" "fmt" - "math" "os" "text/tabwriter" @@ -56,7 +55,7 @@ func listPodResources(ctx context.Context, cfg *Config) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) defer func() { - _ = w.Flush() + _ = w.Flush() }() fmt.Fprintln(w, "CONTAINER\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY") @@ -137,37 +136,36 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { _ = w.Flush() }() - fmt.Fprintln(w, "Container\tCPU Limits\tCPU Period\tCPU Quota\tMem Limits\tMem Requests") + fmt.Fprintln(w, "Container\tCPU Cores\tCPU Shares\tMem Limits\tMem Reservations") for _, container := range containers { + containerInfo, err := dockerClient.ContainerInspect(ctx, container.ID) if err != nil { return fmt.Errorf("error inspecting container %s: %v", container.ID, err) } - cpuLimits := containerInfo.HostConfig.NanoCPUs - cpuPeriod := containerInfo.HostConfig.CPUPeriod - cpuQuota := containerInfo.HostConfig.CPUQuota + cpuCores := containerInfo.HostConfig.NanoCPUs + cpuShares := containerInfo.HostConfig.CPUShares memLimits := containerInfo.HostConfig.Memory - memRequests := containerInfo.HostConfig.MemoryReservation + memReservations := containerInfo.HostConfig.MemoryReservation limUnit, limVal, err := getMemUnits(memLimits) if err != nil { return err } - reqUnit, reqVal, err := getMemUnits(memRequests) + reqUnit, reqVal, err := getMemUnits(memReservations) if err != nil { return err } fmt.Fprintf( w, - "%s\t%d\t%v\t%v\t%v\t%v\n", + "%s\t%d\t%v\t%v\t%v\n", containerInfo.Name, - cpuLimits/1e9, - fmt.Sprintf("%d MS", cpuPeriod/1000), - fmt.Sprintf(`%v%%`, math.Ceil((float64(cpuQuota)/float64(cpuPeriod))*100)), + cpuCores/1e9, + cpuShares, fmt.Sprintf("%d %s", limVal, limUnit), fmt.Sprintf("%d %s", reqVal, reqUnit), ) diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resources/resources_test.go index c592dc58fb..373023f789 100644 --- a/internal/scout/resources/resources_test.go +++ b/internal/scout/resources/resources_test.go @@ -91,8 +91,7 @@ func TestResourcesDocker(t *testing.T) { HostConfig: &container.HostConfig{ Resources: container.Resources{ NanoCPUs: 2000000000, - CPUPeriod: 100000, - CPUQuota: 50000, + CPUShares: 512, Memory: 1536870912, MemoryReservation: 268435456, }, @@ -106,8 +105,7 @@ func TestResourcesDocker(t *testing.T) { HostConfig: &container.HostConfig{ Resources: container.Resources{ NanoCPUs: 1000000000, - CPUPeriod: 50000, - CPUQuota: 25000, + CPUShares: 1024, Memory: 268435456, MemoryReservation: 134217728, }, @@ -121,8 +119,7 @@ func TestResourcesDocker(t *testing.T) { HostConfig: &container.HostConfig{ Resources: container.Resources{ NanoCPUs: 4000000000, - CPUPeriod: 150000, - CPUQuota: 65000, + CPUShares: 2048, Memory: 5268435456, MemoryReservation: 4134217728, }, @@ -135,10 +132,10 @@ func TestResourcesDocker(t *testing.T) { var expectedOutput strings.Builder expectedW := tabwriter.NewWriter(&expectedOutput, 0, 0, 2, ' ', 0) - fmt.Fprintln(expectedW, "Container\tCPU Limits\tCPU Period\tCPU Quota\tMem Limits\tMem Requests") - fmt.Fprintf(expectedW, "container1\t2\t100 MS\t50%%\t1 GB\t268 MB\n") - fmt.Fprintf(expectedW, "container2\t1\t50 MS\t50%%\t268 MB\t134 MB\n") - fmt.Fprintf(expectedW, "container3\t4\t150 MS\t44%%\t5 GB\t4 GB\n") + fmt.Fprintln(expectedW, "Container\tCPU Cores\tCPU Shares\tMem Limits\tMem Reservations") + fmt.Fprintf(expectedW, "container1\t2\t512\t1 GB\t268 MB\n") + fmt.Fprintf(expectedW, "container2\t1\t1024\t268 MB\t134 MB\n") + fmt.Fprintf(expectedW, "container3\t4\t2048\t5 GB\t4 GB\n") expectedW.Flush() oldStdout := os.Stdout From 24d2fa5bf695c255829463f5287576884a13e1f4 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 2 May 2023 15:26:41 -0500 Subject: [PATCH 22/38] retab --- internal/scout/resources/resources.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/internal/scout/resources/resources.go b/internal/scout/resources/resources.go index 1f22fe6827..11b0a9c723 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resources/resources.go @@ -55,7 +55,7 @@ func listPodResources(ctx context.Context, cfg *Config) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) defer func() { - _ = w.Flush() + _ = w.Flush() }() fmt.Fprintln(w, "CONTAINER\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY") @@ -139,14 +139,13 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { fmt.Fprintln(w, "Container\tCPU Cores\tCPU Shares\tMem Limits\tMem Reservations") for _, container := range containers { - containerInfo, err := dockerClient.ContainerInspect(ctx, container.ID) if err != nil { return fmt.Errorf("error inspecting container %s: %v", container.ID, err) } cpuCores := containerInfo.HostConfig.NanoCPUs - cpuShares := containerInfo.HostConfig.CPUShares + cpuShares := containerInfo.HostConfig.CPUShares memLimits := containerInfo.HostConfig.Memory memReservations := containerInfo.HostConfig.MemoryReservation @@ -165,7 +164,7 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { "%s\t%d\t%v\t%v\t%v\n", containerInfo.Name, cpuCores/1e9, - cpuShares, + cpuShares, fmt.Sprintf("%d %s", limVal, limUnit), fmt.Sprintf("%d %s", reqVal, reqUnit), ) From d6c9acf4526943bce77f0b648b3bc6f7d028b805 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Wed, 3 May 2023 20:41:56 -0500 Subject: [PATCH 23/38] Requested changes - change package from resources to resource - renamed files accordingly - changed from if statement to switch statement. - deleted empty README.md --- cmd/src/scout.go | 2 +- .../{scout_resources.go => scout_resource.go} | 22 +++++++-------- internal/scout/README.md | 0 .../resources.go => resource/resource.go} | 27 +++++++------------ .../resource_test.go} | 2 +- 5 files changed, 23 insertions(+), 30 deletions(-) rename cmd/src/{scout_resources.go => scout_resource.go} (77%) delete mode 100644 internal/scout/README.md rename internal/scout/{resources/resources.go => resource/resource.go} (89%) rename internal/scout/{resources/resources_test.go => resource/resource_test.go} (99%) diff --git a/cmd/src/scout.go b/cmd/src/scout.go index f7e82330ad..33dfaa400c 100644 --- a/cmd/src/scout.go +++ b/cmd/src/scout.go @@ -20,7 +20,7 @@ func init() { The commands are: resources print all known sourcegraph resources - estimate (coming soon) reccommend resource allocation for one or many services + estimate (coming soon) recommend resource allocation for one or many services usage (coming soon) get CPU, memory and current disk usage spy (coming soon) track resource usage in real time diff --git a/cmd/src/scout_resources.go b/cmd/src/scout_resource.go similarity index 77% rename from cmd/src/scout_resources.go rename to cmd/src/scout_resource.go index c350f144f0..af003b7ca4 100644 --- a/cmd/src/scout_resources.go +++ b/cmd/src/scout_resource.go @@ -12,25 +12,25 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "github.com/sourcegraph/src-cli/internal/scout/resources" + "github.com/sourcegraph/src-cli/internal/scout/resource" ) func init() { - usage := `'src scout resources' is a tool that provides an overview of resource usage + usage := `'src scout resource' is a tool that provides an overview of resource usage across an instance of Sourcegraph. Part of the EXPERIMENTAL "src scout" tool. Examples List pods and resource allocations in a Kubernetes deployment: - $ src scout resources + $ src scout resource List containers and resource allocations in a Docker deployment: - $ src scout resources --docker + $ src scout resource --docker Add namespace if using namespace in a Kubernetes cluster - $ src scout resources --namespace sg + $ src scout resource --namespace sg ` - flagSet := flag.NewFlagSet("resources", flag.ExitOnError) + flagSet := flag.NewFlagSet("resource", flag.ExitOnError) usageFunc := func() { fmt.Fprintf(flag.CommandLine.Output(), "Usage of 'src scout %s':\n", flagSet.Name()) flagSet.PrintDefaults() @@ -70,22 +70,22 @@ func init() { return errors.New(fmt.Sprintf("%v: failed to load kubernetes config", err)) } - var options []resources.Option + var options []resource.Option if *namespace != "" { - options = append(options, resources.WithNamespace(*namespace)) + options = append(options, resource.WithNamespace(*namespace)) } if *docker { dockerClient, err := client.NewClientWithOpts(client.FromEnv) if err != nil { - return errors.Wrap(err, "Error creating docker client: ") + return errors.Wrap(err, "error creating docker client: ") } - return resources.Docker(context.Background(), dockerClient) + return resource.Docker(context.Background(), dockerClient) } - return resources.K8s(context.Background(), clientSet, config, options...) + return resource.K8s(context.Background(), clientSet, config, options...) } scoutCommands = append(scoutCommands, &command{ diff --git a/internal/scout/README.md b/internal/scout/README.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/internal/scout/resources/resources.go b/internal/scout/resource/resource.go similarity index 89% rename from internal/scout/resources/resources.go rename to internal/scout/resource/resource.go index 11b0a9c723..9e3faad68f 100644 --- a/internal/scout/resources/resources.go +++ b/internal/scout/resource/resource.go @@ -1,4 +1,4 @@ -package resources +package resource import ( "context" @@ -30,7 +30,7 @@ func WithNamespace(namespace string) Option { } } -// ResourcesK8s prints the CPU and memory resource limits and requests for all pods in the given namespace. +// K8s prints the CPU and memory resource limits and requests for all pods in the given namespace. func K8s(ctx context.Context, clientSet *kubernetes.Clientset, restConfig *rest.Config, opts ...Option) error { cfg := &Config{ namespace: "default", @@ -91,7 +91,6 @@ func listPodResources(ctx context.Context, cfg *Config) error { } func getPVCCapacity(ctx context.Context, cfg *Config, container v1.Container, pod v1.Pod) (string, error) { - var capacity string for _, volumeMount := range container.VolumeMounts { for _, volume := range pod.Spec.Volumes { if volume.Name == volumeMount.Name && volume.PersistentVolumeClaim != nil { @@ -100,21 +99,14 @@ func getPVCCapacity(ctx context.Context, cfg *Config, container v1.Container, po volume.PersistentVolumeClaim.ClaimName, metav1.GetOptions{}, ) - if err != nil { - return "", errors.Wrapf( - err, - "error getting PVC %s", - volume.PersistentVolumeClaim.ClaimName, - ) + return "", errors.Wrapf(err, "error getting PVC %s", volume.PersistentVolumeClaim.ClaimName) } - - capacity = pvc.Status.Capacity.Storage().String() - break + return pvc.Status.Capacity.Storage().String(), nil } } } - return capacity, nil + return "", nil } // DockerClientInterface defines the interface for interacting with the Docker API. @@ -124,7 +116,7 @@ type DockerClientInterface interface { Close() error } -// ResourcesDocker prints the CPU and memory resource limits and requests for running Docker containers. +// Docker prints the CPU and memory resource limits and requests for running Docker containers. func Docker(ctx context.Context, dockerClient DockerClientInterface) error { containers, err := dockerClient.ContainerList(ctx, types.ContainerListOptions{}) if err != nil { @@ -180,12 +172,13 @@ func getMemUnits(valToConvert int64) (string, int64, error) { } var memUnit string - if valToConvert < 1000000 { + switch { + case valToConvert < 1000000: memUnit = "KB" - } else if valToConvert < 1000000000 { + case valToConvert < 1000000000: memUnit = "MB" valToConvert = valToConvert / 1000000 - } else { + default: memUnit = "GB" valToConvert = valToConvert / 1000000000 } diff --git a/internal/scout/resources/resources_test.go b/internal/scout/resource/resource_test.go similarity index 99% rename from internal/scout/resources/resources_test.go rename to internal/scout/resource/resource_test.go index 373023f789..22ed233e7f 100644 --- a/internal/scout/resources/resources_test.go +++ b/internal/scout/resource/resource_test.go @@ -1,4 +1,4 @@ -package resources +package resource import ( "context" From 79eec1a2c77452a67f7ef58503466cdf894231e5 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Thu, 11 May 2023 16:09:45 -0500 Subject: [PATCH 24/38] test skeleton --- internal/scout/resource/resource.go | 61 +++++++++++------------- internal/scout/resource/resource_test.go | 44 +++++++++++++++++ 2 files changed, 73 insertions(+), 32 deletions(-) diff --git a/internal/scout/resource/resource.go b/internal/scout/resource/resource.go index 9e3faad68f..d6a1abe56e 100644 --- a/internal/scout/resource/resource.go +++ b/internal/scout/resource/resource.go @@ -109,15 +109,8 @@ func getPVCCapacity(ctx context.Context, cfg *Config, container v1.Container, po return "", nil } -// DockerClientInterface defines the interface for interacting with the Docker API. -type DockerClientInterface interface { - ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) - ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) - Close() error -} - // Docker prints the CPU and memory resource limits and requests for running Docker containers. -func Docker(ctx context.Context, dockerClient DockerClientInterface) error { +func Docker(ctx context.Context, dockerClient client.Client) error { containers, err := dockerClient.ContainerList(ctx, types.ContainerListOptions{}) if err != nil { return fmt.Errorf("error listing docker containers: %v", err) @@ -136,30 +129,7 @@ func Docker(ctx context.Context, dockerClient DockerClientInterface) error { return fmt.Errorf("error inspecting container %s: %v", container.ID, err) } - cpuCores := containerInfo.HostConfig.NanoCPUs - cpuShares := containerInfo.HostConfig.CPUShares - memLimits := containerInfo.HostConfig.Memory - memReservations := containerInfo.HostConfig.MemoryReservation - - limUnit, limVal, err := getMemUnits(memLimits) - if err != nil { - return err - } - - reqUnit, reqVal, err := getMemUnits(memReservations) - if err != nil { - return err - } - - fmt.Fprintf( - w, - "%s\t%d\t%v\t%v\t%v\n", - containerInfo.Name, - cpuCores/1e9, - cpuShares, - fmt.Sprintf("%d %s", limVal, limUnit), - fmt.Sprintf("%d %s", reqVal, reqUnit), - ) + getResourceInfo(&containerInfo, w) } return nil @@ -185,3 +155,30 @@ func getMemUnits(valToConvert int64) (string, int64, error) { return memUnit, valToConvert, nil } + +func getResourceInfo(container *types.ContainerJSON, w *tabwriter.Writer) error { + cpuCores := container.HostConfig.NanoCPUs + cpuShares := container.HostConfig.CPUShares + memLimits := container.HostConfig.Memory + memReservations := container.HostConfig.MemoryReservation + + limUnit, limVal, err := getMemUnits(memLimits) + if err != nil { + return errors.Wrap(err, "error while getting limit units") + } + + reqUnit, reqVal, err := getMemUnits(memReservations) + if err != nil { + return errors.Wrap(err, "error while getting request units") + } + + fmt.Fprintf( + w, + "%s\t%d\t%v\t%v\t%v\n", + container.Name, + cpuCores/1e9, + cpuShares, + fmt.Sprintf("%d %s", limVal, limUnit), + fmt.Sprintf("%d %s", reqVal, reqUnit), + ) +} diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index 22ed233e7f..30a925b786 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -166,3 +166,47 @@ func TestResourcesDocker(t *testing.T) { dockerClient.AssertExpectations(t) } + +func containerHelper() *types.ContainerJSONBase { + return &types.ContainerJSONBase{ + Name: "", + HostConfig: &container.HostConfig{ + Resources: container.Resources{ + NanoCPUs: 0, + CPUShares: 0, + Memory: 0, + MemoryReservation: 0, + }, + }, + } +} + +func TestGetResourceInfo(t *testing.T) { + cases := []struct { + name string + container func(*types.ContainerJSONBase) + }{ + { + name: "bad format: bad input", + container: func(container *types.ContainerJSONBase) { + container.Name = "container1" + container.HostConfig.Resources.NanoCPUs = 2000000000 + container.HostConfig.Resources.CPUShares = 512 + container.HostConfig.Resources.Memory = 1536870912 + container.HostConfig.Resources.MemoryReservation = 268435456 + }, + }, + } + + for _, tc := range cases { + tc := tc + t.Run(tc.name, func(t *testing.T) { + container := containerHelper() + if tc.container != nil { + tc.container(container) + } + }) + // TODO: logic for testing + // call function then validate the output + } +} From c11677cc8e4ff136b3de2b71ef6026eb23687db6 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Fri, 12 May 2023 12:36:45 -0500 Subject: [PATCH 25/38] test for getMemUnits added --- internal/scout/resource/resource.go | 47 ++--- internal/scout/resource/resource_test.go | 208 +++++++++-------------- 2 files changed, 108 insertions(+), 147 deletions(-) diff --git a/internal/scout/resource/resource.go b/internal/scout/resource/resource.go index d6a1abe56e..5051540451 100644 --- a/internal/scout/resource/resource.go +++ b/internal/scout/resource/resource.go @@ -129,7 +129,7 @@ func Docker(ctx context.Context, dockerClient client.Client) error { return fmt.Errorf("error inspecting container %s: %v", container.ID, err) } - getResourceInfo(&containerInfo, w) + getResourceInfo(&containerInfo, w) } return nil @@ -157,28 +157,29 @@ func getMemUnits(valToConvert int64) (string, int64, error) { } func getResourceInfo(container *types.ContainerJSON, w *tabwriter.Writer) error { - cpuCores := container.HostConfig.NanoCPUs - cpuShares := container.HostConfig.CPUShares - memLimits := container.HostConfig.Memory - memReservations := container.HostConfig.MemoryReservation + cpuCores := container.HostConfig.NanoCPUs + cpuShares := container.HostConfig.CPUShares + memLimits := container.HostConfig.Memory + memReservations := container.HostConfig.MemoryReservation - limUnit, limVal, err := getMemUnits(memLimits) - if err != nil { - return errors.Wrap(err, "error while getting limit units") - } + limUnit, limVal, err := getMemUnits(memLimits) + if err != nil { + return errors.Wrap(err, "error while getting limit units") + } - reqUnit, reqVal, err := getMemUnits(memReservations) - if err != nil { - return errors.Wrap(err, "error while getting request units") - } + reqUnit, reqVal, err := getMemUnits(memReservations) + if err != nil { + return errors.Wrap(err, "error while getting request units") + } - fmt.Fprintf( - w, - "%s\t%d\t%v\t%v\t%v\n", - container.Name, - cpuCores/1e9, - cpuShares, - fmt.Sprintf("%d %s", limVal, limUnit), - fmt.Sprintf("%d %s", reqVal, reqUnit), - ) -} + fmt.Fprintf( + w, + "%s\t%d\t%v\t%v\t%v\n", + container.Name, + cpuCores/1e9, + cpuShares, + fmt.Sprintf("%d %s", limVal, limUnit), + fmt.Sprintf("%d %s", reqVal, reqUnit), + ) + return nil +} diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index 30a925b786..dac9661bf0 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -3,16 +3,18 @@ package resource import ( "context" "fmt" + + /* "fmt" "io" "os" - "strings" + "strings" */ "testing" - "text/tabwriter" + // "text/tabwriter" - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" + // "github.com/docker/docker/api/types" + // "github.com/docker/docker/api/types/container" "github.com/sourcegraph/sourcegraph/lib/errors" - "github.com/stretchr/testify/mock" + // "github.com/stretchr/testify/mock" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -57,114 +59,37 @@ func TestResourcesK8s(t *testing.T) { } } -type DockerClientMock struct { - mock.Mock -} - -func (m *DockerClientMock) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) { - args := m.Called(ctx, options) - return args.Get(0).([]types.Container), args.Error(1) -} - -func (m *DockerClientMock) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) { - args := m.Called(ctx, containerID) - return args.Get(0).(types.ContainerJSON), args.Error(1) -} - -func (m *DockerClientMock) Close() error { - args := m.Called() - return args.Error(0) -} - -func TestResourcesDocker(t *testing.T) { - dockerClient := new(DockerClientMock) - - dockerClient.On("ContainerList", mock.Anything, mock.Anything).Return([]types.Container{ - {ID: "container1"}, - {ID: "container2"}, - {ID: "container3"}, - }, nil) - - dockerClient.On("ContainerInspect", mock.Anything, "container1").Return(types.ContainerJSON{ - ContainerJSONBase: &types.ContainerJSONBase{ - Name: "container1", - HostConfig: &container.HostConfig{ - Resources: container.Resources{ - NanoCPUs: 2000000000, - CPUShares: 512, - Memory: 1536870912, - MemoryReservation: 268435456, - }, - }, - }, - }, nil) - - dockerClient.On("ContainerInspect", mock.Anything, "container2").Return(types.ContainerJSON{ - ContainerJSONBase: &types.ContainerJSONBase{ - Name: "container2", - HostConfig: &container.HostConfig{ - Resources: container.Resources{ - NanoCPUs: 1000000000, - CPUShares: 1024, - Memory: 268435456, - MemoryReservation: 134217728, - }, - }, - }, - }, nil) - - dockerClient.On("ContainerInspect", mock.Anything, "container3").Return(types.ContainerJSON{ - ContainerJSONBase: &types.ContainerJSONBase{ - Name: "container3", - HostConfig: &container.HostConfig{ - Resources: container.Resources{ - NanoCPUs: 4000000000, - CPUShares: 2048, - Memory: 5268435456, - MemoryReservation: 4134217728, - }, +/* func TestGetResourceInfo(t *testing.T) { + cases := []struct { + name string + container func(*types.ContainerJSONBase) + expected string + }{ + { + name: "bad format: bad input", + container: func(container *types.ContainerJSONBase) { + container.Name = "container1" + container.HostConfig.Resources.NanoCPUs = 2000000000 + container.HostConfig.Resources.CPUShares = 512 + container.HostConfig.Resources.Memory = 1536870912 + container.HostConfig.Resources.MemoryReservation = 268435456 }, + expected: "", }, - }, nil) - - dockerClient.On("Close").Return(nil) - - var expectedOutput strings.Builder - expectedW := tabwriter.NewWriter(&expectedOutput, 0, 0, 2, ' ', 0) - - fmt.Fprintln(expectedW, "Container\tCPU Cores\tCPU Shares\tMem Limits\tMem Reservations") - fmt.Fprintf(expectedW, "container1\t2\t512\t1 GB\t268 MB\n") - fmt.Fprintf(expectedW, "container2\t1\t1024\t268 MB\t134 MB\n") - fmt.Fprintf(expectedW, "container3\t4\t2048\t5 GB\t4 GB\n") - expectedW.Flush() - - oldStdout := os.Stdout - r, w, _ := os.Pipe() - os.Stdout = w - - err := Docker(context.Background(), dockerClient) - if err != nil { - t.Fatalf("ResourcesDocker returned an error: %v", err) } - err = dockerClient.Close() - if err != nil { - t.Fatalf("Error closing docker client: %v", err) - } - - w.Close() - os.Stdout = oldStdout - - output, err := io.ReadAll(r) - if err != nil { - t.Fatalf("Error reading from pipe: %v", err) - } - - if string(output) != expectedOutput.String() { - t.Errorf("Expected output:\n%s\nActual output:\n%s", expectedOutput.String(), output) + for _, tc := range cases { + // why does this have to be here to work? + tc := tc + t.Run(tc.name, func(t *testing.T) { + container := containerHelper() + if tc.container != nil { + tc.container(container) + } + // TODO: logic for testing + // call function then validate the output + }) } - - dockerClient.AssertExpectations(t) } func containerHelper() *types.ContainerJSONBase { @@ -179,34 +104,69 @@ func containerHelper() *types.ContainerJSONBase { }, }, } -} +} */ -func TestGetResourceInfo(t *testing.T) { +func TestGetMemUnits(t *testing.T) { cases := []struct { name string - container func(*types.ContainerJSONBase) + param int64 + wantUnit string + wantValue int64 + wantError error }{ { - name: "bad format: bad input", - container: func(container *types.ContainerJSONBase) { - container.Name = "container1" - container.HostConfig.Resources.NanoCPUs = 2000000000 - container.HostConfig.Resources.CPUShares = 512 - container.HostConfig.Resources.Memory = 1536870912 - container.HostConfig.Resources.MemoryReservation = 268435456 - }, + name: "convert bytes below a million to KB", + param: 999999, + wantUnit: "KB", + wantValue: 999999, + wantError: nil, + }, + { + name: "convert bytes below a billion to MB", + param: 999999999, + wantUnit: "MB", + wantValue: 999, + wantError: nil, + }, + { + name: "convert bytes above a billion to GB", + param: 12999999900, + wantUnit: "GB", + wantValue: 12, + wantError: nil, + }, + { + name: "return error for a negative number", + param: -300, + wantUnit: "", + wantValue: -300, + wantError: fmt.Errorf("invalid memory value: %d", -300), }, } for _, tc := range cases { tc := tc t.Run(tc.name, func(t *testing.T) { - container := containerHelper() - if tc.container != nil { - tc.container(container) + gotUnit, gotValue, gotError := getMemUnits(tc.param) + + if gotUnit != tc.wantUnit { + t.Errorf("got %s want %s", gotUnit, tc.wantUnit) + } + + if gotValue != tc.wantValue { + t.Errorf("got %v want %v", gotValue, tc.wantValue) } + + if gotError == nil && tc.wantError != nil { + t.Error("got nil want error") + } + + if gotError != nil && tc.wantError == nil { + t.Error("got error want nil") + } + + return }) - // TODO: logic for testing - // call function then validate the output } + } From 29225dfebd771f0e11a9323a3417520ed7403be2 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Fri, 12 May 2023 12:38:42 -0500 Subject: [PATCH 26/38] replace 'spy' with 'advise' in command instructions --- cmd/src/scout.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/src/scout.go b/cmd/src/scout.go index 33dfaa400c..b9dfa6edec 100644 --- a/cmd/src/scout.go +++ b/cmd/src/scout.go @@ -22,7 +22,7 @@ func init() { resources print all known sourcegraph resources estimate (coming soon) recommend resource allocation for one or many services usage (coming soon) get CPU, memory and current disk usage - spy (coming soon) track resource usage in real time + advise (coming soon) recommend lowering or raising resource allocations based on actual usage Use "src scout [command] -h" for more information about a command. ` From 8dc2babaf4c14f55b036db157852c248d92a9d26 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 15 May 2023 09:20:40 -0500 Subject: [PATCH 27/38] Add bubbles/bubble tea components to CLI for better UI/UX --- cmd/src/scout_resource.go | 2 +- go.mod | 19 +++- go.sum | 55 ++++++++- internal/scout/resource/resource.go | 135 +++++++++++++++-------- internal/scout/resource/resource_test.go | 1 - internal/scout/resource/style/Table.go | 135 +++++++++++++++++++++++ 6 files changed, 292 insertions(+), 55 deletions(-) create mode 100644 internal/scout/resource/style/Table.go diff --git a/cmd/src/scout_resource.go b/cmd/src/scout_resource.go index af003b7ca4..32accda5f6 100644 --- a/cmd/src/scout_resource.go +++ b/cmd/src/scout_resource.go @@ -82,7 +82,7 @@ func init() { return errors.Wrap(err, "error creating docker client: ") } - return resource.Docker(context.Background(), dockerClient) + return resource.Docker(context.Background(), *dockerClient) } return resource.K8s(context.Background(), clientSet, config, options...) diff --git a/go.mod b/go.mod index 6224d854e7..c232d6109d 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,8 @@ require ( github.com/aws/aws-sdk-go-v2/service/ec2 v1.86.0 github.com/aws/aws-sdk-go-v2/service/eks v1.27.3 github.com/aws/aws-sdk-go-v2/service/iam v1.19.3 + github.com/charmbracelet/bubbles v0.15.0 + github.com/charmbracelet/lipgloss v0.7.1 github.com/creack/goselect v0.1.2 github.com/derision-test/glock v0.0.0-20210316032053-f5b74334bb29 github.com/dineshappavoo/basex v0.0.0-20170425072625-481a6f6dc663 @@ -21,7 +23,7 @@ require ( github.com/jig/teereadcloser v0.0.0-20181016160506-953720c48e05 github.com/json-iterator/go v1.1.12 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 - github.com/mattn/go-isatty v0.0.16 + github.com/mattn/go-isatty v0.0.17 github.com/neelance/parallel v0.0.0-20160708114440-4de9ce63d14c github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/sourcegraph/conc v0.3.0 @@ -30,6 +32,7 @@ require ( github.com/sourcegraph/scip v0.2.4-0.20221213205653-aa0e511dcfef github.com/sourcegraph/sourcegraph/lib v0.0.0-20230316093010-26299ec302d0 github.com/stretchr/testify v1.8.1 + golang.design/x/clipboard v0.7.0 golang.org/x/net v0.8.0 golang.org/x/sync v0.1.0 google.golang.org/api v0.110.0 @@ -41,6 +44,12 @@ require ( k8s.io/client-go v0.26.1 ) +require ( + golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 // indirect + golang.org/x/image v0.6.0 // indirect + golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c // indirect +) + require ( cloud.google.com/go v0.107.0 // indirect cloud.google.com/go/compute v1.18.0 // indirect @@ -63,12 +72,15 @@ require ( github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.2 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 // indirect github.com/aws/smithy-go v1.13.5 // indirect + github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/bufbuild/buf v1.4.0 // indirect + github.com/charmbracelet/bubbletea v0.23.1 // direct github.com/charmbracelet/glamour v0.5.0 // indirect github.com/cockroachdb/errors v1.9.0 // indirect github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f // indirect github.com/cockroachdb/redact v1.1.3 // indirect + github.com/containerd/console v1.0.3 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/dlclark/regexp2 v1.7.0 // indirect @@ -112,6 +124,7 @@ require ( github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-localereader v0.0.1 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/microcosm-cc/bluemonday v1.0.22 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -121,8 +134,10 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b // indirect + github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/reflow v0.3.0 // indirect - github.com/muesli/termenv v0.12.0 // indirect + github.com/muesli/termenv v0.15.1 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mwitkow/go-proto-validators v0.3.2 // indirect github.com/nightlyone/lockfile v1.0.0 // indirect diff --git a/go.sum b/go.sum index 4d32c74809..4c9e9d0f55 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,7 @@ github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOv github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/CloudyKit/fastprinter v0.0.0-20170127035650-74b38d55f37a/go.mod h1:EFZQ978U7x8IRnstaskI3IysnWY5Ao3QgZUKOXlsAdw= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet v2.1.3-0.20180809161101-62edd43e4f88+incompatible/go.mod h1:HPYO+50pSWkPoj9Q/eq0aRGByCL6ScRlUmiEX5Zgm+w= @@ -34,6 +35,7 @@ github.com/alecthomas/chroma v0.10.0 h1:7XDcGkCQopCNKjZHfYrNLraA+M7e0fMiJ/Mfikbf github.com/alecthomas/chroma v0.10.0/go.mod h1:jtJATyUxlIORhUOFNA9NZDWGAQ8wpxQQqNSB4rjA/1s= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aws/aws-sdk-go-v2 v1.17.4/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= github.com/aws/aws-sdk-go-v2 v1.17.5 h1:TzCUW1Nq4H8Xscph5M/skINUitxM5UBAyvm2s7XBzL4= github.com/aws/aws-sdk-go-v2 v1.17.5/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw= @@ -67,6 +69,9 @@ github.com/aws/aws-sdk-go-v2/service/sts v1.18.3 h1:s49mSnsBZEXjfGBkRfmK+nPqzT7L github.com/aws/aws-sdk-go-v2/service/sts v1.18.3/go.mod h1:b+psTJn33Q4qGoDaM7ZiOVVG8uVjGI6HaZ8WBHdgDgU= github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8= github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= +github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4= +github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= +github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= @@ -74,8 +79,16 @@ github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT github.com/bufbuild/buf v1.4.0 h1:GqE3a8CMmcFvWPzuY3Mahf9Kf3S9XgZ/ORpfYFzO+90= github.com/bufbuild/buf v1.4.0/go.mod h1:mwHG7klTHnX+rM/ym8LXGl7vYpVmnwT96xWoRB4H5QI= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/charmbracelet/bubbles v0.15.0 h1:c5vZ3woHV5W2b8YZI1q7v4ZNQaPetfHuoHzx+56Z6TI= +github.com/charmbracelet/bubbles v0.15.0/go.mod h1:Y7gSFbBzlMpUDR/XM9MhZI374Q+1p1kluf1uLl8iK74= +github.com/charmbracelet/bubbletea v0.23.1 h1:CYdteX1wCiCzKNUlwm25ZHBIc1GXlYFyUIte8WPvhck= +github.com/charmbracelet/bubbletea v0.23.1/go.mod h1:JAfGK/3/pPKHTnAS8JIE2u9f61BjWTQY57RbT25aMXU= github.com/charmbracelet/glamour v0.5.0 h1:wu15ykPdB7X6chxugG/NNfDUbyyrCLV9XBalj5wdu3g= github.com/charmbracelet/glamour v0.5.0/go.mod h1:9ZRtG19AUIzcTm7FGLGbq3D5WKQ5UyZBbQsMQN0XIqc= +github.com/charmbracelet/harmonica v0.2.0/go.mod h1:KSri/1RMQOZLbw7AHqgcBycp8pgJnQMYYT8QZRqZ1Ao= +github.com/charmbracelet/lipgloss v0.6.0/go.mod h1:tHh2wr34xcHjC2HCXIlGSG1jaDF0S0atAUvBMP6Ppuk= +github.com/charmbracelet/lipgloss v0.7.1 h1:17WMwi7N1b1rVWOjMT+rCh7sQkvDU75B2hbZpc5Kc1E= +github.com/charmbracelet/lipgloss v0.7.1/go.mod h1:yG0k3giv8Qj8edTCbbg6AlQ5e8KNWpFujkNawKNhE2c= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -92,6 +105,8 @@ github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5w github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= github.com/cockroachdb/sentry-go v0.6.1-cockroachdb.2/go.mod h1:8BT+cPK6xvFOcRlk0R8eg+OTkcqI6baNH4xAkpiYVvQ= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw= +github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -332,6 +347,7 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g= github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= @@ -355,9 +371,13 @@ github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2y github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.13/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= +github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= +github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= 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= @@ -390,11 +410,18 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34= +github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho= +github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= +github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= +github.com/muesli/reflow v0.2.1-0.20210115123740-9e1d0d53df68/go.mod h1:Xk+z4oIWdQqJzsxyjgl3P22oYZnHdZ8FFTHAQQt5BMQ= github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw= -github.com/muesli/termenv v0.12.0 h1:KuQRUE3PgxRFWhq4gHvZtPSLCGDqM5q/cYr1pZ39ytc= -github.com/muesli/termenv v0.12.0/go.mod h1:WCCv32tusQ/EEZ5S8oUIIrC/nIuBcxCVqlN4Xfkv+7A= +github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs= +github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc= +github.com/muesli/termenv v0.15.1 h1:UzuTb/+hhlBugQz28rpzey4ZuKcZ03MeKsoG7IJZIxs= +github.com/muesli/termenv v0.15.1/go.mod h1:HeAQPTzpfs016yGtA4g00CsdYnVLJvxsS4ANqrZs2sQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/mwitkow/go-proto-validators v0.3.2 h1:qRlmpTzm2pstMKKzTdvwPCF5QfBNURSlAgN/R+qbKos= @@ -457,6 +484,7 @@ github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sahilm/fuzzy v0.1.0/go.mod h1:VFvziUEIMCrT6A6tw2RFIXPXXmzXbOsSHF0DOI8ZK9Y= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZKJ48R1S7H23Ji7oFO5Bw= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= @@ -549,8 +577,11 @@ go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= +golang.design/x/clipboard v0.7.0 h1:4Je8M/ys9AJumVnl8m+rZnIvstSnYj1fvzqYrU3TXvo= +golang.design/x/clipboard v0.7.0/go.mod h1:PQIvqYO9GP29yINEfsEn5zSQKAz3UgXmZKzDA6dnq2E= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -560,10 +591,19 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56 h1:estk1glOnSVeJ9tdEZZc5mAMDZk5lNJNyJ6DvrBkTEU= +golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.6.0 h1:bR8b5okrPI3g/gyZakLZHeWxAR8Dn5CyxXv1hLH5g/4= +golang.org/x/image v0.6.0/go.mod h1:MXLdDR43H7cDJq5GEGXEVeeNhPgi+YYEQ2pC1byI1x0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c h1:Gk61ECugwEHL6IiyyNLXNzmu8XslmRP2dS0xjIYhbb4= +golang.org/x/mobile v0.0.0-20230301163155-e0f57694e12c/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -572,6 +612,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/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.7.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.9.0 h1:KENHtAZL2y3NLMYZeHY9DW8HW8V+kQyJsY/V9JlKvCs= golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -598,6 +639,7 @@ golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.3.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -632,6 +674,7 @@ golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210218084038-e8e29180ff58/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -643,17 +686,20 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/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-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220829200755-d48e67d00261/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -663,6 +709,7 @@ golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -674,6 +721,7 @@ golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGm golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -688,6 +736,7 @@ golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.4.0/go.mod h1:UE5sM2OK9E/d67R0ANs2xJizIymRP5gJU295PvKXxjQ= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.7.0 h1:W4OVu8VVOaIO0yzWMNdepAulS7YfoS3Zabrm8DOXXU4= golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/scout/resource/resource.go b/internal/scout/resource/resource.go index 5051540451..4a63ef9374 100644 --- a/internal/scout/resource/resource.go +++ b/internal/scout/resource/resource.go @@ -4,11 +4,13 @@ import ( "context" "fmt" "os" - "text/tabwriter" + "github.com/charmbracelet/bubbles/table" + "github.com/charmbracelet/lipgloss" "github.com/docker/docker/api/types" "github.com/docker/docker/client" "github.com/sourcegraph/sourcegraph/lib/errors" + "github.com/sourcegraph/src-cli/internal/scout/resource/style" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -53,13 +55,30 @@ func listPodResources(ctx context.Context, cfg *Config) error { return errors.Wrap(err, "error listing pods: ") } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - defer func() { - _ = w.Flush() - }() + columns := []table.Column{ + {Title: "CONTAINER", Width: 20}, + {Title: "CPU LIMITS", Width: 10}, + {Title: "CPU REQUESTS", Width: 12}, + {Title: "MEM LIMITS", Width: 10}, + {Title: "MEM REQUESTS", Width: 12}, + {Title: "CAPACITY", Width: 8}, + } - fmt.Fprintln(w, "CONTAINER\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY") + if len(podList.Items) == 0 { + msg := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500")) + fmt.Println(msg.Render(` + No pods exist in this namespace. + Did you mean to use the --namespace flag? + + If you are attemptying to check + resources for a docker deployment, you + must use the --docker flag. + See --help for more info. + `)) + os.Exit(1) + } + var rows []table.Row for _, pod := range podList.Items { if pod.GetNamespace() == cfg.namespace { for _, container := range pod.Spec.Containers { @@ -73,20 +92,24 @@ func listPodResources(ctx context.Context, cfg *Config) error { return err } - fmt.Fprintf( - w, - "%s\t%s\t%s\t%s\t%s\t%s\t\n", + if capacity == "" { + capacity = "N/A" + } + + row := table.Row{ container.Name, - cpuLimits, - cpuRequests, - memLimits, - memRequests, + cpuLimits.String(), + cpuRequests.String(), + memLimits.String(), + memRequests.String(), capacity, - ) + } + rows = append(rows, row) } } } + style.ResourceTable(columns, rows) return nil } @@ -116,12 +139,23 @@ func Docker(ctx context.Context, dockerClient client.Client) error { return fmt.Errorf("error listing docker containers: %v", err) } - w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) - defer func() { - _ = w.Flush() - }() + if len(containers) == 0 { + msg := lipgloss.NewStyle().Foreground(lipgloss.Color("#FFA500")) + fmt.Println(msg.Render(` + There are no containers, or the Docker Daemon is not running + `)) + os.Exit(1) + } + + columns := []table.Column{ + {Title: "Container", Width: 20}, + {Title: "CPU Cores", Width: 15}, + {Title: "CPU Shares", Width: 15}, + {Title: "Mem Limits", Width: 15}, + {Title: "Mem Reservations", Width: 17}, + } - fmt.Fprintln(w, "Container\tCPU Cores\tCPU Shares\tMem Limits\tMem Reservations") + var rows []table.Row for _, container := range containers { containerInfo, err := dockerClient.ContainerInspect(ctx, container.ID) @@ -129,12 +163,45 @@ func Docker(ctx context.Context, dockerClient client.Client) error { return fmt.Errorf("error inspecting container %s: %v", container.ID, err) } - getResourceInfo(&containerInfo, w) + row, err := getResourceInfo(&containerInfo, rows) + if err != nil { + return errors.Wrap(err, "error while getting resource info from container: ") + } + + rows = append(rows, row) } + style.ResourceTable(columns, rows) return nil } +func getResourceInfo(container *types.ContainerJSON, rows []table.Row) (table.Row, error) { + cpuCores := container.HostConfig.NanoCPUs + cpuShares := container.HostConfig.CPUShares + memLimits := container.HostConfig.Memory + memReservations := container.HostConfig.MemoryReservation + + reqUnit, reqVal, err := getMemUnits(memReservations) + if err != nil { + return table.Row{}, errors.Wrap(err, "error while getting request units") + } + + limUnit, limVal, err := getMemUnits(memLimits) + if err != nil { + return table.Row{}, errors.Wrap(err, "error while getting limit units") + } + + row := table.Row{ + container.Name, + fmt.Sprintf("%v", float64(cpuCores/1e9)), + fmt.Sprint(cpuShares), + fmt.Sprintf("%d%s", limVal, limUnit), + fmt.Sprintf("%d%s", reqVal, reqUnit), + } + + return row, nil +} + // getMemUnits converts a byte value to the appropriate memory unit. func getMemUnits(valToConvert int64) (string, int64, error) { if valToConvert < 0 { @@ -155,31 +222,3 @@ func getMemUnits(valToConvert int64) (string, int64, error) { return memUnit, valToConvert, nil } - -func getResourceInfo(container *types.ContainerJSON, w *tabwriter.Writer) error { - cpuCores := container.HostConfig.NanoCPUs - cpuShares := container.HostConfig.CPUShares - memLimits := container.HostConfig.Memory - memReservations := container.HostConfig.MemoryReservation - - limUnit, limVal, err := getMemUnits(memLimits) - if err != nil { - return errors.Wrap(err, "error while getting limit units") - } - - reqUnit, reqVal, err := getMemUnits(memReservations) - if err != nil { - return errors.Wrap(err, "error while getting request units") - } - - fmt.Fprintf( - w, - "%s\t%d\t%v\t%v\t%v\n", - container.Name, - cpuCores/1e9, - cpuShares, - fmt.Sprintf("%d %s", limVal, limUnit), - fmt.Sprintf("%d %s", reqVal, reqUnit), - ) - return nil -} diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index dac9661bf0..5f7086c00e 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -168,5 +168,4 @@ func TestGetMemUnits(t *testing.T) { return }) } - } diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go new file mode 100644 index 0000000000..28464b125c --- /dev/null +++ b/internal/scout/resource/style/Table.go @@ -0,0 +1,135 @@ +package style + +import ( + "fmt" + + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "golang.design/x/clipboard" + "os" +) + +var baseStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")) + +type model struct { + table table.Model +} + +func (m model) Init() tea.Cmd { return nil } + +func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + switch msg.String() { + case "esc", "q", "ctrl+c": + return m, tea.Quit + case "c": + m.copyRowToClipboard(m.table.SelectedRow()) + copiedMessage := lipgloss.NewStyle(). + Foreground(lipgloss.Color("#32CD32")). + Render(fmt.Sprintf( + "Copied resource allocations for %s to clipboard", + m.table.SelectedRow()[0], + )) + return m, tea.Batch( + tea.Printf( + copiedMessage, + ), + ) + case "C": + return m, tea.Batch( + tea.Printf("pressing C has correct action"), + ) + } + } + m.table, cmd = m.table.Update(msg) + return m, cmd +} + +func (m model) View() string { + s := "\n > Press 'j' and 'k' to go up and down\n" + s += " > Press 'c' to copy highlighted row to clipboard\n" + // s += " > Press 'C' to copy all rows to clipboard\n" + s += " > Press 'q' to quit\n\n" + s += baseStyle.Render(m.table.View()) + "\n" + return s +} + +func (m model) copyRowToClipboard(row table.Row) { + var containerInfo string + + // change output based on the length of row + // docker rows will always be length of 5 + // kubernetes rows will always be length of 6 + if len(row) == 5 { + name := row[0] + cpuCores := row[1] + cpuShares := row[2] + memLimits := row[3] + memReservations := row[4] + containerInfo = fmt.Sprintf(`container: %s + cpu cores: %s + cpu shares: %s + mem limits: %s + mem reservations: %s`, + name, + cpuCores, + cpuShares, + memLimits, + memReservations, + ) + } else if len(row) == 6 { + name := row[0] + cpuLimits := row[1] + cpuRequests := row[2] + memLimits := row[3] + memRequests := row[4] + capacity := row[5] + containerInfo = fmt.Sprintf(`container: %s + cpu limits: %s + cpu requests: %s + mem limits: %s + mem requests: %s + disk capacity: %s`, + name, + cpuLimits, + cpuRequests, + memLimits, + memRequests, + capacity, + ) + } + + clipboard.Write(clipboard.FmtText, []byte(containerInfo)) +} + +func ResourceTable(columns []table.Column, rows []table.Row) { + t := table.New( + table.WithColumns(columns), + table.WithRows(rows), + table.WithFocused(true), + table.WithHeight(14), + ) + + 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) + + m := model{t} + if _, err := tea.NewProgram(m).Run(); err != nil { + fmt.Println("Error running program:", err) + os.Exit(1) + } +} From d49544b3e3ce02da92a42e8e967588efb4a0e2c9 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 15 May 2023 09:28:01 -0500 Subject: [PATCH 28/38] remove unnecessary tests. Formatting --- internal/scout/resource/resource_test.go | 56 ------------------------ 1 file changed, 56 deletions(-) diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index 5f7086c00e..bbf397c8c3 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -4,17 +4,8 @@ import ( "context" "fmt" - /* "fmt" - "io" - "os" - "strings" */ "testing" - // "text/tabwriter" - - // "github.com/docker/docker/api/types" - // "github.com/docker/docker/api/types/container" "github.com/sourcegraph/sourcegraph/lib/errors" - // "github.com/stretchr/testify/mock" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" @@ -59,53 +50,6 @@ func TestResourcesK8s(t *testing.T) { } } -/* func TestGetResourceInfo(t *testing.T) { - cases := []struct { - name string - container func(*types.ContainerJSONBase) - expected string - }{ - { - name: "bad format: bad input", - container: func(container *types.ContainerJSONBase) { - container.Name = "container1" - container.HostConfig.Resources.NanoCPUs = 2000000000 - container.HostConfig.Resources.CPUShares = 512 - container.HostConfig.Resources.Memory = 1536870912 - container.HostConfig.Resources.MemoryReservation = 268435456 - }, - expected: "", - }, - } - - for _, tc := range cases { - // why does this have to be here to work? - tc := tc - t.Run(tc.name, func(t *testing.T) { - container := containerHelper() - if tc.container != nil { - tc.container(container) - } - // TODO: logic for testing - // call function then validate the output - }) - } -} - -func containerHelper() *types.ContainerJSONBase { - return &types.ContainerJSONBase{ - Name: "", - HostConfig: &container.HostConfig{ - Resources: container.Resources{ - NanoCPUs: 0, - CPUShares: 0, - Memory: 0, - MemoryReservation: 0, - }, - }, - } -} */ - func TestGetMemUnits(t *testing.T) { cases := []struct { name string From 1b4b89d91228f991b02e406b052f7f5fe4abd1ab Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 15 May 2023 09:44:13 -0500 Subject: [PATCH 29/38] formatting and removes placeholder code/comments --- cmd/src/scout.go | 2 +- internal/scout/resource/resource_test.go | 14 +++++++------- internal/scout/resource/style/Table.go | 5 ----- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/cmd/src/scout.go b/cmd/src/scout.go index b9dfa6edec..753012cac8 100644 --- a/cmd/src/scout.go +++ b/cmd/src/scout.go @@ -19,7 +19,7 @@ func init() { The commands are: - resources print all known sourcegraph resources + resource print all known sourcegraph resources and their allocations estimate (coming soon) recommend resource allocation for one or many services usage (coming soon) get CPU, memory and current disk usage advise (coming soon) recommend lowering or raising resource allocations based on actual usage diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index bbf397c8c3..26ab1c7b6b 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -4,13 +4,13 @@ import ( "context" "fmt" - "testing" "github.com/sourcegraph/sourcegraph/lib/errors" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" + "testing" ) func TestResourcesK8s(t *testing.T) { @@ -101,13 +101,13 @@ func TestGetMemUnits(t *testing.T) { t.Errorf("got %v want %v", gotValue, tc.wantValue) } - if gotError == nil && tc.wantError != nil { - t.Error("got nil want error") - } + if gotError == nil && tc.wantError != nil { + t.Error("got nil want error") + } - if gotError != nil && tc.wantError == nil { - t.Error("got error want nil") - } + if gotError != nil && tc.wantError == nil { + t.Error("got error want nil") + } return }) diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go index 28464b125c..432cfff43c 100644 --- a/internal/scout/resource/style/Table.go +++ b/internal/scout/resource/style/Table.go @@ -40,10 +40,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { copiedMessage, ), ) - case "C": - return m, tea.Batch( - tea.Printf("pressing C has correct action"), - ) } } m.table, cmd = m.table.Update(msg) @@ -53,7 +49,6 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m model) View() string { s := "\n > Press 'j' and 'k' to go up and down\n" s += " > Press 'c' to copy highlighted row to clipboard\n" - // s += " > Press 'C' to copy all rows to clipboard\n" s += " > Press 'q' to quit\n\n" s += baseStyle.Render(m.table.View()) + "\n" return s From 21f66e9025ba08a0d8363bd16a74d99566b978d8 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 15 May 2023 09:47:28 -0500 Subject: [PATCH 30/38] remove redundant return statement --- internal/scout/resource/resource_test.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index 26ab1c7b6b..a236a6d2dc 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -108,8 +108,6 @@ func TestGetMemUnits(t *testing.T) { if gotError != nil && tc.wantError == nil { t.Error("got error want nil") } - - return }) } } From ee1e64ec4a31adc51eca2e9bbab6b74805c22038 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 15 May 2023 15:23:17 -0500 Subject: [PATCH 31/38] export to file now works for docker as well. --- internal/scout/resource/style/Table.go | 86 +++++++++++++++++++++++++- 1 file changed, 85 insertions(+), 1 deletion(-) diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go index 432cfff43c..4549f2c1c7 100644 --- a/internal/scout/resource/style/Table.go +++ b/internal/scout/resource/style/Table.go @@ -2,12 +2,16 @@ package style import ( "fmt" + "path/filepath" + "text/tabwriter" + + "os" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/sourcegraph/sourcegraph/lib/errors" "golang.design/x/clipboard" - "os" ) var baseStyle = lipgloss.NewStyle(). @@ -40,6 +44,21 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { copiedMessage, ), ) + case "C": + tmpDir := os.TempDir() + filePath := filepath.Join(tmpDir, "resource-dump.txt") + m.dumpResources(m.table.Rows(), filePath) + savedMessage := lipgloss.NewStyle(). + Foreground(lipgloss.Color("#32CD32")). + Render(fmt.Sprintf( + "saved resource allocations to %s", + filePath, + )) + return m, tea.Batch( + tea.Printf( + savedMessage, + ), + ) } } m.table, cmd = m.table.Update(msg) @@ -49,11 +68,76 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m model) View() string { s := "\n > Press 'j' and 'k' to go up and down\n" s += " > Press 'c' to copy highlighted row to clipboard\n" + s += " > Press 'C' to copy all rows to a file\n" s += " > Press 'q' to quit\n\n" s += baseStyle.Render(m.table.View()) + "\n" return s } +func (m model) dumpResources(rows []table.Row, filePath string) error { + dumpFile, err := os.Create(filePath) + if err != nil { + return errors.Wrap(err, "error while creating new file") + } + + tw := tabwriter.NewWriter(dumpFile, 0, 0, 3, ' ', 0) + defer func() { + tw.Flush() + }() + fmt.Println(len(rows)) + + if len(rows[0]) == 6 { + _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY\n")) + if err != nil { + return errors.Wrap(err, "error while appending columns to filepath") + } + + for _, row := range rows { + name := row[0] + cpuLimits := row[1] + cpuRequests := row[2] + memLimits := row[3] + memRequests := row[4] + capacity := row[5] + fmt.Fprintf( + tw, + "%s\t%s\t%s\t%s\t%s\t%s\n", + name, + cpuLimits, + cpuRequests, + memLimits, + memRequests, + capacity, + ) + } + } else if len(rows[0]) == 5 { + fmt.Println("got here!") + _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU CORES\tCPU SHARES\tMEM LIMITS\tMEM RESERVATIONS\n")) + if err != nil { + return errors.Wrap(err, "error while appending columns to filepath") + } + + for _, row := range rows { + name := row[0] + cpuCores := row[1] + cpuShares := row[2] + memLimits := row[3] + memReservations := row[4] + fmt.Fprintf( + tw, + "%s\t%s\t%s\t%s\t%s\n", + name, + cpuCores, + cpuShares, + memLimits, + memReservations, + ) + } + } + + return nil +} + func (m model) copyRowToClipboard(row table.Row) { var containerInfo string From 575a290eeb534a96db541f8eb1c5cfc189bebdc9 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 15 May 2023 15:34:44 -0500 Subject: [PATCH 32/38] refactor --- internal/scout/resource/style/Table.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go index 4549f2c1c7..96150d8eff 100644 --- a/internal/scout/resource/style/Table.go +++ b/internal/scout/resource/style/Table.go @@ -79,12 +79,15 @@ func (m model) dumpResources(rows []table.Row, filePath string) error { if err != nil { return errors.Wrap(err, "error while creating new file") } + + defer func() { + dumpFile.Close() + }() tw := tabwriter.NewWriter(dumpFile, 0, 0, 3, ' ', 0) defer func() { tw.Flush() }() - fmt.Println(len(rows)) if len(rows[0]) == 6 { _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY\n")) @@ -111,7 +114,6 @@ func (m model) dumpResources(rows []table.Row, filePath string) error { ) } } else if len(rows[0]) == 5 { - fmt.Println("got here!") _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU CORES\tCPU SHARES\tMEM LIMITS\tMEM RESERVATIONS\n")) if err != nil { return errors.Wrap(err, "error while appending columns to filepath") From 941bc31441d18417e1a622e6d9957400cc2e3c9b Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Mon, 15 May 2023 15:36:07 -0500 Subject: [PATCH 33/38] add file export functionality for docker --- internal/scout/resource/style/Table.go | 106 ++++++++++++------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go index 96150d8eff..bd86363c7a 100644 --- a/internal/scout/resource/style/Table.go +++ b/internal/scout/resource/style/Table.go @@ -56,7 +56,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { )) return m, tea.Batch( tea.Printf( - savedMessage, + savedMessage, ), ) } @@ -68,7 +68,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m model) View() string { s := "\n > Press 'j' and 'k' to go up and down\n" s += " > Press 'c' to copy highlighted row to clipboard\n" - s += " > Press 'C' to copy all rows to a file\n" + s += " > Press 'C' to copy all rows to a file\n" s += " > Press 'q' to quit\n\n" s += baseStyle.Render(m.table.View()) + "\n" return s @@ -79,63 +79,63 @@ func (m model) dumpResources(rows []table.Row, filePath string) error { if err != nil { return errors.Wrap(err, "error while creating new file") } - - defer func() { - dumpFile.Close() - }() + + defer func() { + dumpFile.Close() + }() tw := tabwriter.NewWriter(dumpFile, 0, 0, 3, ' ', 0) defer func() { tw.Flush() }() - if len(rows[0]) == 6 { - _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY\n")) - if err != nil { - return errors.Wrap(err, "error while appending columns to filepath") - } - - for _, row := range rows { - name := row[0] - cpuLimits := row[1] - cpuRequests := row[2] - memLimits := row[3] - memRequests := row[4] - capacity := row[5] - fmt.Fprintf( - tw, - "%s\t%s\t%s\t%s\t%s\t%s\n", - name, - cpuLimits, - cpuRequests, - memLimits, - memRequests, - capacity, - ) - } - } else if len(rows[0]) == 5 { - _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU CORES\tCPU SHARES\tMEM LIMITS\tMEM RESERVATIONS\n")) - if err != nil { - return errors.Wrap(err, "error while appending columns to filepath") - } - - for _, row := range rows { - name := row[0] - cpuCores := row[1] - cpuShares := row[2] - memLimits := row[3] - memReservations := row[4] - fmt.Fprintf( - tw, - "%s\t%s\t%s\t%s\t%s\n", - name, - cpuCores, - cpuShares, - memLimits, - memReservations, - ) - } - } + if len(rows[0]) == 6 { + _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY\n")) + if err != nil { + return errors.Wrap(err, "error while appending columns to filepath") + } + + for _, row := range rows { + name := row[0] + cpuLimits := row[1] + cpuRequests := row[2] + memLimits := row[3] + memRequests := row[4] + capacity := row[5] + fmt.Fprintf( + tw, + "%s\t%s\t%s\t%s\t%s\t%s\n", + name, + cpuLimits, + cpuRequests, + memLimits, + memRequests, + capacity, + ) + } + } else if len(rows[0]) == 5 { + _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU CORES\tCPU SHARES\tMEM LIMITS\tMEM RESERVATIONS\n")) + if err != nil { + return errors.Wrap(err, "error while appending columns to filepath") + } + + for _, row := range rows { + name := row[0] + cpuCores := row[1] + cpuShares := row[2] + memLimits := row[3] + memReservations := row[4] + fmt.Fprintf( + tw, + "%s\t%s\t%s\t%s\t%s\n", + name, + cpuCores, + cpuShares, + memLimits, + memReservations, + ) + } + } return nil } From 20a6c478c6483557ace87496e612cd588351d583 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 16 May 2023 07:47:30 -0500 Subject: [PATCH 34/38] fix errors linter caught --- internal/scout/resource/resource_test.go | 2 +- internal/scout/resource/style/Table.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index a236a6d2dc..f36ddc6506 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -3,6 +3,7 @@ package resource import ( "context" "fmt" + "testing" "github.com/sourcegraph/sourcegraph/lib/errors" v1 "k8s.io/api/core/v1" @@ -10,7 +11,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "testing" ) func TestResourcesK8s(t *testing.T) { diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go index bd86363c7a..3c5ba59cf0 100644 --- a/internal/scout/resource/style/Table.go +++ b/internal/scout/resource/style/Table.go @@ -36,7 +36,7 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { copiedMessage := lipgloss.NewStyle(). Foreground(lipgloss.Color("#32CD32")). Render(fmt.Sprintf( - "Copied resource allocations for %s to clipboard", + "Copied resource allocations for '%s' to clipboard", m.table.SelectedRow()[0], )) return m, tea.Batch( @@ -90,7 +90,7 @@ func (m model) dumpResources(rows []table.Row, filePath string) error { }() if len(rows[0]) == 6 { - _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY\n")) + _, err = fmt.Fprintf(tw, "NAME\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY\n") if err != nil { return errors.Wrap(err, "error while appending columns to filepath") } @@ -114,7 +114,7 @@ func (m model) dumpResources(rows []table.Row, filePath string) error { ) } } else if len(rows[0]) == 5 { - _, err = fmt.Fprintf(tw, fmt.Sprintf("NAME\tCPU CORES\tCPU SHARES\tMEM LIMITS\tMEM RESERVATIONS\n")) + _, err = fmt.Fprintf(tw, "NAME\tCPU CORES\tCPU SHARES\tMEM LIMITS\tMEM RESERVATIONS\n") if err != nil { return errors.Wrap(err, "error while appending columns to filepath") } From 69f421641622a6d11b654f7e5c6420d88499e23b Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 16 May 2023 07:49:22 -0500 Subject: [PATCH 35/38] fix import order (linter caught) --- internal/scout/resource/resource_test.go | 2 +- internal/scout/resource/style/Table.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index a236a6d2dc..f36ddc6506 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -3,6 +3,7 @@ package resource import ( "context" "fmt" + "testing" "github.com/sourcegraph/sourcegraph/lib/errors" v1 "k8s.io/api/core/v1" @@ -10,7 +11,6 @@ import ( "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/util/homedir" - "testing" ) func TestResourcesK8s(t *testing.T) { diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go index 432cfff43c..a24b02cc4a 100644 --- a/internal/scout/resource/style/Table.go +++ b/internal/scout/resource/style/Table.go @@ -2,12 +2,12 @@ package style import ( "fmt" + "os" "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "golang.design/x/clipboard" - "os" ) var baseStyle = lipgloss.NewStyle(). From e3dddeff8f8306ae2dc42fe79bac3c19ada28dde Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 16 May 2023 07:59:00 -0500 Subject: [PATCH 36/38] remove unnecessary integration test --- internal/scout/resource/resource.go | 2 +- internal/scout/resource/resource_test.go | 45 ------------------------ 2 files changed, 1 insertion(+), 46 deletions(-) diff --git a/internal/scout/resource/resource.go b/internal/scout/resource/resource.go index 4a63ef9374..54327fa003 100644 --- a/internal/scout/resource/resource.go +++ b/internal/scout/resource/resource.go @@ -1,7 +1,7 @@ package resource import ( - "context" + "context" "fmt" "os" diff --git a/internal/scout/resource/resource_test.go b/internal/scout/resource/resource_test.go index f36ddc6506..f0cbd74d36 100644 --- a/internal/scout/resource/resource_test.go +++ b/internal/scout/resource/resource_test.go @@ -1,55 +1,10 @@ package resource import ( - "context" "fmt" "testing" - - "github.com/sourcegraph/sourcegraph/lib/errors" - v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/tools/clientcmd" - "k8s.io/client-go/util/homedir" ) -func TestResourcesK8s(t *testing.T) { - ctx := context.Background() - - config, err := clientcmd.BuildConfigFromFlags("", homedir.HomeDir()+"/.kube/config") - if err != nil { - t.Fatal(errors.Wrap(err, "Error getting in cluster config")) - } - - k8sClientSet, err := kubernetes.NewForConfig(config) - if err != nil { - t.Fatal(errors.Wrap(err, "Error creating kubernetes clientset")) - } - - // Create some test pods to list - pod1 := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod1", - Namespace: "test", - }, - } - - pod2 := &v1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod2", - Namespace: "other", - }, - } - - k8sClientSet.CoreV1().Pods("test").Create(ctx, pod1, metav1.CreateOptions{}) - k8sClientSet.CoreV1().Pods("other").Create(ctx, pod2, metav1.CreateOptions{}) - - err = K8s(ctx, k8sClientSet, nil, WithNamespace("test")) - if err != nil { - t.Fatal(errors.Wrap(err, "Error calling ResourcesK8s")) - } -} - func TestGetMemUnits(t *testing.T) { cases := []struct { name string From 3ee7c08998fcd07c3277d048655434657ad2f086 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 16 May 2023 08:04:46 -0500 Subject: [PATCH 37/38] import order --- internal/scout/resource/resource.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/scout/resource/resource.go b/internal/scout/resource/resource.go index 54327fa003..4a63ef9374 100644 --- a/internal/scout/resource/resource.go +++ b/internal/scout/resource/resource.go @@ -1,7 +1,7 @@ package resource import ( - "context" + "context" "fmt" "os" From d5ce0af845c36c642887103e1ce28620ed69e015 Mon Sep 17 00:00:00 2001 From: Jason Hawk Harris Date: Tue, 16 May 2023 08:28:14 -0500 Subject: [PATCH 38/38] add the ability to dump all container resource data txt file --- cmd/src/scout.go | 2 +- internal/scout/resource/style/Table.go | 147 +++++++++---------------- 2 files changed, 54 insertions(+), 95 deletions(-) diff --git a/cmd/src/scout.go b/cmd/src/scout.go index 753012cac8..0ad74a33c9 100644 --- a/cmd/src/scout.go +++ b/cmd/src/scout.go @@ -8,7 +8,7 @@ import ( var scoutCommands commander func init() { - usage := `'src scout' is a tool that provides monitoring for Sourcegraph resource allocations + usage := `'src scout' is a tool that provides monitoring for Sourcegraph resources EXPERIMENTAL: 'scout' is an experimental command in the 'src' tool. To use, you must point your .kube config to your Sourcegraph instance. diff --git a/internal/scout/resource/style/Table.go b/internal/scout/resource/style/Table.go index 6a07df5062..fe5830b9c5 100644 --- a/internal/scout/resource/style/Table.go +++ b/internal/scout/resource/style/Table.go @@ -2,14 +2,11 @@ package style import ( "fmt" -<<<<<<< HEAD + "os" "path/filepath" + "strings" "text/tabwriter" -======= ->>>>>>> jhh/src-resource - "os" - "github.com/charmbracelet/bubbles/table" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" @@ -82,110 +79,72 @@ func (m model) dumpResources(rows []table.Row, filePath string) error { if err != nil { return errors.Wrap(err, "error while creating new file") } - - defer func() { - dumpFile.Close() - }() + defer dumpFile.Close() tw := tabwriter.NewWriter(dumpFile, 0, 0, 3, ' ', 0) - defer func() { - tw.Flush() - }() + defer tw.Flush() + + // default to docker terms + headers := []string{ + "NAME", + "CPU CORES", + "CPU SHARES", + "MEM LIMITS", + "MEM RESERVATIONS", + } + // kubernetes rows will always have 6 items + // change column headers to reflect k8s terms if len(rows[0]) == 6 { - _, err = fmt.Fprintf(tw, "NAME\tCPU LIMITS\tCPU REQUESTS\tMEM LIMITS\tMEM REQUESTS\tCAPACITY\n") - if err != nil { - return errors.Wrap(err, "error while appending columns to filepath") + headers = []string{ + "NAME", + "CPU LIMITS", + "CPU REQUESTS", + "MEM LIMITS", + "MEM REQUESTS", + "CAPACITY", } + } - for _, row := range rows { - name := row[0] - cpuLimits := row[1] - cpuRequests := row[2] - memLimits := row[3] - memRequests := row[4] - capacity := row[5] - fmt.Fprintf( - tw, - "%s\t%s\t%s\t%s\t%s\t%s\n", - name, - cpuLimits, - cpuRequests, - memLimits, - memRequests, - capacity, - ) - } - } else if len(rows[0]) == 5 { - _, err = fmt.Fprintf(tw, "NAME\tCPU CORES\tCPU SHARES\tMEM LIMITS\tMEM RESERVATIONS\n") - if err != nil { - return errors.Wrap(err, "error while appending columns to filepath") - } + fmt.Fprintf(tw, strings.Join(headers, "\t")+"\n") - for _, row := range rows { - name := row[0] - cpuCores := row[1] - cpuShares := row[2] - memLimits := row[3] - memReservations := row[4] - fmt.Fprintf( - tw, - "%s\t%s\t%s\t%s\t%s\n", - name, - cpuCores, - cpuShares, - memLimits, - memReservations, - ) + for _, row := range rows { + values := []string{row[0], row[1], row[2], row[3], row[4]} + if len(row) == 6 { + values = append(values, row[5]) } + fmt.Fprintf(tw, strings.Join(values, "\t")+"\n") } - return nil } func (m model) copyRowToClipboard(row table.Row) { var containerInfo string - // change output based on the length of row - // docker rows will always be length of 5 - // kubernetes rows will always be length of 6 - if len(row) == 5 { - name := row[0] - cpuCores := row[1] - cpuShares := row[2] - memLimits := row[3] - memReservations := row[4] - containerInfo = fmt.Sprintf(`container: %s - cpu cores: %s - cpu shares: %s - mem limits: %s - mem reservations: %s`, - name, - cpuCores, - cpuShares, - memLimits, - memReservations, - ) - } else if len(row) == 6 { - name := row[0] - cpuLimits := row[1] - cpuRequests := row[2] - memLimits := row[3] - memRequests := row[4] - capacity := row[5] - containerInfo = fmt.Sprintf(`container: %s - cpu limits: %s - cpu requests: %s - mem limits: %s - mem requests: %s - disk capacity: %s`, - name, - cpuLimits, - cpuRequests, - memLimits, - memRequests, - capacity, - ) + // default to docker headers + headers := []string{ + "NAME", + "CPU CORES", + "CPU SHARES", + "MEM LIMITS", + "MEM RESERVATIONS", + } + + // kubernetes rows will always have 6 items + // change column headers to reflect k8s terms + if len(row) == 6 { + headers = []string{ + "NAME", + "CPU LIMITS", + "CPU REQUESTS", + "MEM LIMITS", + "MEM REQUESTS", + "CAPACITY", + } + } + + for i, header := range headers { + containerInfo += fmt.Sprintf("%s: %s\n", header, row[i]) } clipboard.Write(clipboard.FmtText, []byte(containerInfo))