Skip to content

Commit

Permalink
Added summary command to kompanion
Browse files Browse the repository at this point in the history
kompanion summary walks the relevant cluster inspecting cnrm resources.
It will sum the count of resources by namespace, type and status.
  • Loading branch information
cheftako committed Nov 14, 2024
1 parent 22ac833 commit 4895664
Show file tree
Hide file tree
Showing 7 changed files with 597 additions and 70 deletions.
71 changes: 5 additions & 66 deletions experiments/kompanion/cmd/export/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ import (
"log"
"os"
"path/filepath"
"sort"
"strings"
"sync"
"time"

"github.com/GoogleCloudPlatform/k8s-config-connector/experiments/kompanion/pkg/utils"
"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
Expand Down Expand Up @@ -268,65 +268,14 @@ func RunExport(ctx context.Context, opts *ExportOptions) error {
return fmt.Errorf("error creating dynamic client: %w", err)
}

// use the discovery client to iterate over all api resoruces
// use the discovery client to iterate over all api resources
discoveryClient := clientset.Discovery()
apiResourceLists, err := discoveryClient.ServerPreferredResources()
if err != nil {
return fmt.Errorf("failed to get preferred resources: %w", err)
}

var resources []schema.GroupVersionResource

for _, apiResourceList := range apiResourceLists {
if !strings.Contains(apiResourceList.GroupVersion, ".cnrm.cloud.google.com/") {
// todo acpana log debug level
// log.Printf("ApiResource %s group doesn't contain \"cnrm\"; skipping", apiResourceList.GroupVersion)
continue
}

apiResourceListGroupVersion, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
if err != nil {
klog.Warningf("skipping unparseable groupVersion %q", apiResourceList.GroupVersion)
continue
}

for _, apiResource := range apiResourceList.APIResources {
if !apiResource.Namespaced {
// todo acpana log debug level
// log.Printf("ApiResource %s is not namespaced; skipping", apiResource.SingularName)
continue
}
if !contains(apiResource.Verbs, "list") {
// todo acpana log debug level
// log.Printf("ApiResource %s is not listabble; skipping", apiResource.SingularName)
continue
}

gvr := schema.GroupVersionResource{
Group: apiResource.Group,
Version: apiResource.Version,
Resource: apiResource.Name,
}

if gvr.Group == "" {
// Empty implies the group of the containing resource list.
gvr.Group = apiResourceListGroupVersion.Group
}

if gvr.Version == "" {
// Empty implies the version of the containing resource list
gvr.Version = apiResourceListGroupVersion.Version
}

resources = append(resources, gvr)
}
resources, err = utils.GetResources(discoveryClient, resources)
if err != nil {
return fmt.Errorf("error fetching resources: %w", err)
}

// Improve determinism for debuggability and idempotency
sort.Slice(resources, func(i, j int) bool {
return resources[i].String() < resources[j].String()
})

// todo acpana debug logs
// log.Printf("Going to iterate over the following resources %+v", resourcesToName(resources))

Expand Down Expand Up @@ -448,16 +397,6 @@ func resourcesToName(resources []*metav1.APIResource) []string {
return names
}

// contains checks if a slice contains a specific string.
func contains(slice []string, str string) bool {
for _, s := range slice {
if strings.ToLower(s) == strings.ToLower(str) {
return true
}
}
return false
}

func shouldExclude(name string, excludes []string, includes []string) bool {
// todo acpana: maps
for _, exclude := range excludes {
Expand Down
88 changes: 88 additions & 0 deletions experiments/kompanion/cmd/summary/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Copyright 2024 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package summary

import (
"fmt"
"log"

"github.com/spf13/pflag"
)

const (
// flag names.
kubeconfigFlag = "kubeconfig"
reportNamePrefixFlag = "report-prefix"

targetNamespacesFlag = "target-namespaces"
ignoreNamespacesFlag = "exclude-namespaces"

targetObjectsFlag = "target-objects"

workerRoutinesFlag = "worker-routines"
)

type SummaryOptions struct {
kubeconfig string
reportNamePrefix string

targetNamespaces []string
ignoreNamespaces []string

targetObjects []string

workerRountines int
}

func (o *SummaryOptions) AddFlags(flags *pflag.FlagSet) {
flags.StringVarP(&o.kubeconfig, kubeconfigFlag, "", o.kubeconfig, "path to the kubeconfig file.")
flags.StringVarP(&o.reportNamePrefix, reportNamePrefixFlag, "", o.reportNamePrefix, "Prefix for the report name. The tool appends a timestamp to this in the format \"YYYYMMDD-HHMMSS.milliseconds\".")

flags.StringArrayVarP(&o.targetNamespaces, targetNamespacesFlag, "", o.targetNamespaces, "namespace prefix to target the export tool. Targets all if empty. Can be specified multiple times.")
flags.StringArrayVarP(&o.ignoreNamespaces, ignoreNamespacesFlag, "", o.ignoreNamespaces, "namespace prefix to ignore. Excludes nothing if empty. Can be specified multiple times. Defaults to \"kube\".")

flags.StringArrayVarP(&o.targetObjects, targetObjectsFlag, "", o.targetObjects, "object name prefix to target. Targets all if empty. Can be specified multiple times.")

flags.IntVarP(&o.workerRountines, workerRoutinesFlag, "", o.workerRountines, "Configure the number of worker routines to export namespaces with. Defaults to 10. ")
}

func (opts *SummaryOptions) validateFlags() error {
if opts.workerRountines <= 0 || opts.workerRountines > 100 {
return fmt.Errorf("invalid value %d for flag %s. Supported values are [1,100]", opts.workerRountines, workerRoutinesFlag)
}

return nil
}

func (o *SummaryOptions) Print() {
log.Printf("kubeconfig set to %q.\n", o.kubeconfig)
log.Printf("reportNamePrefix set to %q.\n", o.reportNamePrefix)
log.Printf("targetNamespaces set to %v.\n", o.targetNamespaces)
log.Printf("ignoreNamespaces set to %v.\n", o.ignoreNamespaces)
log.Printf("targetObjects set to %v.\n", o.targetObjects)
log.Printf("workerRountines set to %d.\n", o.workerRountines)
}

func NewSummaryOptions() *SummaryOptions {
o := SummaryOptions{
kubeconfig: "",
reportNamePrefix: "report",
targetNamespaces: []string{},
ignoreNamespaces: []string{"kube"},
targetObjects: []string{},
workerRountines: 10,
}
return &o
}
Loading

0 comments on commit 4895664

Please sign in to comment.