From fd163ddcf231fc73464cea4abd6b816a12e2eab5 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 | 77 +++++++++++++++++++---------------- cmd/flux/cluster_info.go | 20 ++++++++- cmd/flux/cluster_info_test.go | 11 +++++ cmd/flux/version.go | 45 +++++++++++++++++--- pkg/status/status.go | 18 ++++---- 5 files changed, 122 insertions(+), 49 deletions(-) diff --git a/cmd/flux/check.go b/cmd/flux/check.go index df5fec61ec..244f60de59 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,20 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { fluxCheck() - if !kubernetesCheck(kubernetesConstraints) { + ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) + defer cancel() + + cfg, err := utils.KubeConfig(kubeconfigArgs, kubeclientOptions) + if err != nil { + return fmt.Errorf("Kubernetes client initialization failed: %s", err.Error()) + } + + kubeClient, err := client.New(cfg, client.Options{Scheme: utils.NewScheme()}) + if err != nil { + return err + } + + if !kubernetesCheck(cfg, kubernetesConstraints) { checkFailed = true } @@ -92,13 +107,18 @@ func runCheckCmd(cmd *cobra.Command, args []string) error { return nil } + logger.Actionf("checking version in cluster") + if !fluxClusterVersionCheck(ctx, kubeClient) { + checkFailed = true + } + logger.Actionf("checking controllers") - if !componentsCheck() { + if !componentsCheck(ctx, kubeClient) { checkFailed = true } logger.Actionf("checking crds") - if !crdsCheck() { + if !crdsCheck(ctx, kubeClient) { checkFailed = true } @@ -129,17 +149,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,21 +192,8 @@ func kubernetesCheck(constraints []string) bool { return true } -func componentsCheck() bool { - ctx, cancel := context.WithTimeout(context.Background(), 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) +func componentsCheck(ctx context.Context, kubeClient client.Client) bool { + statusChecker, err := status.NewStatusCheckerWithClient(kubeClient, checkArgs.pollInterval, rootArgs.timeout, logger) if err != nil { return false } @@ -222,15 +223,7 @@ func componentsCheck() bool { return ok } -func crdsCheck() bool { - ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) - defer cancel() - - kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) - if err != nil { - return false - } - +func crdsCheck(ctx context.Context, kubeClient client.Client) bool { ok := true selector := client.MatchingLabels{manifestgen.PartOfLabelKey: manifestgen.PartOfLabelValue} var list apiextensionsv1.CustomResourceDefinitionList @@ -253,3 +246,17 @@ func crdsCheck() bool { } return ok } + +func fluxClusterVersionCheck(ctx context.Context, kubeClient client.Client) bool { + clusterInfo, err := getFluxClusterInfo(ctx, kubeClient) + if err != nil { + logger.Failuref("checking failed: %s", err.Error()) + return false + } + + if clusterInfo.distribution() != "" { + logger.Successf("distribution: %s", clusterInfo.distribution()) + } + logger.Successf("bootstrapped: %t", clusterInfo.bootstrapped) + return true +} diff --git a/cmd/flux/cluster_info.go b/cmd/flux/cluster_info.go index daf122eeca..2f2f79a339 100644 --- a/cmd/flux/cluster_info.go +++ b/cmd/flux/cluster_info.go @@ -27,6 +27,8 @@ import ( kustomizev1 "github.com/fluxcd/kustomize-controller/api/v1" sourcev1 "github.com/fluxcd/source-controller/api/v1" + + "github.com/fluxcd/flux2/v2/pkg/manifestgen" ) // bootstrapLabels are labels put on a resource by kustomize-controller. These labels on the CRD indicates @@ -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 { @@ -78,11 +82,15 @@ func getFluxClusterInfo(ctx context.Context, c client.Client) (fluxClusterInfo, info.bootstrapped = true } - // the `app.kubernetes.io` label is not set by flux but might be set by other + // the `app.kubernetes.io/managed-by` label is not set by flux but might be set by other // tools used to install Flux e.g Helm. 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,14 @@ func confirmFluxInstallOverride(info fluxClusterInfo) error { return err } +func (info fluxClusterInfo) distribution() string { + distribution := info.version + if info.partOf != "" { + distribution = fmt.Sprintf("%s-%s", info.partOf, info.version) + } + return distribution +} + 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..bddbfd4d68 100644 --- a/cmd/flux/version.go +++ b/cmd/flux/version.go @@ -25,8 +25,9 @@ import ( "github.com/google/go-containerregistry/pkg/name" "github.com/spf13/cobra" v1 "k8s.io/api/apps/v1" + "k8s.io/apimachinery/pkg/api/errors" "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 +56,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 +78,12 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { ctx, cancel := context.WithTimeout(context.Background(), rootArgs.timeout) defer cancel() - info := map[string]string{} - info["flux"] = rootArgs.defaults.Version + // versionInfo struct and goyaml is used because we care about the order. + // Without this `distribution` is printed before `flux` when the struct is marshalled. + info := &versionInfo{ + Controller: map[string]string{}, + } + info.Flux = rootArgs.defaults.Version if !versionArgs.client { kubeClient, err := utils.KubeClient(kubeconfigArgs, kubeclientOptions) @@ -80,6 +91,16 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { return err } + clusterInfo, err := getFluxClusterInfo(ctx, kubeClient) + // ignoring not found errors because it means that the GitRepository CRD isn't installed but a user might + // have other controllers(e.g notification-controller), and we want to still return information for them. + if err != nil && !errors.IsNotFound(err) { + return err + } + if clusterInfo.distribution() != "" { + info.Distribution = clusterInfo.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 { @@ -96,7 +117,7 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { if err != nil { return err } - info[name] = tag + info.Controller[name] = tag } } } @@ -105,7 +126,7 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { var err error if versionArgs.output == "json" { - marshalled, err = json.MarshalIndent(&info, "", " ") + marshalled, err = info.toJSON() marshalled = append(marshalled, "\n"...) } else { marshalled, err = yaml.Marshal(&info) @@ -119,6 +140,20 @@ func versionCmdRun(cmd *cobra.Command, args []string) error { return nil } +func (info versionInfo) toJSON() ([]byte, error) { + mapInfo := map[string]string{ + "flux": info.Flux, + } + + if info.Distribution != "" { + mapInfo["distribution"] = info.Distribution + } + for k, v := range info.Controller { + mapInfo[k] = v + } + return json.MarshalIndent(&mapInfo, "", " ") +} + func splitImageStr(image string) (string, string, error) { ref, err := name.ParseReference(image) if err != nil { diff --git a/pkg/status/status.go b/pkg/status/status.go index 8b61881c47..859c4a1e81 100644 --- a/pkg/status/status.go +++ b/pkg/status/status.go @@ -45,6 +45,16 @@ type StatusChecker struct { logger log.Logger } +func NewStatusCheckerWithClient(c client.Client, pollInterval time.Duration, timeout time.Duration, log log.Logger) (*StatusChecker, error) { + return &StatusChecker{ + pollInterval: pollInterval, + timeout: timeout, + client: c, + statusPoller: polling.NewStatusPoller(c, c.RESTMapper(), polling.Options{}), + logger: log, + }, nil +} + func NewStatusChecker(kubeConfig *rest.Config, pollInterval time.Duration, timeout time.Duration, log log.Logger) (*StatusChecker, error) { restMapper, err := runtimeclient.NewDynamicRESTMapper(kubeConfig) if err != nil { @@ -55,13 +65,7 @@ func NewStatusChecker(kubeConfig *rest.Config, pollInterval time.Duration, timeo return nil, err } - return &StatusChecker{ - pollInterval: pollInterval, - timeout: timeout, - client: c, - statusPoller: polling.NewStatusPoller(c, restMapper, polling.Options{}), - logger: log, - }, nil + return NewStatusCheckerWithClient(c, pollInterval, timeout, log) } func (sc *StatusChecker) Assess(identifiers ...object.ObjMetadata) error {