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 }