From 5692ea939dfd00d270c22c33e2066c30bc080838 Mon Sep 17 00:00:00 2001 From: Somtochi Onyekwere Date: Wed, 22 Nov 2023 21:01:13 +0100 Subject: [PATCH] show cluster instance name and version in flux check and flux version Signed-off-by: Somtochi Onyekwere --- cmd/flux/check.go | 72 ++++++++++++++++++++++------------- cmd/flux/cluster_info.go | 25 +++++++++++- cmd/flux/cluster_info_test.go | 11 ++++++ cmd/flux/version.go | 27 ++++++++++++- 4 files changed, 105 insertions(+), 30 deletions(-) diff --git a/cmd/flux/check.go b/cmd/flux/check.go index df5fec61ec..75fa773674 100644 --- a/cmd/flux/check.go +++ b/cmd/flux/check.go @@ -18,6 +18,7 @@ package main import ( "context" + "fmt" "os" "time" @@ -26,6 +27,7 @@ import ( v1 "k8s.io/api/apps/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/fluxcd/pkg/version" @@ -80,7 +82,18 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { fluxCheck() - if !kubernetesCheck(kubernetesConstraints) { + ctx := context.Background() + kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) + if err != nil { + return err + } + + cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) + if err != nil { + return fmt.Errorf("Kubernetes client initialization failed: %s", err.Error()) + } + + if !kubernetesCheck(cfg, kubernetesConstraints) { checkFailed = true } @@ -92,13 +105,18 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { return nil } + logger.Actionf("checking cluster version") + if !fluxClusterVersionCheck(ctx, kubeClient) { + checkFailed = true + } + logger.Actionf("checking controllers") - if !componentsCheck() { + if !componentsCheck(ctx, cfg, kubeClient) { checkFailed = true } logger.Actionf("checking crds") - if !crdsCheck() { + if !crdsCheck(ctx, kubeClient) { checkFailed = true } @@ -129,17 +147,11 @@ func fluxCheck() { return } if latestSv.GreaterThan(curSv) { - logger.Failuref("flux %s <%s (new version is available, please upgrade)", curSv, latestSv) + logger.Failuref("flux %s <%s (new CLI version is available, please upgrade)", curSv, latestSv) } } -func kubernetesCheck(constraints []string) bool { - cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) - if err != nil { - logger.Failuref("Kubernetes client initialization failed: %s", err.Error()) - return false - } - +func kubernetesCheck(cfg *rest.Config, constraints []string) bool { clientSet, err := kubernetes.NewForConfig(cfg) if err != nil { logger.Failuref("Kubernetes client initialization failed: %s", err.Error()) @@ -178,30 +190,20 @@ func kubernetesCheck(constraints []string) bool { return true } -func componentsCheck() bool { - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) +func componentsCheck(ctx context.Context, kubeConfig *rest.Config, kubeClient client.Client) bool { + timeoutCtx, cancel := context.WithTimeout(ctx, rootArgs.timeout) defer cancel() - kubeConfig, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) - if err != nil { - return false - } - statusChecker, err := status.NewStatusChecker(kubeConfig, checkArgs.pollInterval, rootArgs.timeout, logger) if err != nil { return false } - kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) - if err != nil { - return false - } - ok := true selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue} var list v1.DeploymentList ns := *kubeconfigArgs.Namespace - if err := kubeClient.List(ctx, &list, client.InNamespace(ns), selector); err == nil { + if err := kubeClient.List(timeoutCtx, &list, client.InNamespace(ns), selector); err == nil { if len(list.Items) == 0 { logger.Failuref("no controllers found in the '%s' namespace with the label selector '%s=%s'", ns, manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue) @@ -222,8 +224,8 @@ func componentsCheck() bool { return ok } -func crdsCheck() bool { - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) +func crdsCheck(ctx context.Context, kubeClient client.Client) bool { + timeoutCtx, cancel := context.WithTimeout(ctx, rootArgs.timeout) defer cancel() kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) @@ -234,7 +236,7 @@ func crdsCheck() bool { ok := true selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue} var list apiextensionsv1.CustomResourceDefinitionList - if err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err == nil { + if err := kubeClient.List(timeoutCtx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err == nil { if len(list.Items) == 0 { logger.Failuref("no crds found with the label selector '%s=%s'", manifestgen.PartOfLabelKey, manifestgen.PartOfLabelValue) @@ -253,3 +255,19 @@ func crdsCheck() bool { } return ok } + +func fluxClusterVersionCheck(ctx context.Context, kubeClient client.Client) bool { + timeoutCtx, cancel := context.WithTimeout(ctx, rootArgs.timeout) + defer cancel() + + distribution, bootstrapped, err := getFluxDistribution(timeoutCtx, kubeClient) + if err != nil { + return false + } + + if distribution != "" { + logger.Successf("distribution: %s", distribution) + } + logger.Successf("bootstrapped: %t", bootstrapped) + return true +} diff --git a/cmd/flux/cluster_info.go b/cmd/flux/cluster_info.go index daf122eeca..b9458db522 100644 --- a/cmd/flux/cluster_info.go +++ b/cmd/flux/cluster_info.go @@ -22,9 +22,11 @@ import ( "github.com/manifoldco/promptui" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/fluxcd/flux2/v2/pkg/manifestgen" kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1" ) @@ -42,6 +44,8 @@ type fluxClusterInfo struct { bootstrapped bool // managedBy is the name of the tool being used to manage the installation of Flux. managedBy string + // partOf indicates which distribution the instance is a part of. + partOf string // version is the Flux version number in semver format. version string } @@ -68,7 +72,7 @@ func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, return info, err } - info.version = crdMetadata.Labels["app.kubernetes.io/version"] + info.version = crdMetadata.Labels[manifestgen.VersionLabelKey] var present bool for _, l := range bootstrapLabels { @@ -83,6 +87,10 @@ func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, if manager, ok := crdMetadata.Labels["app.kubernetes.io/managed-by"]; ok { info.managedBy = manager } + + if partOf, ok := crdMetadata.Labels[manifestgen.PartOfLabelKey]; ok { + info.partOf = partOf + } return info, nil } @@ -105,6 +113,21 @@ func confirmFluxInstallOverride(info fluxClusterInfo) error { return err } +func getFluxDistribution(ctx context.Context, kubeClient client.Client) (string, bool, error) { + clusterInfo, err := getFluxClusterInfo(ctx, kubeClient) + if err != nil { + if !errors.IsNotFound(err) { + return "", false, fmt.Errorf("cluster info unavailable: %w", err) + } + } + + distribution := clusterInfo.version + if clusterInfo.partOf != "" { + distribution = fmt.Sprintf("%s-%s", clusterInfo.partOf, clusterInfo.version) + } + return distribution, clusterInfo.bootstrapped, nil +} + func installManagedByFlux(manager string) bool { return manager == "" || manager == "flux" } diff --git a/cmd/flux/cluster_info_test.go b/cmd/flux/cluster_info_test.go index 550ab16591..560c881b5c 100644 --- a/cmd/flux/cluster_info_test.go +++ b/cmd/flux/cluster_info_test.go @@ -102,6 +102,17 @@ func Test_getFluxClusterInfo(t *testing.T) { version: "v2.1.0", }, }, + { + name: "CRD with version and part-of labels", + labels: map[string]string{ + "app.kubernetes.io/version": "v2.1.0", + "app.kubernetes.io/part-of": "flux", + }, + wantInfo: fluxClusterInfo{ + version: "v2.1.0", + partOf: "flux", + }, + }, } for _, tt := range tests { diff --git a/cmd/flux/version.go b/cmd/flux/version.go index f02a161fb6..197b2a9d5f 100644 --- a/cmd/flux/version.go +++ b/cmd/flux/version.go @@ -26,7 +26,7 @@ import ( "github.com/spf13/cobra" v1 "k8s.io/api/apps/v1" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/yaml" + "sigs.k8s.io/yaml/goyaml.v2" "github.com/fluxcd/flux2/v2/internal/utils" "github.com/fluxcd/flux2/v2/pkg/manifestgen" @@ -55,6 +55,12 @@ type versionFlags struct { var versionArgs versionFlags +type VersionInfo struct { + Flux string `yaml:"flux"` + Distribution string `yaml:"distribution,omitempty"` + Controller map[string]string `yaml:"controller,inline"` +} + func init() { versionCmd.Flags().BoolVar(&versionArgs.client, "client", false, "print only client version") @@ -71,8 +77,15 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) defer cancel() + // VersionInfo struct is used for yaml because we care about the order. + // Without this `distribution` is printed before `flux`. + // Unfortunately, encoding/json doesn't support the inline tag, so the struct can't be used for json. + yamlInfo := &VersionInfo{ + Controller: map[string]string{}, + } info := map[string]string{} info["flux"] = rootArgs.defaults.Version + yamlInfo.Flux = rootArgs.defaults.Version if !versionArgs.client { kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) @@ -80,6 +93,15 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { return err } + distribution, _, err := getFluxDistribution(ctx, kubeClient) + if err != nil { + return err + } + if distribution != "" { + info["distribution"] = distribution + yamlInfo.Distribution = distribution + } + selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue} var list v1.DeploymentList if err := kubeClient.List(ctx, &list, client.InNamespace(*kubeconfigArgs.Namespace), selector); err != nil { @@ -97,6 +119,7 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { return err } info[name] = tag + yamlInfo.Controller[name] = tag } } } @@ -108,7 +131,7 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { marshalled, err = json.MarshalIndent(&info, "", " ") marshalled = append(marshalled, "\n"...) } else { - marshalled, err = yaml.Marshal(&info) + marshalled, err = yaml.Marshal(&yamlInfo) } if err != nil {