github.com/olli-ai/jx/v2@v2.0.400-0.20210921045218-14731b4dd448/pkg/cmd/get/get_crd_counts.go (about)

     1  package get
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"strconv"
     7  
     8  	"github.com/jenkins-x/jx-logging/pkg/log"
     9  	"github.com/olli-ai/jx/v2/pkg/util"
    10  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    11  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    12  
    13  	"github.com/olli-ai/jx/v2/pkg/cmd/helper"
    14  	"github.com/pkg/errors"
    15  	"github.com/spf13/cobra"
    16  	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime/schema"
    18  
    19  	"github.com/olli-ai/jx/v2/pkg/cmd/opts"
    20  	"github.com/olli-ai/jx/v2/pkg/cmd/templates"
    21  )
    22  
    23  // CRDCountOptions the command line options
    24  type CRDCountOptions struct {
    25  	*opts.CommonOptions
    26  }
    27  
    28  type tableLine struct {
    29  	name      string
    30  	version   string
    31  	count     int
    32  	namespace string
    33  }
    34  
    35  var (
    36  	getCrdCountLong = templates.LongDesc(`
    37  		Count the number of resources for all custom resources definitions
    38  
    39  `)
    40  
    41  	getCrdCountExample = templates.Examples(`
    42  
    43  		# Count the number of resources for all custom resources definitions
    44  		jx get crd count
    45  	`)
    46  )
    47  
    48  // NewCmdGetCRDCount creates the command object
    49  func NewCmdGetCRDCount(commonOpts *opts.CommonOptions) *cobra.Command {
    50  	options := &CRDCountOptions{
    51  		CommonOptions: commonOpts,
    52  	}
    53  
    54  	cmd := &cobra.Command{
    55  		Use:     "crd count",
    56  		Short:   "Display resources count for all custom resources",
    57  		Long:    getCrdCountLong,
    58  		Example: getCrdCountExample,
    59  		Run: func(cmd *cobra.Command, args []string) {
    60  			options.Cmd = cmd
    61  			options.Args = args
    62  			err := options.Run()
    63  			helper.CheckErr(err)
    64  		},
    65  	}
    66  	return cmd
    67  }
    68  
    69  // Run implements this command
    70  func (o *CRDCountOptions) Run() error {
    71  	results, err := o.getCustomResourceCounts()
    72  	if err != nil {
    73  		return errors.Wrap(err, "cannot get custom resource counts")
    74  	}
    75  
    76  	table := o.CreateTable()
    77  	table.AddRow("NAME", "VERSION", "COUNT", "NAMESPACE")
    78  
    79  	for _, r := range results {
    80  		table.AddRow(r.name, r.version, strconv.Itoa(r.count), r.namespace)
    81  	}
    82  
    83  	table.Render()
    84  	return nil
    85  }
    86  
    87  func (o *CRDCountOptions) getCustomResourceCounts() ([]tableLine, error) {
    88  
    89  	exClient, err := o.ApiExtensionsClient()
    90  	if err != nil {
    91  		return nil, errors.Wrap(err, "failed to get api extensions client")
    92  	}
    93  	dynamicClient, _, err := o.GetFactory().CreateDynamicClient()
    94  	if err != nil {
    95  		return nil, errors.Wrap(err, "failed to get dynamic client")
    96  	}
    97  
    98  	// lets loop over each arg and validate they are resources, note we could have "--all"
    99  	crdList, err := exClient.ApiextensionsV1beta1().CustomResourceDefinitions().List(v1.ListOptions{})
   100  	if err != nil {
   101  		return nil, errors.Wrap(err, "failed to get a list of custom resource definitions")
   102  	}
   103  
   104  	kclient, _, err := o.GetFactory().CreateKubeClient()
   105  	if err != nil {
   106  		return nil, errors.Wrap(err, "failed to get kubernetes client")
   107  	}
   108  
   109  	// get the list of namespaces the user has access to
   110  	namespaces, err := kclient.CoreV1().Namespaces().List(v1.ListOptions{})
   111  	if err != nil {
   112  		return nil, errors.Wrap(err, "failed to list namespaces")
   113  	}
   114  
   115  	log.Logger().Info("Looking for cluster wide custom resource counts and namespaced custom resources in these namespaces:")
   116  	for _, namespace := range namespaces.Items {
   117  		log.Logger().Infof("%s", util.ColorInfo(namespace.Name))
   118  	}
   119  	log.Logger().Info("this operation may take a while depending on how many custom resources exist")
   120  
   121  	var results []tableLine
   122  	// loop over each crd and check how many resources exist for them
   123  	for _, crd := range crdList.Items {
   124  		// each crd can have multiple versions
   125  		for _, v := range crd.Spec.Versions {
   126  			r := schema.GroupVersionResource{Group: crd.Spec.Group, Version: v.Name, Resource: crd.Spec.Names.Plural}
   127  
   128  			if crd.Spec.Scope == v1beta1.ClusterScoped {
   129  				// get cluster scoped resources
   130  				resources, err := dynamicClient.Resource(r).List(v1.ListOptions{})
   131  				if err != nil {
   132  					return nil, errors.Wrapf(err, "finding resource %s.%s %s", crd.Spec.Names.Plural, crd.Spec.Group, v.Name)
   133  				}
   134  
   135  				results = o.addLine(crd, v, resources, results, "cluster scoped")
   136  			} else if crd.Spec.Scope == v1beta1.NamespaceScoped {
   137  				// get namespaced scoped resources
   138  				for _, n := range namespaces.Items {
   139  
   140  					resources, err := dynamicClient.Resource(r).Namespace(n.Name).List(v1.ListOptions{})
   141  					if err != nil {
   142  						return nil, errors.Wrapf(err, "finding resource %s.%s %s", crd.Spec.Names.Plural, crd.Spec.Group, v.Name)
   143  					}
   144  
   145  					results = o.addLine(crd, v, resources, results, n.Name)
   146  				}
   147  			}
   148  		}
   149  	}
   150  	// sort the entries so resources with the most come at the bottom as it's clearer to see after running the command
   151  	sort.Slice(results, func(i, j int) bool {
   152  		return results[i].count < results[j].count
   153  	})
   154  	return results, nil
   155  }
   156  
   157  func (o *CRDCountOptions) addLine(crd v1beta1.CustomResourceDefinition, v v1beta1.CustomResourceDefinitionVersion, resources *unstructured.UnstructuredList, results []tableLine, namespace string) []tableLine {
   158  	line := tableLine{
   159  		name:      fmt.Sprintf("%s.%s", crd.Spec.Names.Plural, crd.Spec.Group),
   160  		version:   v.Name,
   161  		count:     len(resources.Items),
   162  		namespace: namespace,
   163  	}
   164  	return append(results, line)
   165  }