diff --git a/cmd/container.go b/cmd/container.go index 14e1a98..3192c08 100644 --- a/cmd/container.go +++ b/cmd/container.go @@ -40,7 +40,7 @@ var containerCmd = &cobra.Command{ Aliases: []string{"containers", "docker"}, RunE: func(cmd *cobra.Command, args []string) error { // validate args and extract flags. - containerCmd, err := constructContainerCommand(cmd, args) + containerCmd, err := constructKubernetesCommand(cmd, args) if err != nil { return err } @@ -79,13 +79,15 @@ var containerCmd = &cobra.Command{ }, } -type containerCommand struct { +type kubernetesCommand struct { refreshRate uint64 cid string all bool } -func constructContainerCommand(cmd *cobra.Command, args []string) (*containerCommand, error) { +func constructKubernetesCommand(cmd *cobra.Command, args []string) (*kubernetesCommand, error) { + // This is the individual command parsing logic + // TODO leave this untouched as of now, since we'll be using defaults if len(args) > 0 { return nil, fmt.Errorf("the container command should have no arguments, see grofer container --help for further info") } @@ -108,7 +110,7 @@ func constructContainerCommand(cmd *cobra.Command, args []string) (*containerCom return nil, errors.New("invalid refresh rate: minimum refresh rate is 1000(ms)") } - containerCmd := &containerCommand{ + containerCmd := &kubernetesCommand{ refreshRate: containerRefreshRate, cid: cid, all: allFlag, @@ -117,7 +119,7 @@ func constructContainerCommand(cmd *cobra.Command, args []string) (*containerCom return containerCmd, nil } -func (cc *containerCommand) isPerContainer() bool { +func (cc *kubernetesCommand) isPerContainer() bool { return cc.cid != defaultCid } diff --git a/cmd/kuberenetes.go b/cmd/kuberenetes.go new file mode 100644 index 0000000..76c25a5 --- /dev/null +++ b/cmd/kuberenetes.go @@ -0,0 +1,134 @@ +package cmd + +import ( + "errors" + "fmt" + "log" + + "github.com/pesos/grofer/pkg/core" + "github.com/pesos/grofer/pkg/metrics/factory" + "github.com/pesos/grofer/pkg/utils" + "github.com/spf13/cobra" +) + +const ( + defaultCid = "" + defaultContainerRefreshRate = 1000 +) + +// k8sCmd represents the kubernetes command +var k8sCmd = &cobra.Command{ + Use: "kubernetes", + Short: "kubernetes command is used to get information related to the local kubernetes cluster", + Long: `kubernetes command is used to get information related to the local kubernetes cluster. It provides both overall and per pod metrics.`, + Aliases: []string{"k8s", "kubernetes"}, + RunE: func(cmd *cobra.Command, args []string) error { + + // validate args and extract flags. + containerCmd, err := constructContainerCommand(cmd, args) + if err != nil { + return err + } + + // create a metric scraper factory that will help construct + // a container metric specific MetricScraper. + metricScraperFactory := factory. + NewMetricScraperFactory(). + ForCommand(core.KubernetesCommand). + WithScrapeInterval(containerCmd.refreshRate) + + /* + if containerCmd.isPerContainer() { + metricScraperFactory = metricScraperFactory.ForSingularEntity(containerCmd.cid) + } + */ + + // construct a container specific MetricScraper. + kubeMetricScraper, err := metricScraperFactory.Construct() + if err != nil { + return err + } + + if containerCmd.all { + err = kubeMetricScraper.Serve(factory.WithAllAs(containerCmd.all)) + } else { + err = kubeMetricScraper.Serve() + } + + if err != nil && err != core.ErrCanceledByUser { + if err == core.ErrInvalidContainer { + utils.ErrorMsg("cid") + } + log.Printf("Error: %v\n", err) + } + + return nil + }, +} + +type k8sCommand struct { + refreshRate uint64 + cid string + all bool +} + +func constructK8sCommand(cmd *cobra.Command, args []string) (*containerCommand, error) { + if len(args) > 0 { + return nil, fmt.Errorf("the container command should have no arguments, see grofer container --help for further info") + } + cid, err := cmd.Flags().GetString("container-id") + if err != nil { + return nil, errors.New("error extracting flag --container-id") + } + + allFlag, err := cmd.Flags().GetBool("all") + if err != nil { + return nil, errors.New("error extracting flag --all") + } + + containerRefreshRate, err := cmd.Flags().GetUint64("refresh") + if err != nil { + return nil, errors.New("error extracting flag --refresh") + } + + if containerRefreshRate < 1000 { + return nil, errors.New("invalid refresh rate: minimum refresh rate is 1000(ms)") + } + + containerCmd := &containerCommand{ + refreshRate: containerRefreshRate, + cid: cid, + all: allFlag, + } + + return containerCmd, nil +} + +func (cc *containerCommand) isPerContainer() bool { + return cc.cid != defaultCid +} + +func init() { + rootCmd.AddCommand(containerCmd) + + containerCmd.Flags().StringP( + "container-id", + "c", + "", + "specify container ID", + ) + + containerCmd.Flags().Uint64P( + "refresh", + "r", + defaultContainerRefreshRate, + "Container information UI refreshes rate in milliseconds greater than 1000", + ) + + containerCmd.Flags().BoolP( + "all", + "a", + false, + "Specify to list all containers or only running containers.", + ) +} diff --git a/pkg/core/types.go b/pkg/core/types.go index 7531b89..cfb730d 100644 --- a/pkg/core/types.go +++ b/pkg/core/types.go @@ -30,6 +30,8 @@ const ( ContainerCommand // ExportCommand is `grofer export` and its variants. ExportCommand + // KubernetesCommand is `grofer kube` and its variants. + KubernetesCommand ) // Sink represents any entity that consumes generated metrics. diff --git a/pkg/metrics/factory/metrics_factory.go b/pkg/metrics/factory/metrics_factory.go index 04c01e3..3fa6146 100644 --- a/pkg/metrics/factory/metrics_factory.go +++ b/pkg/metrics/factory/metrics_factory.go @@ -22,10 +22,15 @@ import ( proc "github.com/shirou/gopsutil/process" + // TODO add client-go import "github.com/docker/docker/client" "github.com/pesos/grofer/pkg/core" "github.com/pesos/grofer/pkg/metrics/container" + kubernetes_metrics "github.com/pesos/grofer/pkg/metrics/kubernetes" "github.com/pesos/grofer/pkg/metrics/process" + + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/clientcmd" ) // MetricScraperFactory constructs a MetricScaper for a command @@ -84,10 +89,46 @@ func (msf *MetricScraperFactory) Construct() (MetricScraper, error) { return msf.constructContainerMetricScraper() case core.ProcCommand: return msf.constructProcessMetricScraper() + case core.KubernetesCommand: + return msf.constructKubernetesMetricScraper() } return nil, errors.New("command not recognized") } +func (msf* MetricScraperFactory) constructKubernetesMetricScraper() (MetricScraper, error) { + // TODO Construct a new metrics scraper here + return msf.newKubernetesMetrics() +} + +func (msf* MetricScraperFactory) newKubernetesMetrics() (*kubernetes_metrics.KubernetesMetricsScraper, error) { + + var kubeconfig string + + // Default kubeconfig in ~/.kube/config + // TODO take this in as a CLI argument, and use homedir to find it + kubeconfig = "~/.kube/config" + config, err := clientcmd.BuildConfigFromFlags("", kubeconfig) + if err != nil { + // TODO propagate error + panic(err.Error()) + } + // create the clientset + // TODO pass the clientset into the struct here + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + panic(err.Error()) + } + + // construct the actual metrics struct here + kubeMetricsStruct := &kubernetes_metrics.KubernetesMetricsScraper{ + Clientset: clientset, + RefreshRate: msf.scrapeIntervalMillisecond, + MetricBus: make(chan kubernetes_metrics.KubernetesMetrics), + } + + return kubeMetricsStruct, nil +} + func (msf *MetricScraperFactory) constructSystemWideMetricScraper() (MetricScraper, error) { return &systemWideMetrics{ refreshRate: msf.scrapeIntervalMillisecond, @@ -159,3 +200,4 @@ func (msf *MetricScraperFactory) newSingluarProcessMetrics() (*singularProcessMe return spm, nil } + diff --git a/pkg/metrics/kubernetes/kubernetes.go b/pkg/metrics/kubernetes/kubernetes.go new file mode 100644 index 0000000..b5c2bf2 --- /dev/null +++ b/pkg/metrics/kubernetes/kubernetes.go @@ -0,0 +1,34 @@ +package kubernetes_metrics + +// TODO import client-go +import ( + "github.com/pesos/grofer/pkg/core" +) + +// TODO define k8s-specific metrics structs +/* +type containerMetrics struct { + client *client.Client + all bool + refreshRate uint64 + sink core.Sink // defaults to TUI. + metricBus chan container.OverallMetrics +} +*/ + +type Pod struct { + name string +} + +type KubernetesMetricsScraper struct { + // TODO reference to initialized client-go goes here. + Clientset string + RefreshRate uint64 + Sink core.Sink + MetricBus chan KubernetesMetrics +} + +// TODO add more data here! +type KubernetesMetrics struct { + pods []string +}