From 7490d4d4b8a03dac37bfdbaba235ef254e793285 Mon Sep 17 00:00:00 2001 From: Stefan Prodan Date: Sat, 11 Feb 2023 12:35:02 +0200 Subject: [PATCH] Add command to print the reconcilers status Signed-off-by: Stefan Prodan --- cmd/flux/stats.go | 219 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 cmd/flux/stats.go diff --git a/cmd/flux/stats.go b/cmd/flux/stats.go new file mode 100644 index 00000000..fa6eb47f --- /dev/null +++ b/cmd/flux/stats.go @@ -0,0 +1,219 @@ +/* +Copyright 2023 The Flux authors + +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 main + +import ( + "context" + "fmt" + "github.com/fluxcd/flux2/internal/utils" + "github.com/fluxcd/flux2/pkg/printers" + helmv2 "github.com/fluxcd/helm-controller/api/v2beta1" + autov1 "github.com/fluxcd/image-automation-controller/api/v1beta1" + imagev1 "github.com/fluxcd/image-reflector-controller/api/v1beta1" + kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1beta2" + notificationv1 "github.com/fluxcd/notification-controller/api/v1beta2" + sourcev1 "github.com/fluxcd/source-controller/api/v1beta2" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "sigs.k8s.io/cli-utils/pkg/kstatus/status" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +var statsCmd = &cobra.Command{ + Use: "stats", + Short: "Stats of Flux reconciles", + Long: `The stats command prints a report of Flux custom resources present on a cluster, +including their reconcile status and the amount of cumulative storage used for each source type`, + Example: ` # Print the stats report for a namespace + flux stats --namespace default + + # Print the stats report for the whole cluster + flux stats -A`, + RunE: runStatsCmd, +} + +type StatsFlags struct { + allNamespaces bool +} + +var statsArgs StatsFlags + +func init() { + statsCmd.PersistentFlags().BoolVarP(&statsArgs.allNamespaces, "all-namespaces", "A", false, + "list the statistics for objects across all namespaces") + rootCmd.AddCommand(statsCmd) +} + +func runStatsCmd(cmd *cobra.Command, args []string) error { + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + + types := []metav1.GroupVersionKind{ + { + Kind: sourcev1.GitRepositoryKind, + Version: sourcev1.GroupVersion.Version, + Group: sourcev1.GroupVersion.Group, + }, + { + Kind: sourcev1.OCIRepositoryKind, + Version: sourcev1.GroupVersion.Version, + Group: sourcev1.GroupVersion.Group, + }, + { + Kind: sourcev1.HelmRepositoryKind, + Version: sourcev1.GroupVersion.Version, + Group: sourcev1.GroupVersion.Group, + }, + { + Kind: sourcev1.HelmChartKind, + Version: sourcev1.GroupVersion.Version, + Group: sourcev1.GroupVersion.Group, + }, + { + Kind: sourcev1.BucketKind, + Version: sourcev1.GroupVersion.Version, + Group: sourcev1.GroupVersion.Group, + }, + { + Kind: kustomizev1.KustomizationKind, + Version: kustomizev1.GroupVersion.Version, + Group: kustomizev1.GroupVersion.Group, + }, + { + Kind: helmv2.HelmReleaseKind, + Version: helmv2.GroupVersion.Version, + Group: helmv2.GroupVersion.Group, + }, + { + Kind: notificationv1.AlertKind, + Version: notificationv1.GroupVersion.Version, + Group: notificationv1.GroupVersion.Group, + }, + { + Kind: notificationv1.ProviderKind, + Version: notificationv1.GroupVersion.Version, + Group: notificationv1.GroupVersion.Group, + }, + { + Kind: notificationv1.ReceiverKind, + Version: notificationv1.GroupVersion.Version, + Group: notificationv1.GroupVersion.Group, + }, + { + Kind: autov1.ImageUpdateAutomationKind, + Version: autov1.GroupVersion.Version, + Group: autov1.GroupVersion.Group, + }, + { + Kind: imagev1.ImagePolicyKind, + Version: imagev1.GroupVersion.Version, + Group: imagev1.GroupVersion.Group, + }, + { + Kind: imagev1.ImageRepositoryKind, + Version: imagev1.GroupVersion.Version, + Group: imagev1.GroupVersion.Group, + }, + } + + header := []string{"Reconcilers", "Running", "Failing", "Suspended", "Storage"} + var rows [][]string + + for _, t := range types { + var total int + var suspended int + var failing int + var totalSize int64 + + list := unstructured.UnstructuredList{ + Object: map[string]interface{}{ + "apiVersion": t.Group + "/" + t.Version, + "kind": t.Kind, + }, + } + + scope := client.InNamespace("") + if !statsArgs.allNamespaces { + scope = client.InNamespace(*kubeconfigArgs.Namespace) + } + + if err := kubeClient.List(ctx, &list, scope); err == nil { + total = len(list.Items) + + for _, item := range list.Items { + if s, _, _ := unstructured.NestedBool(item.Object, "spec", "suspend"); s { + suspended++ + } + + if obj, err := status.GetObjectWithConditions(item.Object); err == nil { + for _, cond := range obj.Status.Conditions { + if cond.Type == "Ready" && cond.Status == corev1.ConditionFalse { + failing++ + } + } + } + + if size, found, _ := unstructured.NestedInt64(item.Object, "status", "artifact", "size"); found { + totalSize += size + } + } + } + + rows = append(rows, []string{ + t.Kind, + formatInt(total - suspended), + formatInt(failing), + formatInt(suspended), + formatSize(totalSize), + }) + } + + err = printers.TablePrinter(header).Print(cmd.OutOrStdout(), rows) + if err != nil { + return err + } + + return nil +} + +func formatInt(i int) string { + return fmt.Sprintf("%d", i) +} + +func formatSize(b int64) string { + if b == 0 { + return "-" + } + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d B", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %ciB", + float64(b)/float64(div), "KMGTPE"[exp]) +}