From bc833d8c5fa4e7d93831f705e52e7b184e534f39 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 24 Oct 2024 16:24:07 -0400 Subject: [PATCH 01/20] Update CRD creation method to properly set GVK --- pkg/helm-locker/crd/crds.go | 33 ++++++++++++------------- pkg/helm-project-operator/crd/crds.go | 35 +++++++++++++-------------- 2 files changed, 33 insertions(+), 35 deletions(-) diff --git a/pkg/helm-locker/crd/crds.go b/pkg/helm-locker/crd/crds.go index 72af73c4..aea2da25 100644 --- a/pkg/helm-locker/crd/crds.go +++ b/pkg/helm-locker/crd/crds.go @@ -10,7 +10,6 @@ import ( "github.com/rancher/wrangler/pkg/crd" "github.com/rancher/wrangler/pkg/yaml" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" ) @@ -66,13 +65,16 @@ func Objects(v1beta1 bool) (result []runtime.Object, err error) { // List returns the set of CRDs that need to be generated func List() []crd.CRD { return []crd.CRD{ - newCRD(&v1alpha1.HelmRelease{}, func(c crd.CRD) crd.CRD { - return c. - WithColumn("Release Name", ".spec.release.name"). - WithColumn("Release Namespace", ".spec.release.namespace"). - WithColumn("Version", ".status.version"). - WithColumn("State", ".status.state") - }), + newCRD( + "HelmRelease.helm.cattle.io/v1alpha1", + &v1alpha1.HelmRelease{}, + func(c crd.CRD) crd.CRD { + return c. + WithColumn("Release Name", ".spec.release.name"). + WithColumn("Release Namespace", ".spec.release.namespace"). + WithColumn("Version", ".status.version"). + WithColumn("State", ".status.state") + }), } } @@ -88,17 +90,14 @@ func Create(ctx context.Context, cfg *rest.Config) error { // newCRD returns the CustomResourceDefinition of an object that is customized // according to the provided customize function -func newCRD(obj interface{}, customize func(crd.CRD) crd.CRD) crd.CRD { - crd := crd.CRD{ - GVK: schema.GroupVersionKind{ - Group: "helm.cattle.io", - Version: "v1alpha1", - }, - Status: true, - SchemaObject: obj, - } +func newCRD(namespacedType string, obj interface{}, customize func(crd.CRD) crd.CRD) crd.CRD { + crd := crd.NamespacedType(namespacedType). + WithSchemaFromStruct(obj). + WithStatus() + if customize != nil { crd = customize(crd) } + return crd } diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 84a9938d..054c74c5 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -18,7 +18,6 @@ import ( "github.com/sirupsen/logrus" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/client-go/rest" ) @@ -141,14 +140,18 @@ func objects(v1beta1 bool, crdDefs []crd.CRD) (crds []runtime.Object, err error) // List returns the list of CRDs and dependent CRDs for this operator func List() ([]crd.CRD, []crd.CRD) { crds := []crd.CRD{ - newCRD(&v1alpha1.ProjectHelmChart{}, func(c crd.CRD) crd.CRD { - return c. - WithColumn("Status", ".status.status"). - WithColumn("System Namespace", ".status.systemNamespace"). - WithColumn("Release Namespace", ".status.releaseNamespace"). - WithColumn("Release Name", ".status.releaseName"). - WithColumn("Target Namespaces", ".status.targetNamespaces") - }), + newCRD( + "ProjectHelmChart.helm.cattle.io/v1alpha1", + &v1alpha1.ProjectHelmChart{}, + func(c crd.CRD) crd.CRD { + return c. + WithColumn("Status", ".status.status"). + WithColumn("System Namespace", ".status.systemNamespace"). + WithColumn("Release Namespace", ".status.releaseNamespace"). + WithColumn("Release Name", ".status.releaseName"). + WithColumn("Target Namespaces", ".status.targetNamespaces") + }, + ), } crdDeps := append(helmcontrollercrd.List(), helmlockercrd.List()...) return crds, crdDeps @@ -165,15 +168,11 @@ func Create(ctx context.Context, cfg *rest.Config) error { return factory.BatchCreateCRDs(ctx, append(crds, crdDeps...)...).BatchWait() } -func newCRD(obj interface{}, customize func(crd.CRD) crd.CRD) crd.CRD { - crd := crd.CRD{ - GVK: schema.GroupVersionKind{ - Group: "helm.cattle.io", - Version: "v1alpha1", - }, - Status: true, - SchemaObject: obj, - } +func newCRD(namespacedType string, obj interface{}, customize func(crd.CRD) crd.CRD) crd.CRD { + crd := crd.NamespacedType(namespacedType). + WithSchemaFromStruct(obj). + WithStatus() + if customize != nil { crd = customize(crd) } From 2a654e1347fe1880a6b3564d07355dce772df683 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 24 Oct 2024 17:05:17 -0400 Subject: [PATCH 02/20] correct ambiguous variable names --- pkg/helm-project-operator/crd/crds.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 054c74c5..5123d890 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -52,11 +52,11 @@ func writeFiles(dirpath string, objs []runtime.Object) error { if err != nil { return err } - meta, err := meta.Accessor(o) + metaData, err := meta.Accessor(o) if err != nil { return err } - key := strings.SplitN(meta.GetName(), ".", 2)[0] + key := strings.SplitN(metaData.GetName(), ".", 2)[0] objMap[key] = data } @@ -121,17 +121,17 @@ func Objects(v1beta1 bool) (crds, crdDeps []runtime.Object, err error) { func objects(v1beta1 bool, crdDefs []crd.CRD) (crds []runtime.Object, err error) { for _, crdDef := range crdDefs { if v1beta1 { - crd, err := crdDef.ToCustomResourceDefinitionV1Beta1() + crdDefInstance, err := crdDef.ToCustomResourceDefinitionV1Beta1() if err != nil { return nil, err } - crds = append(crds, crd) + crds = append(crds, crdDefInstance) } else { - crd, err := crdDef.ToCustomResourceDefinition() + crdDefInstance, err := crdDef.ToCustomResourceDefinition() if err != nil { return nil, err } - crds = append(crds, crd) + crds = append(crds, crdDefInstance) } } return @@ -169,12 +169,12 @@ func Create(ctx context.Context, cfg *rest.Config) error { } func newCRD(namespacedType string, obj interface{}, customize func(crd.CRD) crd.CRD) crd.CRD { - crd := crd.NamespacedType(namespacedType). + newCrd := crd.NamespacedType(namespacedType). WithSchemaFromStruct(obj). WithStatus() if customize != nil { - crd = customize(crd) + newCrd = customize(newCrd) } - return crd + return newCrd } From f8f3cefa6f679788ecbd789571cae6529dc077df Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 24 Oct 2024 17:07:55 -0400 Subject: [PATCH 03/20] initial CRD filter implementation --- pkg/helm-project-operator/crd/crds.go | 37 ++++++++++++++++++++++ pkg/helm-project-operator/operator/init.go | 2 ++ 2 files changed, 39 insertions(+) diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 5123d890..458c30b9 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -3,7 +3,11 @@ package crd import ( "context" "fmt" + "github.com/rancher/wrangler/pkg/name" "io" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" "path/filepath" "strings" @@ -164,7 +168,19 @@ func Create(ctx context.Context, cfg *rest.Config) error { return err } + crdClientSet := factory.CRDClient.(*clientset.Clientset) crds, crdDeps := List() + // TODO: CRD updates topic, We could opt to not filter `crds` value and always install those? + err = filterMissingCRDs(crdClientSet, &crds) + if err != nil { + return err + } + + err = filterMissingCRDs(crdClientSet, &crdDeps) + if err != nil { + return err + } + return factory.BatchCreateCRDs(ctx, append(crds, crdDeps...)...).BatchWait() } @@ -178,3 +194,24 @@ func newCRD(namespacedType string, obj interface{}, customize func(crd.CRD) crd. } return newCrd } + +// filterMissingCRDs takes a list of expected CRDs and returns a filtered list of missing CRDs. +func filterMissingCRDs(apiExtClient *clientset.Clientset, expectedCRDs *[]crd.CRD) error { + for i := len(*expectedCRDs) - 1; i >= 0; i-- { + currentCRD := (*expectedCRDs)[i] + crdName := currentCRD.GVK.GroupVersion().WithKind(strings.ToLower(name.GuessPluralName(currentCRD.GVK.Kind))).GroupKind().String() + + // try to get the given CRD just to check for error, verifying if it exists + _, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crdName, metav1.GetOptions{}) + // TODO: Add debugging logs here to show: expected version of CRD vs Found version, if found CRD needs to be updated + if err == nil { + // Update the list to remove the current item since the CRD is in the cluster already + *expectedCRDs = append((*expectedCRDs)[:i], (*expectedCRDs)[i+1:]...) + } else if !errors.IsNotFound(err) { + *expectedCRDs = []crd.CRD{} + return fmt.Errorf("failed to check CRD %s: %v", crdName, err) + } + } + + return nil +} diff --git a/pkg/helm-project-operator/operator/init.go b/pkg/helm-project-operator/operator/init.go index 5278e601..d58420f4 100644 --- a/pkg/helm-project-operator/operator/init.go +++ b/pkg/helm-project-operator/operator/init.go @@ -27,6 +27,8 @@ func Init(ctx context.Context, systemNamespace string, cfg clientcmd.ClientConfi } clientConfig.RateLimiter = ratelimit.None + // TODO: Sort out how updating CRDs should be done if at all... + // IT could be a second phase, or maybe build it into Create? if err := crd.Create(ctx, clientConfig); err != nil { return err } From b40883484b2bd33e8161e637af382cd19aeffbe9 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 24 Oct 2024 17:23:03 -0400 Subject: [PATCH 04/20] Add debug logging for CRD install step --- pkg/helm-project-operator/crd/crds.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 458c30b9..deb41839 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -202,14 +202,24 @@ func filterMissingCRDs(apiExtClient *clientset.Clientset, expectedCRDs *[]crd.CR crdName := currentCRD.GVK.GroupVersion().WithKind(strings.ToLower(name.GuessPluralName(currentCRD.GVK.Kind))).GroupKind().String() // try to get the given CRD just to check for error, verifying if it exists - _, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crdName, metav1.GetOptions{}) - // TODO: Add debugging logs here to show: expected version of CRD vs Found version, if found CRD needs to be updated + foundCRD, err := apiExtClient.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crdName, metav1.GetOptions{}) + if err == nil { + logrus.Debugf( + "Found `%s` at version `%s`, expecting version `%s`", + crdName, + foundCRD.Status.StoredVersions[0], + currentCRD.GVK.Version, + ) + logrus.Debugf("Installing `%s` will be skipped; a suitible version exists on the cluster", crdName) // Update the list to remove the current item since the CRD is in the cluster already *expectedCRDs = append((*expectedCRDs)[:i], (*expectedCRDs)[i+1:]...) } else if !errors.IsNotFound(err) { *expectedCRDs = []crd.CRD{} return fmt.Errorf("failed to check CRD %s: %v", crdName, err) + } else { + logrus.Debugf("Did not find `%s` on the cluster", crdName) + logrus.Debugf("`%s` will be installed or updated", crdName) } } From e6361f8fa01842dde3d77f376a9d048455bfb4e4 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Fri, 25 Oct 2024 17:05:18 -0400 Subject: [PATCH 05/20] Add dev script for running helm-project-operator e2e locally --- scripts/dev-hpo-e2e | 60 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100755 scripts/dev-hpo-e2e diff --git a/scripts/dev-hpo-e2e b/scripts/dev-hpo-e2e new file mode 100755 index 00000000..ff63d6f6 --- /dev/null +++ b/scripts/dev-hpo-e2e @@ -0,0 +1,60 @@ +#!/usr/bin/env bash +set -e + +# Check if k3d binary exists +if ! command -v k3d &> /dev/null; then + echo "Error: k3d binary not found. Please install k3d before proceeding." + exit 1 +fi + +# Check if k3d binary exists +if ! command -v yq &> /dev/null; then + echo "Error: yq binary not found. Please install yq before proceeding." + exit 1 +fi + +export E2E_CI=true +export REPO=dev +export K3S_VERSION=v1.27.9-k3s1 +export CLUSTER_NAME=e2e-ci-helm-project-operator +export TAG=v0.0.0-dev.1 + +echo "Checking if pre-e2e cleanup is required..." +if k3d cluster list | grep -q $CLUSTER_NAME; then + echo "Cluster $CLUSTER_NAME already exists, deleting before creating new one..." + k3d cluster delete $CLUSTER_NAME +fi + +echo "Performing pre-e2e prebuild of binary and images." +BUILD_TARGET=helm-project-operator ./scripts/build; +BUILD_TARGET=helm-project-operator ./scripts/package; + +echo "Creating cluster..." && \ +/usr/bin/env bash ./.github/workflows/e2e/scripts/setup-cluster.sh && \ + +echo "Import Images Into k3d" && \ +k3d image import ${REPO}/helm-project-operator:${TAG} -c "$CLUSTER_NAME" && \ + +echo "Setup kubectl context" && \ +kubectl config use-context "k3d-$CLUSTER_NAME" && \ + +echo "Install Helm Project Operator" && \ +/usr/bin/env bash ./.github/workflows/e2e/scripts/hpo-install-helm-project-operator.sh && \ + +echo "Check if Helm Project Operator is up" && \ +/usr/bin/env bash ./.github/workflows/e2e/scripts/hpo-validate-helm-project-operator.sh && \ + +echo "Check if Project Registration Namespace is auto-created on namespace detection" && \ +/usr/bin/env bash ./.github/workflows/e2e/scripts/hpo-create-project-namespace.sh && \ + +echo "Deploy Example Chart via ProjectHelmChart CR" && \ +/usr/bin/env bash ./.github/workflows/e2e/scripts/hpo-create-projecthelmchart.sh; + +echo "Delete Example Chart" +/usr/bin/env bash ./.github/workflows/e2e/scripts/hpo-delete-projecthelmchart.sh; + +echo "Uninstall Helm Project Operator" +/usr/bin/env bash ./.github/workflows/e2e/scripts/hpo-uninstall-helm-project-operator.sh; + +echo "Delete k3d cluster" +k3d cluster delete "$CLUSTER_NAME"; \ No newline at end of file From aad1df7b373b4d854179f8c234a338c6106ab06a Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Dec 2024 14:40:58 -0500 Subject: [PATCH 06/20] go mod tidy && goimports -l -w --- cmd/helm-project-operator/main.go | 5 +++-- go.mod | 2 +- pkg/helm-project-operator/crd/crds.go | 9 +++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/cmd/helm-project-operator/main.go b/cmd/helm-project-operator/main.go index 22bf2645..de860ef4 100644 --- a/cmd/helm-project-operator/main.go +++ b/cmd/helm-project-operator/main.go @@ -4,12 +4,13 @@ package main import ( _ "embed" - "github.com/rancher/prometheus-federator/pkg/helm-project-operator/controllers/common" - "github.com/rancher/prometheus-federator/pkg/helm-project-operator/operator" "log" "net/http" _ "net/http/pprof" + "github.com/rancher/prometheus-federator/pkg/helm-project-operator/controllers/common" + "github.com/rancher/prometheus-federator/pkg/helm-project-operator/operator" + "github.com/rancher/prometheus-federator/pkg/version" command "github.com/rancher/wrangler-cli" _ "github.com/rancher/wrangler/pkg/generated/controllers/apiextensions.k8s.io" diff --git a/go.mod b/go.mod index 3854395e..b1fa2201 100644 --- a/go.mod +++ b/go.mod @@ -24,6 +24,7 @@ require ( gopkg.in/yaml.v2 v2.4.0 helm.sh/helm/v3 v3.8.0 k8s.io/api v0.23.3 + k8s.io/apiextensions-apiserver v0.23.1 k8s.io/apimachinery v0.23.3 k8s.io/client-go v0.23.3 ) @@ -82,7 +83,6 @@ require ( gopkg.in/gorp.v1 v1.7.2 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect - k8s.io/apiextensions-apiserver v0.23.1 // indirect k8s.io/code-generator v0.23.3 // indirect k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect k8s.io/klog v1.0.0 // indirect diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index deb41839..4be675d9 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -3,16 +3,17 @@ package crd import ( "context" "fmt" - "github.com/rancher/wrangler/pkg/name" "io" - "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" "path/filepath" "strings" "sync" + "github.com/rancher/wrangler/pkg/name" + "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/rancher/prometheus-federator/pkg/helm-project-operator/apis/helm.cattle.io/v1alpha1" helmcontrollercrd "github.com/k3s-io/helm-controller/pkg/crd" From 6aa9a9b0d3fc4a0ba07052f54178953bc33c5f69 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Dec 2024 15:26:13 -0500 Subject: [PATCH 07/20] Add MANAGE_CRD_UPDATES env to HPO/PromFed --- cmd/helm-project-operator/main.go | 6 ++ cmd/prometheus-federator/main.go | 14 +++- .../controllers/common/operator.go | 3 + pkg/helm-project-operator/crd/crds.go | 74 ++++++++++++------- pkg/helm-project-operator/operator/init.go | 5 +- 5 files changed, 66 insertions(+), 36 deletions(-) diff --git a/cmd/helm-project-operator/main.go b/cmd/helm-project-operator/main.go index de860ef4..b5d05549 100644 --- a/cmd/helm-project-operator/main.go +++ b/cmd/helm-project-operator/main.go @@ -35,6 +35,7 @@ var ( base64TgzChart string debugConfig command.DebugConfig + updateCRDs bool = false ) type DummyOperator struct { @@ -54,6 +55,10 @@ func (o *DummyOperator) Run(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() + if os.Getenv("MANAGE_CRD_UPDATES") == "true" { + updateCRDs = true + } + if err := operator.Init(ctx, o.Namespace, cfg, common.Options{ OperatorOptions: common.OperatorOptions{ HelmAPIVersion: DummyHelmAPIVersion, @@ -61,6 +66,7 @@ func (o *DummyOperator) Run(cmd *cobra.Command, _ []string) error { SystemNamespaces: DummySystemNamespaces, ChartContent: base64TgzChart, Singleton: false, + UpdateCRDs: updateCRDs, }, RuntimeOptions: o.RuntimeOptions, }); err != nil { diff --git a/cmd/prometheus-federator/main.go b/cmd/prometheus-federator/main.go index 804dce44..6dabe90c 100644 --- a/cmd/prometheus-federator/main.go +++ b/cmd/prometheus-federator/main.go @@ -4,10 +4,6 @@ package main import ( _ "embed" - "log" - "net/http" - _ "net/http/pprof" - "github.com/rancher/prometheus-federator/pkg/helm-project-operator/controllers/common" "github.com/rancher/prometheus-federator/pkg/helm-project-operator/operator" "github.com/rancher/prometheus-federator/pkg/version" @@ -16,6 +12,10 @@ import ( _ "github.com/rancher/wrangler/pkg/generated/controllers/networking.k8s.io" "github.com/rancher/wrangler/pkg/kubeconfig" "github.com/spf13/cobra" + "log" + "net/http" + _ "net/http/pprof" + "os" ) const ( @@ -35,6 +35,7 @@ var ( base64TgzChart string debugConfig command.DebugConfig + updateCRDs bool = false ) type PrometheusFederator struct { @@ -55,6 +56,10 @@ func (f *PrometheusFederator) Run(cmd *cobra.Command, _ []string) error { ctx := cmd.Context() + if os.Getenv("MANAGE_CRD_UPDATES") == "true" { + updateCRDs = true + } + if err := operator.Init(ctx, f.Namespace, cfg, common.Options{ OperatorOptions: common.OperatorOptions{ HelmAPIVersion: HelmAPIVersion, @@ -62,6 +67,7 @@ func (f *PrometheusFederator) Run(cmd *cobra.Command, _ []string) error { SystemNamespaces: SystemNamespaces, ChartContent: base64TgzChart, Singleton: true, // indicates only one HelmChart can be registered per project defined + UpdateCRDs: updateCRDs, }, RuntimeOptions: f.RuntimeOptions, }); err != nil { diff --git a/pkg/helm-project-operator/controllers/common/operator.go b/pkg/helm-project-operator/controllers/common/operator.go index df17662e..9db079fc 100644 --- a/pkg/helm-project-operator/controllers/common/operator.go +++ b/pkg/helm-project-operator/controllers/common/operator.go @@ -26,6 +26,9 @@ type OperatorOptions struct { // the name provided on the ProjectHelmChart, which is what triggers an UnableToCreateHelmRelease status // on the ProjectHelmChart created after this one Singleton bool + + // UpdateCRDs determines if the controller should update CRDs (which it's responsible for) that already exist in the cluster + UpdateCRDs bool } // Validate validates the provided OperatorOptions diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 4be675d9..005f8e0a 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -11,7 +11,7 @@ import ( "github.com/rancher/wrangler/pkg/name" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" - "k8s.io/apimachinery/pkg/api/errors" + apimachineerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/rancher/prometheus-federator/pkg/helm-project-operator/apis/helm.cattle.io/v1alpha1" @@ -35,14 +35,14 @@ import ( // i.e. if you uninstall the HelmChart CRD, it can destroy an RKE2 or K3s cluster that also uses those CRs // to manage internal Kubernetes component state func WriteFiles(crdDirpath, crdDepDirpath string) error { - objs, depObjs, err := Objects(false) + objs, crdLockerDeps, crdControllerDeps, err := Objects(false) if err != nil { return err } if err := writeFiles(crdDirpath, objs); err != nil { return err } - return writeFiles(crdDepDirpath, depObjs) + return writeFiles(crdDepDirpath, append(crdLockerDeps, crdControllerDeps...)) } func writeFiles(dirpath string, objs []runtime.Object) error { @@ -88,14 +88,17 @@ func writeFiles(dirpath string, objs []runtime.Object) error { // Print prints CRDs to out and dependent CRDs to depOut func Print(out io.Writer, depOut io.Writer) { - objs, depObjs, err := Objects(false) + objs, crdLockerDeps, crdControllerDeps, err := Objects(false) if err != nil { logrus.Fatalf("%s", err) } if err := printCrd(out, objs); err != nil { logrus.Fatalf("%s", err) } - if err := printCrd(depOut, depObjs); err != nil { + if err := printCrd(depOut, crdLockerDeps); err != nil { + logrus.Fatalf("%s", err) + } + if err := printCrd(depOut, crdControllerDeps); err != nil { logrus.Fatalf("%s", err) } } @@ -110,17 +113,22 @@ func printCrd(out io.Writer, objs []runtime.Object) error { } // Objects returns runtime.Objects for every CRD or CRD Dependency this operator relies on -func Objects(v1beta1 bool) (crds, crdDeps []runtime.Object, err error) { - crdDefs, crdDepDefs := List() +func Objects(v1beta1 bool) (crds []runtime.Object, crdLockerDeps []runtime.Object, crdControllerDeps []runtime.Object, err error) { + crdDefs, helmLockerCrdDefs, helmControllerCrdDefs := List() crds, err = objects(v1beta1, crdDefs) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - crdDeps, err = objects(v1beta1, crdDepDefs) + crdLockerDeps, err = objects(v1beta1, helmLockerCrdDefs) if err != nil { - return nil, nil, err + return nil, nil, nil, err } - return + crdControllerDeps, err = objects(v1beta1, helmControllerCrdDefs) + if err != nil { + return nil, nil, nil, err + } + + return crds, crdLockerDeps, crdControllerDeps, nil } func objects(v1beta1 bool, crdDefs []crd.CRD) (crds []runtime.Object, err error) { @@ -143,7 +151,7 @@ func objects(v1beta1 bool, crdDefs []crd.CRD) (crds []runtime.Object, err error) } // List returns the list of CRDs and dependent CRDs for this operator -func List() ([]crd.CRD, []crd.CRD) { +func List() ([]crd.CRD, []crd.CRD, []crd.CRD) { crds := []crd.CRD{ newCRD( "ProjectHelmChart.helm.cattle.io/v1alpha1", @@ -158,31 +166,42 @@ func List() ([]crd.CRD, []crd.CRD) { }, ), } - crdDeps := append(helmcontrollercrd.List(), helmlockercrd.List()...) - return crds, crdDeps + return crds, helmlockercrd.List(), helmcontrollercrd.List() } // Create creates all CRDs and dependent CRDs in the cluster -func Create(ctx context.Context, cfg *rest.Config) error { +func Create(ctx context.Context, cfg *rest.Config, shouldUpdateCRDs bool) error { factory, err := crd.NewFactoryFromClient(cfg) if err != nil { return err } crdClientSet := factory.CRDClient.(*clientset.Clientset) - crds, crdDeps := List() - // TODO: CRD updates topic, We could opt to not filter `crds` value and always install those? - err = filterMissingCRDs(crdClientSet, &crds) - if err != nil { - return err - } + crdDefs, helmLockerCrdDefs, helmControllerCrdDefs := List() + // When updateCRDs is true we will skip filtering the CRDs, in turn all CRDs will be re-installed. + if !shouldUpdateCRDs { + err = filterMissingCRDs(crdClientSet, &crdDefs) + if err != nil { + return err + } - err = filterMissingCRDs(crdClientSet, &crdDeps) - if err != nil { - return err + err = filterMissingCRDs(crdClientSet, &helmLockerCrdDefs) + if err != nil { + return err + } + + err = filterMissingCRDs(crdClientSet, &helmControllerCrdDefs) + if err != nil { + return err + } + } else { + logrus.Debug("UpdateCRDs is Enabled; all CRDs will be installed.") } - return factory.BatchCreateCRDs(ctx, append(crds, crdDeps...)...).BatchWait() + crdDefs = append(crdDefs, helmLockerCrdDefs...) + crdDefs = append(crdDefs, helmControllerCrdDefs...) + + return factory.BatchCreateCRDs(ctx, crdDefs...).BatchWait() } func newCRD(namespacedType string, obj interface{}, customize func(crd.CRD) crd.CRD) crd.CRD { @@ -215,12 +234,11 @@ func filterMissingCRDs(apiExtClient *clientset.Clientset, expectedCRDs *[]crd.CR logrus.Debugf("Installing `%s` will be skipped; a suitible version exists on the cluster", crdName) // Update the list to remove the current item since the CRD is in the cluster already *expectedCRDs = append((*expectedCRDs)[:i], (*expectedCRDs)[i+1:]...) - } else if !errors.IsNotFound(err) { + } else if !apimachineerrors.IsNotFound(err) { *expectedCRDs = []crd.CRD{} return fmt.Errorf("failed to check CRD %s: %v", crdName, err) } else { - logrus.Debugf("Did not find `%s` on the cluster", crdName) - logrus.Debugf("`%s` will be installed or updated", crdName) + logrus.Debugf("Did not find `%s` on the cluster, it will be installed", crdName) } } diff --git a/pkg/helm-project-operator/operator/init.go b/pkg/helm-project-operator/operator/init.go index d58420f4..1fd329d4 100644 --- a/pkg/helm-project-operator/operator/init.go +++ b/pkg/helm-project-operator/operator/init.go @@ -7,7 +7,6 @@ import ( "github.com/rancher/prometheus-federator/pkg/helm-project-operator/controllers" "github.com/rancher/prometheus-federator/pkg/helm-project-operator/controllers/common" "github.com/rancher/prometheus-federator/pkg/helm-project-operator/crd" - "github.com/rancher/wrangler/pkg/ratelimit" "k8s.io/client-go/tools/clientcmd" ) @@ -27,9 +26,7 @@ func Init(ctx context.Context, systemNamespace string, cfg clientcmd.ClientConfi } clientConfig.RateLimiter = ratelimit.None - // TODO: Sort out how updating CRDs should be done if at all... - // IT could be a second phase, or maybe build it into Create? - if err := crd.Create(ctx, clientConfig); err != nil { + if err := crd.Create(ctx, clientConfig, opts.UpdateCRDs); err != nil { return err } From f5190f17b904895f84586d6cc3e7e9aeff78bedb Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Dec 2024 15:27:37 -0500 Subject: [PATCH 08/20] bump prom-fed chart version rc --- packages/prometheus-federator/charts/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/prometheus-federator/charts/Chart.yaml b/packages/prometheus-federator/charts/Chart.yaml index ebde72aa..3f7867a4 100755 --- a/packages/prometheus-federator/charts/Chart.yaml +++ b/packages/prometheus-federator/charts/Chart.yaml @@ -15,4 +15,4 @@ dependencies: description: Prometheus Federator icon: https://raw.githubusercontent.com/rancher/prometheus-federator/main/assets/logos/prometheus-federator.svg name: prometheus-federator -version: 0.4.4 +version: 0.4.5-rc.1 From 8ef08a60cf59a7f06541f6201fb0307a55d93003 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Mon, 9 Dec 2024 15:27:49 -0500 Subject: [PATCH 09/20] Add update CRDs to values file --- .../helm-project-operator/charts/templates/deployment.yaml | 4 ++++ packages/helm-project-operator/charts/values.yaml | 7 +++++++ packages/prometheus-federator/charts/values.yaml | 7 +++++++ 3 files changed, 18 insertions(+) diff --git a/packages/helm-project-operator/charts/templates/deployment.yaml b/packages/helm-project-operator/charts/templates/deployment.yaml index 1822ea3f..b31bf390 100644 --- a/packages/helm-project-operator/charts/templates/deployment.yaml +++ b/packages/helm-project-operator/charts/templates/deployment.yaml @@ -99,6 +99,10 @@ spec: value: {{ .Values.hardenedNamespaces.configuration | toYaml | sha256sum }} - name: VALUES_OVERRIDE_SHA_256_HASH value: {{ .Values.valuesOverride | toYaml | sha256sum }} + {{- if .Values.crdManagement.update }} + - name: MANAGE_CRD_UPDATES + value: "true" + {{- end }} {{- if .Values.resources }} resources: {{ toYaml .Values.resources | nindent 12 }} {{- end }} diff --git a/packages/helm-project-operator/charts/values.yaml b/packages/helm-project-operator/charts/values.yaml index a456b1c0..7dad4847 100644 --- a/packages/helm-project-operator/charts/values.yaml +++ b/packages/helm-project-operator/charts/values.yaml @@ -128,6 +128,13 @@ namespaceOverride: "" replicas: 1 +# Configure how the operator will manage the CustomResourceDefinitions (CRDs) it needs to function. +crdManagement: + # Enable or disable automatic updates of CRDs during startup. + # When true, all CRDs will be udpated to the version the operator provides. + # When false, only missing CRDs will be installed, and existing ones will not be updated. + update: true + image: registry: ghcr.io repository: rancher/prometheus-federator/helm-project-operator diff --git a/packages/prometheus-federator/charts/values.yaml b/packages/prometheus-federator/charts/values.yaml index 53f54248..7bb63e51 100755 --- a/packages/prometheus-federator/charts/values.yaml +++ b/packages/prometheus-federator/charts/values.yaml @@ -37,6 +37,13 @@ helmProjectOperator: nameOverride: prometheus-federator + # Configure how the operator will manage the CustomResourceDefinitions (CRDs) it needs to function. + crdManagement: + # Enable or disable automatic updates of CRDs during startup. + # When true, all CRDs will be udpated to the version the operator provides. + # When false, only missing CRDs will be installed, and existing ones will not be updated. + update: true + helmController: # Note: should be disabled for RKE2 clusters since they already run Helm Controller to manage internal Kubernetes components enabled: true From 89263eaf4a11debf70da82ae5387952231207d3c Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Wed, 11 Dec 2024 16:43:21 -0500 Subject: [PATCH 10/20] make charts --- .../prometheus-federator-0.4.5-rc.1.tgz | Bin 0 -> 20990 bytes .../0.4.5-rc.1/Chart.yaml | 18 ++ .../prometheus-federator/0.4.5-rc.1/README.md | 120 +++++++++ .../0.4.5-rc.1/app-README.md | 27 ++ .../charts/helmProjectOperator/Chart.yaml | 18 ++ .../charts/helmProjectOperator/README.md | 77 ++++++ .../charts/helmProjectOperator/app-readme.md | 20 ++ .../charts/helmProjectOperator/questions.yaml | 43 ++++ .../helmProjectOperator/templates/NOTES.txt | 2 + .../templates/_helpers.tpl | 73 ++++++ .../templates/cleanup.yaml | 82 ++++++ .../templates/clusterrole.yaml | 57 +++++ .../templates/configmap.yaml | 14 ++ .../templates/deployment.yaml | 130 ++++++++++ .../helmProjectOperator/templates/psp.yaml | 68 +++++ .../helmProjectOperator/templates/rbac.yaml | 32 +++ .../system-namespaces-configmap.yaml | 62 +++++ .../templates/validate-psp-install.yaml | 7 + .../charts/helmProjectOperator/values.yaml | 236 ++++++++++++++++++ .../0.4.5-rc.1/questions.yaml | 43 ++++ .../0.4.5-rc.1/templates/NOTES.txt | 3 + .../0.4.5-rc.1/templates/_helpers.tpl | 66 +++++ .../0.4.5-rc.1/values.yaml | 100 ++++++++ index.yaml | 22 ++ 24 files changed, 1320 insertions(+) create mode 100644 assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz create mode 100644 charts/prometheus-federator/0.4.5-rc.1/Chart.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/README.md create mode 100644 charts/prometheus-federator/0.4.5-rc.1/app-README.md create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/Chart.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/README.md create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/app-readme.md create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/questions.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/NOTES.txt create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/_helpers.tpl create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/cleanup.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/clusterrole.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/configmap.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/psp.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/rbac.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/system-namespaces-configmap.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/validate-psp-install.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/questions.yaml create mode 100644 charts/prometheus-federator/0.4.5-rc.1/templates/NOTES.txt create mode 100644 charts/prometheus-federator/0.4.5-rc.1/templates/_helpers.tpl create mode 100644 charts/prometheus-federator/0.4.5-rc.1/values.yaml diff --git a/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz b/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz new file mode 100644 index 0000000000000000000000000000000000000000..2e40291e8ce16df8f2578bbbd495ceb235f35d8d GIT binary patch literal 20990 zcmV)gK%~DPiwG0|00000|0w_~VMtOiV@ORlOnEsqVl!4SWK%V1T2nbTPgYhoO;>Dc zVQyr3R8em|NM&qo0PMYMcN@8tD4NgpD{!cuYf&SXB+HK(I^A;>S)C~9wzVX8a%VYN zO|S|i5fuv+0415Ryw>@C{^j{4cVTY;g?f>c?AV=*xso`B*zZ>%#ggd-E7fSm zQYNVu^5AqqrH)s0nf>Zhd=3u}51&1G0{-V zXPl*DB59s1nC!eQ<*LZ&YLwF@8t{5RCPXPZdV0O*qGqO(Ng4G3(52ONUfA+ z%g*P78k3CY<-7e`o~Pngb>3Alx#U`nw!B-48=f*Xn%~GVS+Hzb{TLTg{FNozTulsi z9NZkytXR;a&Y>jB7*)(dqYK9(TJS$Iskq3;_;(+$l^ral=1*g*fE9UmVx>Ho8%r(fy+=lJZ9muyDMOcUI6 zG7}Pr%*ax_k+4J~j&{hK1y_VCLdk!h{P@FYCghT8%~Bvb=7yIn$*5%HhDuJS8B;_H zGG(MNNS_j(Ye80`lti;-kx|W597Q{w6o;IOe8%Uc1nv|?^Gr->W|@@EnE6IXp$hy$ z2+PeI(=nOROfk5K9K^2hlFhl&hC~hq_$;pQ2Rdb$p_?pA>+Hb%dY;0)NmeS&M(bWx?Y*ta@ z>vGCu&I~jSD;9}5NJO5nLaT#9WIS0N6e1ldmXwm~)yQ7kp+!OE68OQV99FR;5zFFJ zkGfKc&itV63$bsD-2*Z3<+%PDnq}gar593|llsa?9b@un@6r86N%0^Bqm0R;R3z6- zy5Fi19dN55r%Z1(qAYXGm#i8xa!_QFMw|#UumhN}aO#67>X1^#@D$5cDGeQ2P)%r-5y_M&Wx^CmB%_A-Ojkse zQxJEET8LXx7KEy9*?|CYI|+oZO5hBT-%EE9fmy%4VN&w6Pa=VtI(o&3nS)+1;z%dC zi2F(^;7(=EvBOKAHXq!z@{@ zk+$p*BrWDRq+G#QT57SRnkS?zQbT2lm=QBFNlUX*Vwj#Ti`b6vCv#j3VcG`}!<{f^ zbDq)ypsWQ<-3njz^*PbGHXe}SeIoK~MV4GC^WNIXJXe}#8B6yG%~QhOaiz^oB4_rd zTxggAEF#6x@efN!?S7t$TuYH=)%&Gok<>ye1+ij3x_WktSrJSDGf*cK(1ADJwFuGDpTdM)PXpGIqliyR=71F(4tpVZ zu8BqUYE4WTv4Pyu9De~BqHpPlHTF$pCdJaf?Kdl8sp>wya!GZv0N673OATBza)}BJ z`O(t}JDR3)Zs?o+bL8~nP=&3iMck*~#_A>+|L5U<9!8FM;)v3VD~}%83zd?$PRojL za&!Nnp|lskMaSfP_Dbjr$rQ^qlC6_81=>S1Lgl97`a{WWI z6%s@%9QcjM7z&gq>6XfzZ@qx~iQ#g3OGfEsmLwEY4`7H9&C`+gukw?sE`?n2ni-Mw znym(Kfq3j3lfl{FN}9RrfKg*IfPdXZHk+|TkICSbxUzs8L|_uIY|;ysJ0#;v?hdSZ zcFC4-nw}mW{>c5+q$tPa=D2$d za(3|Q^_#P+SijRB^NtPr|Ka23P5bZj!>3>M|IhMy|DJpkqrKl~ALPRavY?7g8OtmA zEGDN5mRu9Am605Esf^4m%RJ}#+)xEW9}}IC(a0Ng$@}+S$tQzOGoQbAKw|UaInzer zRb-Trfi7gP+)6@(4AgqBL2;LzO0@Z$G zZke)7+}g{5*k%OdTOqGAK~tmjS?#hCVL?(BiiIOadk1EkA%Nl65y-B==m=jY5r*Eb zK~#x<@T|GeDf)GZk2PrZOhZ{=xwglOEpM1S70bd1>Vsb&#Yay@hXWFy(t=KT#x-Xu zHfpdnr^LVI+J1#`I!s>P8Hdx#6oWh0-qgM8py4_t(EGMv@5v2cjzLeJ@r>zJ!M*cBOvDqd3p$6 z`SJ=TPX?nw4TN|S4)&rGBaOqVEypDl3r8)`s{Sd<0acb|rg^~UD{Wa3o|8l@r#xpV z0@0rRtyXJA0W(WN8brqb?iY%;;f>hRE*6XdL7 zRH6NKx~dKm$>@}2@s|4zk4QGFwo9@iGqW4~`+y979=J2$(CjXts-u5XgW4JC+H&$+ zmZNE8N)=CVdTPt*;mrdx7%hqhY9mslqv>jF#6J(=D<*Gv!nz*d0 zzk~PhH^Ih;itP~!D)mUr##@T0D4j;IG21*-?I1j7(F&eyNV0e2Z>7+%&IOZP(|PkY z2f5(R24*B;xfD70uoV1CwD@nj%sOu%_2UqH{nB#C*Uy*vbjtsiXD2UzJd2m8aKGlX06z%w8aFkb&v9xfb)aj+`Yp|G)X%vxK=7;wRp;shBy@}Hx;7k|37rJuXWx{- zLJe{%&*ulA%IkwrZvEVo{;i}ln$ymK%$~boH7yxCSW=^|9Vl(Y<%2msIwZTe)hs19 zoSFl{mQ$9dEDd(vyvzl(R13K*Gp0aJv-2P+Rg0;hGF7B@bQ6(TPlC|}4C^d&u2-X^ zl;8xdoX;TNzcqqI;N;mFP>tsf!g*VsZx5<<``uEVEUcHxLcz}^ioPRzd*@4|`qErm z-)Fb8#vqL1^Bh&xX)qi$%8Ei4U*(JKI1QnHjOT=U{Te2^pvt>Zy#cZ5J@P}HUcOZE z-d+^d9doQAJw6m6g$?AnW_h}9_HWy6xN2MMZ?G?bB#!XhJdU3ou>2r!&w2bAIxWwU z!LF4A;3KCNAi;eAo0`M6Nr)PY4PBUCuNkRC6f7l6rX^48A)I>9#Sw{i3`J+7a|FG0 zto)=Jds&?b&}p5I(?GZlz9>4Mf%}z8=B&Qxfpt4Ca`M)W(GBY%4DHUvg5LZ?_Nh7! z!Uvk!DeiZ!{*m&UbwCxw?bYc=-?$DUEIz$H}?fuh+B4J<^kLW{GMXT zNG>2cBZrugTcd;RVNSs6OkO$zcFfW}d@ zv$I3aAlwE~u*YE?7znr`pnnBE;w!wzo$_6S8bt9W+BooOsOGzqjbAL}oH%x8lhVrU&j* zLu+uFGqbdOC2~gCyMz_m@BT@;gbwy1mjGE}%!&(TI`Modjgew9T zE-i?JdqkLBPDhblcEfU`*}h^L&YG9WGZ2baIIK2i=r!tkXp~Zz_?yXDG11583wvmx(Y%?NCJ^?{%Cm4 zX2gt2MzeHypK$$1RWuR-TM#E=2mU2W6SD`@TzxY4A++8bkiL za;L=5E?vqTWQToZzSNwXTe@F79J(-okB!OaCj8!VZu zJsuMqg1IESJhu{QLX{0&^g)<u3~ZeYo0))e8j3ed364gI&7!p>Jyg93%sRWpo1x z5)Nq!4$2U%@2sL?h09bJS_%<-FuL~B zpwoa9Vr}1WoKQ-tg=zYk49$4J@lm4f*kTyeprsk7EGqf3LnBo$@J&<$dKsoeZk?yM zL3<6!*oOWfR=#Q-ns2o%*`zw~W+$|8E|^YBfH{(GYjTmdgmSxBqnvolqI?#fnjxEc}WlE)3XWfituKU-Z%WM(_xz(H|f0TS*)a zUIVt0EyWEo^(quv$QtX0{kF4p(WIauJN)Z_jy=*9kHwkS_-1px70R_2zU!gY#?RsU zK{d5#yKdCH%(6+{(abf0nCNb|?HWC1-E@kqob&t_F_m0E&awP_lK~O>JU4Vp*&L0F zpU8oYbhVMQE&V6D*7xkJd2^<;msZ?bEbTIy z^)wSk2%iznm+S?KY+Pab)6DKORU%j9cCo6_u6EqMHSasK0F|Q5Q}X1uzkx9SlOG#` z25Qi~f?W`^ia2DGusl_S<(f;Dt=v8t+18Ny#PI74_)|@N@xZs;n58s=Vlk6o!dCRWWyI`f)!D^ zp^`I23MO${U|BGSoTS21)heY;$m{=#qNA9+&I8kv_I|^CE4{<+p*U!F%E$z#=%+iq zL~JJ$vl($cH*bQ{wjh(WIeHVa3*y6dE^Z+LXV5@CHE;*Q(S)DQhltjDd+uHvYKXSE zDkR7o1)Wp!*F61hGThsXN%R=jt{7lfXdUqjJiXlSNCd2FUkJOvL)kRUxdT%(U_CNe zy`b65EiGILGQJIq2-u)mPvr+gp$%;rrmW+O$$0^egtwvu$Y!v|=m9MMuQ za`^Ax59E|42Rp%lBN%Wbq2{l88QRK0_;-% z3p9@|{rR62}nVPS)j9 zPVm)aVq+AX1;r85fR3(>ZRrfPnr32(WpOr+J|w$n?5V6wF6`719L?K_+HSjjKt?no zKL~N{6=6JGKhgq@aN5mnoCl7z> z_FaRFtrwCplJXglcTJMaaK@QY8c~vMQ_vdLWkOtoi^-gZv61FBaOSaB!Ri*`_Rwwr zDvRgwIwjmj+*gCvPKOco1If{xia;o_?$}Bl2yO+hlp{F-Cct9zY}NGJTA{w-*3-4Q z#d~{3{(HFmSO+9z^l#X!TbrsLI5WdV0)sIiWaLt0>^B@VO9Yf%VC_0GsiMeM?bQX= z;HeER0L}o!M3#EtiH9KqBQK4CoYOEHtPx8MX$PSqWt#F#p{y|s!HV8mvP;d@3|n4X zvddDghbXaKGMbKHL;bHt)*EiMp?iC=*Amz7X(ivm;w#mv$aW-6gEkufv#JZ_bZg28E$H} zS%Zb97APX7VCc8%F}%_yM!achNVzb>!1-4-XE=m>Wz4+1%rmo*$nOF3^Kgf1?*L(J zZl;7pC`7%?%(mw0g69h7V{iG|nrJd*WJ%?9wKDI{8`O&kc9Zv!ki~-LEFFx=K+BR1 zyiwC&70vAaa4-B?2n>k+gV5*TN5-2BDiy*sGh6WTfId}2=C2&T6oUD}NPGLty zo*3LFS7G5RIF|TCHEpxc%4p)0q%2=$T@WHjWwk-@1%j&f4GNg^m>6LRi81OWc@(Lj90F_weH!Me? zpQ?=#42WL2(2UTjxM9OilGY1cTf7Pgo2~M-UefdWshhYBaNGb9(7fPyUV78Su7~h` zDE|+msAh+5Z^(Xs38M}hqYGfjQ(w33R*+=Dv(&{jROXT(A*IvwQQc{%ODXelG$JQ; zGpkR>XeKwjy*^eWCp;Ks2n?y6YGm_Hw7Cu)P4fz+rS*+gcw;&VOFIf$KiB1+}5f*=oMb$psJw1U>Svf*70 z+X4?pKfcwBF%Yl5h0zA@dmyTrhf!D#S+Xo7TRY>Ie0|TZ-ic0PnK#%3_9d@Mzk=Oax?x1(I-KIgP2u`57oK!mr_*DtbNsp z^|M$+Esz|dENUrE(Q7N z?ScVlz=a`2#xB`xQmN_Y|8_(ys;aPpE!vQ2xWbC@LOqkFL8wdbvZOBrNSst7Ub2~r zL9KP1;~@hW^*7)&4;jLc*|r}0?h1)~}YYUm8VVcE)=zNqy- zeIbv15a|SUj%~OL*h~P0-15IvM1l9d0hpnPiu1zN=!1cHol-m4dqc>?NBVgbG-vru z&{*Jn*1!&X{5N9QjAz=RmMW(}-X<;z#9@`=l|KP(!jKVB7Ru&dI_;=w$4W%5v@8=% zXh=|CIPjdpGB!F-JkENR%qi7owLUJaB5#>ZLK-19wY>x*)Ef;EhVAMs$TQjg`H|1Q z7#Xc+Onwc>bZ0Q>&Y;kDlXl+sWQ}*7>~_Tg)ItinnhBQcBnxZ&uyaH8*zj5)gg}Zw9_Vm7chOxZ$E%lApWG2B-UgAj zuLI4&2j*;1H@?!)dW*!P)cc}Q^<5OUVL@f&wDy~hXrs3EW!PaX4hqfjS>yG-%Dk)u zl$d-EURJ{1EoqUlebAX)^My3*YZGLUOkDhPYzw$g$ZifS6SOURBT!7Pd|MnqoA#so z4XA{V04Leo%Z1+Cvk}4x)hwsryX)KIEiBq<5&G*SzV@9sbHQ-{aD+9YX zoI0;*9(JD@#zwSgW-inV1VZ?z6FV@5xOb)Nor!nkbu)#y?Sirk(S~xMzp+)K0E67| zKHE;hvJ`WGLiW>eAzP!a(Gw;c90Osm3KTKYb%8GX-I;vAO`tH0Px_P0D6<()xS_Px z%S=^ARueVqf0peN!*OK_wjMFF`v_R#wy}Rhx&&&^!zXv-Pfp`#i8N>|N9r2dx|?UP zn(#fQ(3I&>jQBIi5r4LV#9gS*D9j!Uy5*!?CFYb8C(H$jq&3Dh6LK^2a)Mu_DufnS zcR$P|CRfY?2KiWL9Bto*ET~Lb&e9Io%bMLNf>J@Xib5v|q!QSGdw9tFUH|osxMr>t zKgQnqTuwKft-F9NC5R2pa^}>~)KRmfyl7Kp)k?!h$GeMK3|$S7fdDur8ND{b&G{8M zijSTV2PLA6cybR|)=Iuunp!Ud(;eB`16Fe?>H$G@K#3fIgw}v+WxX@5HMZ&AdAwM& zUw1o!h%2+{obtUIP)x4+yXGAH;aF9jbEaVIAvZ_y$aUAR5WuIl4tb*T?|dw%}O{QtZOBH8?x0{P%(7-7mbdW@k_`EsD`6tO2>9{`=FX zPoFjWKR$W>wg2yD`D{`D(HDR0E5X@@`7;kJ0El7fT%l>8C1~5k-YP@Hh-YAY zqRy;S69CmdDNvd>;6v+%XN(g}kNpW;;__u=P_6AuCrgNbs<6{AFQO2R;5*fd7NCF$HeBeNTxs&-rKX1UAb;;dwWpb z4^;k{WNP7}1<(cB0);Q%=Q4w>dW#NiG@Y8QGDn-|BaC^l0S7*d$!;5HFpZE~RaAj# zJ|G^|7(OFhSB;Gd$!@qPm8<6?$OP)J#L@r#-~R^!a=?cIL67Ho_+qP{?t>534TH70 zs+1(Elt?&GCzz}64k6gOAx^Mv^^#G8nTptvVabeUx(%P?eXpR%tXd}rOwG~~O5iXQWrO`a0zf?`I zcJqb8hydeRDM4Uw)I+SyFi;h6w;7(&j$y&_c?MD*R?`QDF;wW>Wn-{C z5%5(k-UNZ)PPcVaas<`(L;(b`v;q(m?8U%3t}axQoSmfBq3|*EjJiP%hJoR-R4k3j z#fH7`))tpw6l{*q7p!)@T{A}2i@kzLg58qtRieEVGOB7fTxpu8P&?>LOZ)9lr~X$@ zw*2UKY|#IXpFTTo=zq_SpFa7j|9y^+-)f^;xvu?V(o2Y-%_fkDF&O|s8o(u$NM2yE z>B09Iod?#Gpf9z-A0AYuhT_B`b(rESzPcDxTZR-Ffi0!y$(Hr*JN{tAOk0~e;A_Cv zFtiT@VGNTD%4K@O8k>cHy?jgtwxxE*+{|5iRj@Ic3SkI*M6g=Hm<%e6x1~o1HK1;9 z+10o7 zd-UD6NAc0K_;7UisPlHOn2lX~3h>KeJVfUczLV(wEw+R0ybEIAv>I)BH-`JD(fmdt zeG3ZZSgFBW1w8U~>NFvesF zU$|@Zf5hj$^gl?D-1;5s>HpF5<7Y<=`hR@*{MlFf|2aN8-pOSna%Mty6O}mH@s>hL z{`=&|A4cd{fxI!EF>}L9mSjfjbkd(I`&&?@T!10i+jJ17-_yyt8B2>c3CpE)#>Qau zwo>DHIwpewTq#t6KM`wz2*fZ#!pavw z+z660;I79J`TPN??4!6&czjH>ELnu_Q8wJot+slJv*JR5lxAdHqZ}rL($GZLGTo}e zQg-ax>;3Cae1C=hH^QE~pRfNz)qb@@`6$CT!a*Ar7Ob{V#YrHmkoO;|X-Cfk^xt@EtQp|(Sdg32YxF`sf+#garUi%&i3N+mk;gSs!ozAbhS#2}kA z8YJ7UMHf;Syat=Vm^|8hbiYyTo(H3h$)glZFYdQ$LOJ*L=yUAtMLH zD*h7^?yM`ox?U_{g7Se7_+6A)25#CVox{Q0*&%YnR%oiOVMEZBFfv==YJSQCGvWw_ z8x@k@P|Zft5|M+*?h5>TQD)f{PHRlw{>A)8MpmjC*a6H~IQ2mk)$1gWNm?$KtB%sh z;GnK&4Fr+!pE5lFJmIxa_%I6IYTCDwYt8a{fQP-$Keu3#MfEuYwwpKfS>${&K@fb| zS&!AGm}o&3LTOi91S<%d7q|$VW~snUHH@OZnT_!vp{+87{B619xdDs~JKQQx-%cEi z-hmsi%7wz$)O?H%^38yc4MkT1u4#$=&JL+8p3YI&pL`Hx6F0%pQ9}+rz4(^%8DYyp zulDO@l&z1x_SUr#`F^yuclo{}_MI-=tiuWDL1|6g&MXBG0cLLw&)S3rm$?G3u$fF) z*R7*~Fl6%iyz8L4h6P%c0^pLb&HJJ6T&)?DuQ?xdg$ffW%u`Jx^m>^2HdEFqo^hq4 zwF?J|76CDB+59RmtZEdkTvuPbTU|8(XlERRRU4srZf9BIdfTL^W2P35tA!`fY8CTq`h?J@Qw5Z|Gj^-8xbABvCTe}B`;?{@sGC(onCfoi`Tbmso!=4(x% zkZdCl;1B;%PoA!U$FPfZ$#XQ>V>sXqXSeZo62?Q)tetGp*gjMfU^11DgTS=BRiD7z ztQ|XQ=S%<^^rxN#HL&W56qZl)WKPf=NH%hCpKemmp780>RpLamVv(o}jdd1gU=dEo z>SV-HX503qO7ejB^vpFlp0u6xf{J1738S1i4NcXAJoD+1|6r#!C+OT~9ersjTP!FK z9|+e|h}0F^4>U41mrT_bnK!SXn(}5<0Dl;q;|+s-GC0F;$TE=oRL8F>RSrgo>d!#1 zYpA!1fz-fL!we-yF%KJ{8d~f6s1Iy5ZXd{ZlO`lDCTWB2Gd+(q~q~bres|No#JNNs-j^n1v>{fV#t$kncZsJ zFZ*^OZ0Wv*5m6I&9s;O!9tV3M!X;H*A-#UupvHC~2-y+2=6O2CQXQ5Up`wlN9oOa* zfiI&{Lye&CN3UO&tjKsm)tDHqJLtyj0?z9NZUatv3bo-}h0B+|o}ePTrT;VL;YYhtE^%dWv&#+N8z|O9LylDIlMlnLF7!;602=xM>95lb)*MaC^lDi z7TWnDkueu=tq_W9VXm%i#L?|YBSh2rm^^wE5ik%T3mlX4*(;$hBvUNcXpX6Zns6^L z5UU*>LD*_-R^+zXk6q%=U&R#9zXz82bOGl&<3+(F_*Ly~0D_yt_<4Ld znwC6EA0Iw`e0Y5H6jzSXRO9w*cgRUqU4+Vcxn#NS@(A=-KlAf15We2%P*$6{OxX2Hpot?;Bs!2O$$TuSM z@#kh|OJz&*n7fIlJV)Jp)2qEvwGPq`|L9|1N zN6Rr8oc*n&8D71yCJXo%FR|H-C3;K-uf&xF84xtf$KP;b-r(p8)WX3s>#t%$FLhq~RTs&;pq~ zXa2pZ6ovafA}2rm0N<-hgyHiB|6b?W>QV^(Jo|(xqKT*HM7$39#>f%p| z$>18~k^{-68J3G0TXQem5~XZf&fR?Ak000#%f{p|iXfk$gp~wn+=awy!7?=dJKQ~r zkNy+=D7{n+nCty;YAY3X4OUr2wT)RQqi%y+TTS6!*kvcx#4y~Y0LSn=wXqDZH}DKE zc4HbL8&3XqXTR(8*Ux_)1Nv}aGZug%Tbn1IM04I>iQGIC4j16TVFWz6PFd=-rJK5R z(3d8K>83GFD)Xfs?!U0led9m8-~Qm$>o;dtv3{pFzGFlD-{IruhmH7;C&$M}U*kVM z$LIZf@=c6EowmYXl_u#$^O&4M$x*H`3$BC=4VzhDP|p=h9#s{O$@}*{$b$^JBTD?u z0}`7z&zUBL;WVR+46G6UTbN;^3gO@@8h@hC!}0&;1+JM^x*^C$Y*3-)09TYlz*&{Jl8Wa z_&0Shc+dbWPg~!k9r7m()+JYEQ3$D{=>7YHy~tgyN_l4)p=s)^Rv`a@XbCaDAXln= zqi))+w|F^_1YJ64CTOav_S%(H4BD(vEF3Y~J22A>0Sv#6KpYN6NBBA+GmK$20;H2D z;aPK`Q=06ONNdpQnT9JpUfW~EmN!hEie+JB|G}@1;-e>{!vTp;135GPLpfzO6ea#G z*Y+!n(-FkuL#%OHnPPC~+MBv}z1t5Adrxloatsc@8PAwr73|xk!SrOo#sszl?mBx{ zxI(#5LD#yeFxT1J$W=L;@pmqpe?)85TTxS5GMbwkry4@(j*c@9CfYDc$SFoceVmXDD$H+yZdo0(=0%n?oG{{WL>}qer8#momJ8J%+P^!SS z1b26xIXE=d6@zX8EWDhj-XQ8)(%M)ByvsH^+!7S}Z+BvYz*;^I-ZoFCqev!g}Pz;Ek998FjC0@ButNje{L)oMCx zPIuhz6A1?I-*1AB5f|Gd6jbVwn2ooUQ&B{XU~jg1sM|N++K^}O$lpq#VWkTu zxu)~xZ4P$9oelIz#BwQe2)j}6E79V==`!oQfz*#g`-odB5Tt7_>704fx~<;x!c~FB zI6bJ*kBr;VDPSHNe#iwj`PuSx!3!_fNgQxT?5FhML$7GjxYMJ8p#qo|yE2 z>%)hC0WIX=^1ro4ZjlPIA^+p>`IEN%|M+YE-)H%RnPO9_lf}W!QG_ni--~IqWSXW_ z)3ML;x)-?|*+XKVwwZhPw+y&=O9c<}Jju#*BYTR;9m#o~vRsoV#PWSw6!#_hgfx;Q z2|g67#lb>|>%o{5lG#SJ?Vs%rHZp%OwzU;T@SCf5yHDUxsI$hPMJ)V1qda42q-@BI zn>0=$+?&dc{m}g}pO1s_e>fOF7|w*KTCh|Y_Vnuht84K{OR*Zp>uTC)RjM^zImFJdh%d~*KHD~Skq4OA#4LcQlAq zbN-`2@GL%4d6z)!s-89C2hG!QaA`yiraV7Ti`unOQv36};4ea0vJhkdLJUN^bhrCA zTH02fx9xhrg|6E#YHvFG|1Qo>-<-Yt`FCeO{P^?9#re;FJiEL)fBov)V9oV34D#Va zeT?((ufF}}(Vz22^~*+ws`VTHX4lzI=PaytN=Dfh>tHF&pNvMi7%do0nN(yXzQvY< zqeNuoGFRWeI{EQz96Oo;STcM;Qc;^4POfNx^dwJzS6^vs(#@o2MMgRK=DlCbhX+h= z-+=z);@kp$97A7;I?TJ4KsGPn+i!N^D1GyOMFE`{ zsk}D-hR5KbJ^K%cuof2sUTfoGI(B{BF>l)(b=}+tZ787YmFFdE|2E_8YwpZrUxm%5 zzH7PnCQW2C@{K;5Q%XhNxQ17HSYMTRTBGctN4A&cRnJz;?&xuca@6tOJ_mPw8u#G8 zy#ub9TeabRTo~ICj8r-Gi?!lg`pf3TwMdTeaDNJ}d-dN}Z_a-F*^ua~i<8r{Z<{t6 z75HwH29gvjvEB#cEK}dsO+H5EGv4kA2gNiW%sn;t~|)M-@NY!$p@r9t%;Gx z-~B&F!-1jfgIA)KdtT-c_uuu9Ct~pcXnu>$BKU48_jN+oK@cs;TfmU0j4|LJ$A%t= zJXI~a4Y!cvF1pvA;Z<0>+D*Xqxb2&6H&Fi9;45M6C#;e|FtH;^uk-II&r0da1vG-Z z0u{5<3)Q1p_JAQAynb`fGHVa?zNxmpT0mIO{ApR(RA95^4dJ@Ow)w>Ard}<2lXv-?7k4*9{o!6%lg7R)>3@n1{+IG$ ze*HZB^AYl&OEKQ)0p48ywJHBSefl;2`}2HuT&AKc8K0(v=1|ZD66RMv)z@d-?vo|G zW&|2{(nb?hmfw&YDzS9Awl!HA>A5z@P6QpR!#1E4?rDjfaW=nS3 z?!JdYXcT7iefuD+4Tq{)k;ACl4%j&s3K%0-$#gX18T%Ao9mjWeMS&))T>g+0 zrq(3zwvpXbU)eSEZf-elzn7;zKq<2hxMCZGQfB>cwhwOmp0GrA`-N_s0Q39b*&L+( z@?2>DY;UV2R|csM{I%KGgJCB1SHW{K*dLHxS`;j>Zm%!Wxo+ot5zHY8LmUpaj){-mV0ic68;!h)<_D`brc>g=QI#yNHV7I%!Sx@yrPhnHF z_FCbxU$GQf4e<9G_2y?mX8WxEUp<2~8+AJM(H_7Z@ z0kbV3u{l&Fd9GPJ-1%2A#d`hUJ}9=YG0ne<=_c)<_X!*Ew;3$;=9t@pB*O-n{rg+5 zxaMwXM4~iq3Ic=!r8@i zIdioV%>jh$XlwbdY}2*~BXkLep=0vOW>zXDZyK}4p7S(;YRjZ3Wg!$MAKPw*u9-ZV zydf#pwV)3w!lKQb$@<_>{&!aDQZhGvJ|pQWr^}#vBy^Ou1!Lomef-_Y<;$~I=dXVI z^XrQ@=dWK~{e1Pilb;_yefIP3POg5}-rSnOccmK|JSU1a5_kEv|Uun|VtF~K!~ldbvT5%Z_3=VT>ev2OSI2>;<{ zwEsWc|5K=Ka=(AIMg6Cv<7WQ%ljG-K<3B&kXT2e?>ige}?zDZzZjPo*Q|B^jw##3A zv_rj6|J=FbYogEU4edxP(Y%BDuX(&N-GGoePfAy*&xKIhc7v#2Jij=tU${7bSzV|K zdIDT_$OX2~_0lfzbcoL=Z3h75!CT0&{@lcG40OCgD*wmSmN$<-N*)&H)}n!NtjA4{d-cI;Ls zjci=?TqNHMIprxa$`Ez|!nH!f@7VJV2=YDj>|sgYjmfj8PmiBQgv@N^;LrNfz6!6n z$D`4xBd&AnkkC(q_FoQdZMFB0tI7M|hGS{iSA*Xxziz zX_lMkmAlUgCV@O}hDx1K2%~al?st)(u}laTTGYD)MGq7~{c{iJw_jt6|A{^im;d3cZ>0cikpCY) zJ8Jg-e)8n`*Z8l`@_Bfchokr(9~|~CLBt2neFR>#0W?WA@~O5q=747RQtU4gTh~nW z@mt-P-T&kmz-}$B*}Bi%*>308tHre*ujbk`dF~LiOFQJ%>o;fPXosADD`C(e z2jtMS&ebo2b+{$9rWgNJ8A#lXfV;f^+!0}e`rTEe+Mwpu+-A%wKz#pWLc+G3(fZLk z=d#r+27~=p?9InK{NPB{f$9k%L^yoR6rf>~iEN>Fev+~RzfjK7R~%q4Xcq+VB9`j5 z=5CYEvJN7Ht{MUls0Q%~4Ftc~ZF`vQ;3F)*Z9sUNLP$BbM8hz$sZa6zYRdgr|2$m( zgQogavr(ank!=;%Z4cg{{~bO%Z1(?q{_N;$|F6&T*|B_ou2Iz6&4jov3o=+obX_XOIwX+b7w zxm>P->bN&YG%FVLXoA7(dwcHXHn|pq#;XFwyAzu%>x8Ud^YpvPaBnXr(PLPSVhpO- z&}7k?=IP~r0}#+ou+(BnAtJvrs+-$f!Uci_35CGgG#*B`Ig^hRnX(1VX0CEgxC|sc z8&)s^xR`a2DOJ#!+wKO;o19*P0f2@TZsB5W>bN<+Z&`EoB(Qz^iTXDE2Lv_MYN`O# zsQd35#Qhc7X)Q#7_?7-M=&XN|@LJb4*bKf|S&4rlr8Z8-AXYEYMF~gV z_nevUo9BPHpP6fZ*A;x+b7LPCDdI2*V9JUFb*r0G)%?P$O7Tp!j##i%&CHb09GG5tr7ZPLoe!6KJQbCRgEA9(q_pxth-$_1QF)TMBxtyR`|ulNASEHC&`nExLqYrDd(PE_fE@P z?mt4E55`>fc|RcQH-7=OI%m=&RvO9HnvB@yX=84az_x_Ul{2%gjO0+jhTA4k#)B6bGB z5VtXoL|lQI^C1dZc1_cHo5OI>XEa?JBuGk##=+5DG?_(g6_b2V(gXU3KmT{Pbe9mp zst~Z<+@H1w)GaN8CD^^;5iQOxHI`E8_tVceKCBLw}d+;F`C`*qJc=NC= zdkL?q>4N}?4cps}FQ(E^EjBXllZ<3&Dx{V3ZR0j*Kc(7VW-P-(c&<9PCkmD^wFP2y z{qsDvb_rLVn5hW;9H6!tpAFG#HK8=a8s1Q6;HaL*m!8G2i)vYI6kCb3NM~>7%k4+w zCy&u6Z1iTB>Tfos*gA{cxng?yt#GDgI1t1B@K)l?_tl4gi{ahv<2IdMh*>AZH@liH zvS#vv)2vfT_P24t5=HXx`lr_7cNZ9<-CcYsl~ov2ep|+_;nYP{`eb9^e5&XxjI+wX znfB&{%h&GG5FedpUuLxzY=J+dNg_>Miun{Pk?uEGh|D@_5^=?+ItiB<$_ES7#cTFT zxAt2w0I2R|=nMSqVRlNwu7n-SHL2=4%3z61Lz32ML=>`cgRgpd6;(i1l9|EG$2~?S zPJHjTn*p3g;})}Gk9>btlIH&k@ygnVF{rQ|L&;}yGxQw>VS}ZQ-|)guAJcrEtX4Se zD3L!K7=-&Eo>~E2lFP^A60srnw`eyG-K0HhfgW~Ovrap<8ACJyOM{bzf_`ywv1z>$ zL&w;X$HCk__gJ|p7+v{dN88()A-WM1>Y2_lNUL^9T;!J!5jEy-ec~g9UZa0tt6<7& z{iQTB35emM)!f(8ZhGmuVc_RT!W6;8f|CRRPd|_glZhA{7ndKyCgGW_)-cn9fNZ_b zwhE<;Bp1m@Irv1z4EW>YvWwJnc|TE)J3f+&8(E$yu4bP^KJ8U`YKue z1KI53xWA>oCQ2KyA~4|H+fz{e;lBKC!#2+>-Sg>Ul)m}@1b6hmg4^RXr+T_Je?fwV92Emb`UKv$GT#sFI@)QT zyBb%z@332ZU6!70O9V-0JoY zrN8)dJ8?AE-=(C9_k;_OKyR)jlO5+Fc@ObAOa8Ckt_EqMTr*C$))6KE1vi|RYD1~% zATcN`M@PfLP*OELdj0)61dM^ZwJB*F^I-W!p0 z;{B6ZHA_*2M}b~HQH&U&5i(N<@n(D*&v9{EMPL+IRxXVy$_8jQIcHsKcv^OMN(X-uBHF2J+^>bkPwR^+P<+4P#yRuip;H ztw>-Ug1;pAr0dZO%Mo?b_2<^&ADH;DytP}~mY>xk7UUXAU@zqk6hKl~&M5lJQXNKV zPq|}?OW1aP!hEc*7Nt8tUozTi7T!wpqI)p>HFtN&M3^0xYJPil?~qmlWLIb7{!nP{ zgw!n+H}OE#xN7viG>$kz>iw#qoyIdB!u>JtR_8Jjo{3K%ypMs(G)?rhtc9QCul8Ypog z(lD!s_sLtiaqGyO7$D@P@i8leRBG!E$Z`7^7{{p<$R9Z6Xa2%$j_@rsH=ylfze?QO z&%t&mYK2SXd#6w-hF0kEv%{NvRkm7V23n~ti`pkeToenyE+D_N_;`XxK+LOKJ^jzE)oY5mJcfATxIc4A0}#fJim59!nTVl54O9 zN|L#B@qS>jHArxnFq4V)qI<<~Z5S2+b_STD{`@v*NU#d%85I}p_!U+LVOH^wv>igF z4|xI9-6f`&PmwKY0&%6v&2D`>N|@n~fs$sC;z3VpM5*{jzd`9(0Wh?SbhnxBd9zP`R2K9n^ zXejH*LjQ{9ei!19AjZhhu0LnE;+(9);(NY>?IlP`oB4-1^((&a)#JlRVDo7JELBVuoZO^AX4kyAh@V~fCP?v`Vwp$`pB3_i!%3S@Ww}ZI}&5>`_U)oI~X(@J3yT|8zao!cYg&zm3Xzv{g z{%gDMunQHm!dNsV_)NgN)J^<(0#q&!pXCnH>$t8#$91qk==8hdPu(58fG*-WPSlZp zAsOCJpXd9Qzg$=-m%sKE=7WdbOpWCjl{Xvkd}r4>DS9DKzL7SWrVo9I^p~vi969F7 z*MN9th~QeNKNI!&7MK>zxaLYgLP$i6k9uy{=ZhI3mNQa*54+R7qtK(A^vY~sN)T*a z-$4>evKetwWRQGrUU?z#70O&j8~j}lIPe%G@t1FZtKdm&ww~wPtvPhT^~^g{_9u2X7`Z-7 z{r)?@$as}xKmXP=G#Up2SPZDN5Sa0>brQSx+sMg75DaoKVF zZlEoxifSMOvyRh*sPM5Z)M&BYw)-1l9hFeU7{7P#7Q>!d@=*js+v}x*B@x6|!;2Uz zMq%8n$hdee_avrMibSWN)fWc?dD%&eKYIpK*=Qe~0Jx$v!W~ji)!++>z7xNzH zwFADoMDB-I|F*qVZGApcz#_wjh)sYW;j^(hqbW4`%P$mNf4tk@7<||uC+9nHzs7^H zw9E`)N(IzFz|VDS*6qApR#;B_{>(`H{J4%FS;~hjba6nz!vPp0}OORT-aPBjK z`7bZ0rDS`ND;qwmjs^EBo>F&kt0|cVRr5DE8u63l9CE*5Zkd)+Gn~}_#XrH+F^jFx zuyCf8BBiC2h#}9>XpB1oZ^+Sj^X$laRApW_W@wYh(9NwTi7AEmrH6|++k7tno%{K$ z%X`+?tnQteY!ZcZaR9o}fN(g_${(I7T5m)vCy*XF&|SZkZtiB(;4#NUy?kADc@S4> zc>78AV)U*^SxES`)gWRFb1CWWO>ppR)!o~h--By4mwESLN!HzE$&ecYjyL61ZLRvl zSH|zIWs`+M<%da(j#MCa8{m?#gE^lhCh5~6c>9ESuH(Jr&=v&&kJufHrT;cTV5cL%c(JsOb4^IzfNt2(;bEy#3L zaCW3J_N3%_OaykrS0z-iGC!OjORXL$_Jm6$(54GqIda<~#~9M#iZp!QX>%;%&Ibm< zqMA<{+a6&40F&tBjN=<#9NiBwK#fZR!rn!mta^r2D|%(OsAvFf;2-6`#i$MH(T631 zTx?rHE!r+CgzG#q8+5Q9!Gk#Ti=AmN`@r11WVQ<$gxh}PooFkL92+N;sCL^6_?r>h z5$8NA@fcmaEM7|uZ@Y_S6yvr^>|wHs+@cUAB(8&K*@;$_DxcBKsYE| ziqt5>bFk^<^0`u%wh(bnGfS{$ytnRgvCe8jG%(!&BRsqb8%wn2zmWR$?0=A&$DF57 z&D?nwP*d9iIG0J_1$xFoVegde^mM6kWiK6>GzKw7hh*Bp&tPHcs$stP7%sq+EK;l4|OV4nHJ^*ipi7)~D=`tT!d_N~gW;TAjQ82%rB zLY4p&(MD|oe1H8k=&zr~@n&+v>QNE;W5Z&#dE **Important Note: Prometheus Federator is designed to be deployed alongside an existing Prometheus Operator deployment in a cluster that has already installed the Prometheus Operator CRDs.** + +By default, the chart is configured and intended to be deployed alongside [rancher-monitoring](https://rancher.com/docs/rancher/v2.6/en/monitoring-alerting/), which deploys Prometheus Operator alongside a Cluster Prometheus that each Project Monitoring Stack is configured to federate namespace-scoped metrics from by default. + +## Pre-Installation: Using Prometheus Federator with Rancher and rancher-monitoring + +If you are running your cluster on [Rancher](https://rancher.com/) and already have [rancher-monitoring](https://rancher.com/docs/rancher/v2.6/en/monitoring-alerting/) deployed onto your cluster, Prometheus Federator's default configuration should already be configured to work with your existing Cluster Monitoring Stack; however, here are some notes on how we recommend you configure rancher-monitoring to optimize the security and usability of Prometheus Federator in your cluster: + +### Ensure the cattle-monitoring-system namespace is placed into the System Project (or a similarly locked down Project that has access to other Projects in the cluster) + +Prometheus Operator's security model expects that the namespace it is deployed into (`cattle-monitoring-system`) has limited access for anyone except Cluster Admins to avoid privilege escalation via execing into Pods (such as the Jobs executing Helm operations). In addition, deploying Prometheus Federator and all Project Prometheus stacks into the System Project ensures that the each Project Prometheus is able to reach out to scrape workloads across all Projects (even if Network Policies are defined via Project Network Isolation) but has limited access for Project Owners, Project Members, and other users to be able to access data they shouldn't have access to (i.e. being allowed to exec into pods, set up the ability to scrape namespaces outside of a given Project, etc.). + +### Configure rancher-monitoring to only watch for resources created by the Helm chart itself + +Since each Project Monitoring Stack will watch the other namespaces and collect additional custom workload metrics or dashboards already, it's recommended to configure the following settings on all selectors to ensure that the Cluster Prometheus Stack only monitors resources created by the Helm Chart itself: + +``` +matchLabels: + release: "rancher-monitoring" +``` + +The following selector fields are recommended to have this value: +- `.Values.alertmanager.alertmanagerSpec.alertmanagerConfigSelector` +- `.Values.prometheus.prometheusSpec.serviceMonitorSelector` +- `.Values.prometheus.prometheusSpec.podMonitorSelector` +- `.Values.prometheus.prometheusSpec.ruleSelector` +- `.Values.prometheus.prometheusSpec.probeSelector` + +Once this setting is turned on, you can always create ServiceMonitors or PodMonitors that are picked up by the Cluster Prometheus by adding the label `release: "rancher-monitoring"` to them (in which case they will be ignored by Project Monitoring Stacks automatically by default, even if the namespace in which those ServiceMonitors or PodMonitors reside in are not system namespaces). + +> Note: If you don't want to allow users to be able to create ServiceMonitors and PodMonitors that aggregate into the Cluster Prometheus in Project namespaces, you can additionally set the namespaceSelectors on the chart to only target system namespaces (which must contain `cattle-monitoring-system` and `cattle-dashboards`, where resources are deployed into by default by rancher-monitoring; you will also need to monitor the `default` namespace to get apiserver metrics or create a custom ServiceMonitor to scrape apiserver metrics from the Service residing in the default namespace) to limit your Cluster Prometheus from picking up other Prometheus Operator CRs; in that case, it would be recommended to turn `.Values.prometheus.prometheusSpec.ignoreNamespaceSelectors=true` to allow you to define ServiceMonitors that can monitor non-system namespaces from within a system namespace. + +In addition, if you modified the default `.Values.grafana.sidecar.*.searchNamespace` values on the Grafana Helm subchart for Monitoring V2, it is also recommended to remove the overrides or ensure that your defaults are scoped to only system namespaces for the following values: +- `.Values.grafana.sidecar.dashboards.searchNamespace` (default `cattle-dashboards`) +- `.Values.grafana.sidecar.datasources.searchNamespace` (default `null`, which means it uses the release namespace `cattle-monitoring-system`) +- `.Values.grafana.sidecar.plugins.searchNamespace` (default `null`, which means it uses the release namespace `cattle-monitoring-system`) +- `.Values.grafana.sidecar.notifiers.searchNamespace` (default `null`, which means it uses the release namespace `cattle-monitoring-system`) + +### Increase the CPU / memory limits of the Cluster Prometheus + +Depending on a cluster's setup, it's generally recommended to give a large amount of dedicated memory to the Cluster Prometheus to avoid restarts due to out-of-memory errors (OOMKilled), usually caused by churn created in the cluster that causes a large number of high cardinality metrics to be generated and ingested by Prometheus within one block of time; this is one of the reasons why the default Rancher Monitoring stack expects around 4GB of RAM to be able to operate in a normal-sized cluster. However, when introducing Project Monitoring Stacks that are all sending `/federate` requests to the same Cluster Prometheus and are reliant on the Cluster Prometheus being "up" to federate that system data on their namespaces, it's even more important that the Cluster Prometheus has an ample amount of CPU / memory assigned to it to prevent an outage that can cause data gaps across all Project Prometheis in the cluster. + +> Note: There are no specific recommendations on how much memory the Cluster Prometheus should be configured with since it depends entirely on the user's setup (namely the likelihood of encountering a high churn rate and the scale of metrics that could be generated at that time); it generally varies per setup. + +## How does the operator work? + +1. On deploying this chart, users can create ProjectHelmCharts CRs with `spec.helmApiVersion` set to `monitoring.cattle.io/v1alpha1` (also known as "Project Monitors" in the Rancher UI) in a **Project Registration Namespace (`cattle-project-`)**. +2. On seeing each ProjectHelmChartCR, the operator will automatically deploy a Project Prometheus stack on the Project Owner's behalf in the **Project Release Namespace (`cattle-project--monitoring`)** based on a HelmChart CR and a HelmRelease CR automatically created by the ProjectHelmChart controller in the **Operator / System Namespace**. +3. RBAC will automatically be assigned in the Project Release Namespace to allow users to view the Prometheus, Alertmanager, and Grafana UIs of the Project Monitoring Stack deployed; this will be based on RBAC defined on the Project Registration Namespace against the [default Kubernetes user-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) (see below for more information about configuring RBAC). + +### What is a Project? + +In Prometheus Federator, a Project is a group of namespaces that can be identified by a `metav1.LabelSelector`; by default, the label used to identify projects is `field.cattle.io/projectId`, the label used to identify namespaces that are contained within a given [Rancher](https://rancher.com/) Project. + +### Configuring the Helm release created by a ProjectHelmChart + +The `spec.values` of this ProjectHelmChart resources will correspond to the `values.yaml` override to be supplied to the underlying Helm chart deployed by the operator on the user's behalf; to see the underlying chart's `values.yaml` spec, either: +- View to the chart's definition located at [`rancher/prometheus-federator` under `charts/rancher-project-monitoring`](https://github.com/rancher/prometheus-federator/blob/main/charts/rancher-project-monitoring) (where the chart version will be tied to the version of this operator) +- Look for the ConfigMap named `monitoring.cattle.io.v1alpha1` that is automatically created in each Project Registration Namespace, which will contain both the `values.yaml` and `questions.yaml` that was used to configure the chart (which was embedded directly into the `prometheus-federator` binary). + +### Namespaces + +As a Project Operator based on [rancher/helm-project-operator](https://github.com/rancher/helm-project-operator), Prometheus Federator has three different classifications of namespaces that the operator looks out for: +1. **Operator / System Namespace**: this is the namespace that the operator is deployed into (e.g. `cattle-monitoring-system`). This namespace will contain all HelmCharts and HelmReleases for all ProjectHelmCharts watched by this operator. **Only Cluster Admins should have access to this namespace.** +2. **Project Registration Namespace (`cattle-project-`)**: this is the set of namespaces that the operator watches for ProjectHelmCharts within. The RoleBindings and ClusterRoleBindings that apply to this namespace will also be the source of truth for the auto-assigned RBAC created in the Project Release Namespace (see more details below). **Project Owners (admin), Project Members (edit), and Read-Only Members (view) should have access to this namespace**. +> Note: Project Registration Namespaces will be auto-generated by the operator and imported into the Project it is tied to if `.Values.global.cattle.projectLabel` is provided (which is set to `field.cattle.io/projectId` by default); this indicates that a Project Registration Namespace should be created by the operator if at least one namespace is observed with that label. The operator will not let these namespaces be deleted unless either all namespaces with that label are gone (e.g. this is the last namespace in that project, in which case the namespace will be marked with the label `"helm.cattle.io/helm-project-operator-orphaned": "true"`, which signals that it can be deleted) or it is no longer watching that project (because the project ID was provided under `.Values.helmProjectOperator.otherSystemProjectLabelValues`, which serves as a denylist for Projects). These namespaces will also never be auto-deleted to avoid destroying user data; it is recommended that users clean up these namespaces manually if desired on creating or deleting a project +> Note: if `.Values.global.cattle.projectLabel` is not provided, the Operator / System Namespace will also be the Project Registration Namespace +3. **Project Release Namespace (`cattle-project--monitoring`)**: this is the set of namespaces that the operator deploys Project Monitoring Stacks within on behalf of a ProjectHelmChart; the operator will also automatically assign RBAC to Roles created in this namespace by the Project Monitoring Stack based on bindings found in the Project Registration Namespace. **Only Cluster Admins should have access to this namespace; Project Owners (admin), Project Members (edit), and Read-Only Members (view) will be assigned limited access to this namespace by the deployed Helm Chart and Prometheus Federator.** +> Note: Project Release Namespaces are automatically deployed and imported into the project whose ID is specified under `.Values.helmProjectOperator.projectReleaseNamespaces.labelValue` (which defaults to the value of `.Values.global.cattle.systemProjectId` if not specified) whenever a ProjectHelmChart is specified in a Project Registration Namespace +> Note: Project Release Namespaces follow the same orphaning conventions as Project Registration Namespaces (see note above) +> Note: if `.Values.projectReleaseNamespaces.enabled` is false, the Project Release Namespace will be the same as the Project Registration Namespace + +### Helm Resources (HelmChart, HelmRelease) + +On deploying a ProjectHelmChart, the Prometheus Federator will automatically create and manage two child custom resources that manage the underlying Helm resources in turn: +- A HelmChart CR (managed via an embedded [k3s-io/helm-contoller](https://github.com/k3s-io/helm-controller) in the operator): this custom resource automatically creates a Job in the same namespace that triggers a `helm install`, `helm upgrade`, or `helm uninstall` depending on the change applied to the HelmChart CR; this CR is automatically updated on changes to the ProjectHelmChart (e.g. modifying the values.yaml) or changes to the underlying Project definition (e.g. adding or removing namespaces from a project). +> **Important Note: If a ProjectHelmChart is not deploying or updating the underlying Project Monitoring Stack for some reason, the Job created by this resource in the Operator / System namespace should be the first place you check to see if there's something wrong with the Helm operation; however, this is generally only accessible by a Cluster Admin.** +- A HelmRelease CR (managed via an embedded [rancher/helm-locker](https://github.com/rancher/helm-locker) in the operator): this custom resource automatically locks a deployed Helm release in place and automatically overwrites updates to underlying resources unless the change happens via a Helm operation (`helm install`, `helm upgrade`, or `helm uninstall` performed by the HelmChart CR). +> Note: HelmRelease CRs emit Kubernetes Events that detect when an underlying Helm release is being modified and locks it back to place; to view these events, you can use `kubectl describe helmrelease -n `; you can also view the logs on this operator to see when changes are detected and which resources were attempted to be modified + +Both of these resources are created for all Helm charts in the Operator / System namespaces to avoid escalation of privileges to underprivileged users. + +### RBAC + +As described in the section on namespaces above, Prometheus Federator expects that Project Owners, Project Members, and other users in the cluster with Project-level permissions (e.g. permissions in a certain set of namespaces identified by a single label selector) have minimal permissions in any namespaces except the Project Registration Namespace (which is imported into the project by default) and those that already comprise their projects. Therefore, in order to allow Project Owners to assign specific chart permissions to other users in their Project namespaces, the Helm Project Operator will automatically watch the following bindings: +- ClusterRoleBindings +- RoleBindings in the Project Release Namespace + +On observing a change to one of those types of bindings, the Helm Project Operator will check whether the `roleRef` that the the binding points to matches a ClusterRole with the name provided under `helmProjectOperator.releaseRoleBindings.clusterRoleRefs.admin`, `helmProjectOperator.releaseRoleBindings.clusterRoleRefs.edit`, or `helmProjectOperator.releaseRoleBindings.clusterRoleRefs.view`; by default, these roleRefs correspond will correspond to `admin`, `edit`, and `view` respectively, which are the [default Kubernetes user-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles). + +> Note: for Rancher RBAC users, these [default Kubernetes user-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) directly correlate to the `Project Owner`, `Project Member`, and `Read-Only` default Project Role Templates. + +If the `roleRef` matches, the Helm Project Operator will filter the `subjects` of the binding for all Users and Groups and use that to automatically construct a RoleBinding for each Role in the Project Release Namespace with the same name as the role and the following labels: +- `helm.cattle.io/project-helm-chart-role: {{ .Release.Name }}` +- `helm.cattle.io/project-helm-chart-role-aggregate-from: ` + +By default, the `rancher-project-monitoring` (the underlying chart deployed by Prometheus Federator) creates three default Roles per Project Release Namespace that provide `admin`, `edit`, and `view` users to permissions to view the Prometheus, Alertmanager, and Grafana UIs of the Project Monitoring Stack to provide least privilege; however, if a Cluster Admin would like to assign additional permissions to certain users, they can either directly assign RoleBindings in the Project Release Namespace to certain users or created Roles with the above two labels on them to allow Project Owners to control assigning those RBAC roles to users in their Project Registration namespaces. + +### Advanced Helm Project Operator Configuration + +|Value|Configuration| +|---|---------------------------| +|`helmProjectOperator.valuesOverride`| Allows an Operator to override values that are set on each ProjectHelmChart deployment on an operator-level; user-provided options (specified on the `spec.values` of the ProjectHelmChart) are automatically overridden if operator-level values are provided. For an exmaple, see how the default value overrides `federate.targets` (note: when overriding list values like `federate.targets`, user-provided list values will **not** be concatenated) | +|`helmProjectOperator.projectReleaseNamespaces.labelValues`| The value of the Project that all Project Release Namespaces should be auto-imported into (via label and annotation). Not recommended to be overridden on a Rancher setup. | +|`helmProjectOperator.otherSystemProjectLabelValues`| Other namespaces that the operator should treat as a system namespace that should not be monitored. By default, all namespaces that match `global.cattle.systemProjectId` will not be matched. `cattle-monitoring-system`, `cattle-dashboards`, and `kube-system` are explicitly marked as system namespaces as well, regardless of label or annotation. | +|`helmProjectOperator.releaseRoleBindings.aggregate`| Whether to automatically create RBAC resources in Project Release namespaces +|`helmProjectOperator.releaseRoleBindings.clusterRoleRefs.`| ClusterRoles to reference to discover subjects to create RoleBindings for in the Project Release Namespace for all corresponding Project Release Roles. See RBAC above for more information | +|`helmProjectOperator.hardenedNamespaces.enabled`| Whether to automatically patch the default ServiceAccount with `automountServiceAccountToken: false` and create a default NetworkPolicy in all managed namespaces in the cluster; the default values ensure that the creation of the namespace does not break a CIS 1.16 hardened scan | +|`helmProjectOperator.hardenedNamespaces.configuration`| The configuration to be supplied to the default ServiceAccount or auto-generated NetworkPolicy on managing a namespace | +|`helmProjectOperator.helmController.enabled`| Whether to enable an embedded k3s-io/helm-controller instance within the Helm Project Operator. Should be disabled for RKE2/K3s clusters before v1.23.14 / v1.24.8 / v1.25.4 since RKE2/K3s clusters already run Helm Controller at a cluster-wide level to manage internal Kubernetes components | +|`helmProjectOperator.helmLocker.enabled`| Whether to enable an embedded rancher/helm-locker instance within the Helm Project Operator. | diff --git a/charts/prometheus-federator/0.4.5-rc.1/app-README.md b/charts/prometheus-federator/0.4.5-rc.1/app-README.md new file mode 100644 index 00000000..99fa7ca1 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/app-README.md @@ -0,0 +1,27 @@ +# Prometheus Federator + +This chart deploys an operator that manages Project Monitoring Stacks composed of the following set of resources that are scoped to project namespaces: +- [Prometheus](https://prometheus.io/) (managed externally by [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator)) +- [Alertmanager](https://prometheus.io/docs/alerting/latest/alertmanager/) (managed externally by [Prometheus Operator](https://github.com/prometheus-operator/prometheus-operator)) +- [Grafana](https://github.com/helm/charts/tree/master/stable/grafana) (deployed via an embedded Helm chart) +- Default PrometheusRules and Grafana dashboards based on the collection of community-curated resources from [kube-prometheus](https://github.com/prometheus-operator/kube-prometheus/) +- Default ServiceMonitors that watch the deployed Prometheus, Grafana, and Alertmanager + +Since this Project Monitoring Stack deploys Prometheus Operator CRs, an existing Prometheus Operator instance must already be deployed in the cluster for Prometheus Federator to successfully be able to deploy Project Monitoring Stacks. It is recommended to use [`rancher-monitoring`](https://rancher.com/docs/rancher/v2.6/en/monitoring-alerting/) for this. For more information on how the chart works or advanced configurations, please read the `README.md`. + +## Upgrading to Kubernetes v1.25+ + +Starting in Kubernetes v1.25, [Pod Security Policies](https://kubernetes.io/docs/concepts/security/pod-security-policy/) have been removed from the Kubernetes API. + +As a result, **before upgrading to Kubernetes v1.25** (or on a fresh install in a Kubernetes v1.25+ cluster), users are expected to perform an in-place upgrade of this chart with `global.cattle.psp.enabled` set to `false` if it has been previously set to `true`. +​ +> **Note:** +> In this chart release, any previous field that was associated with any PSP resources have been removed in favor of a single global field: `global.cattle.psp.enabled`. + ​ +> **Note:** +> If you upgrade your cluster to Kubernetes v1.25+ before removing PSPs via a `helm upgrade` (even if you manually clean up resources), **it will leave the Helm release in a broken state within the cluster such that further Helm operations will not work (`helm uninstall`, `helm upgrade`, etc.).** +> +> If your charts get stuck in this state, please consult the Rancher docs on how to clean up your Helm release secrets. +Upon setting `global.cattle.psp.enabled` to false, the chart will remove any PSP resources deployed on its behalf from the cluster. This is the default setting for this chart. +​ +As a replacement for PSPs, [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) should be used. Please consult the Rancher docs for more details on how to configure your chart release namespaces to work with the new Pod Security Admission and apply Pod Security Standards. \ No newline at end of file diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/Chart.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/Chart.yaml new file mode 100644 index 00000000..a04c6997 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/Chart.yaml @@ -0,0 +1,18 @@ +annotations: + catalog.cattle.io/certified: rancher + catalog.cattle.io/display-name: Helm Project Operator + catalog.cattle.io/kube-version: '>=1.16.0-0' + catalog.cattle.io/namespace: cattle-helm-system + catalog.cattle.io/os: linux,windows + catalog.cattle.io/permits-os: linux,windows + catalog.cattle.io/provides-gvr: helm.cattle.io.projecthelmchart/v1alpha1 + catalog.cattle.io/rancher-version: '>= 2.6.0-0' + catalog.cattle.io/release-name: helm-project-operator +apiVersion: v2 +appVersion: v0.4.3 +description: Helm Project Operator +maintainers: +- email: dan.pock@suse.com + name: Dan Pock +name: helmProjectOperator +version: 0.3.2 diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/README.md b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/README.md new file mode 100644 index 00000000..9623ff22 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/README.md @@ -0,0 +1,77 @@ +# Helm Project Operator + +## How does the operator work? + +1. On deploying a Helm Project Operator, users can create ProjectHelmCharts CRs with `spec.helmApiVersion` set to `dummy.cattle.io/v1alpha1` in a **Project Registration Namespace (`cattle-project-`)**. +2. On seeing each ProjectHelmChartCR, the operator will automatically deploy the embedded Helm chart on the Project Owner's behalf in the **Project Release Namespace (`cattle-project--dummy`)** based on a HelmChart CR and a HelmRelease CR automatically created by the ProjectHelmChart controller in the **Operator / System Namespace**. +3. RBAC will automatically be assigned in the Project Release Namespace to allow users to based on Role created in the Project Release Namespace with a given set of labels; this will be based on RBAC defined on the Project Registration Namespace against the [default Kubernetes user-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) (see below for more information about configuring RBAC). + +### What is a Project? + +In Helm Project Operator, a Project is a group of namespaces that can be identified by a `metav1.LabelSelector`; by default, the label used to identify projects is `field.cattle.io/projectId`, the label used to identify namespaces that are contained within a given [Rancher](https://rancher.com/) Project. + +### What is a ProjectHelmChart? + +A ProjectHelmChart is an instance of a (project-scoped) Helm chart deployed on behalf of a user who has permissions to create ProjectHelmChart resources in a Project Registration namespace. + +Generally, the best way to think about the ProjectHelmChart model is by comparing it to two other models: +1. Managed Kubernetes providers (EKS, GKE, AKS, etc.): in this model, a user has the ability to say "I want a Kubernetes cluster" but the underlying cloud provider is responsible for provisioning the infrastructure and offering **limited view and access** of the underlying resources created on their behalf; similarly, Helm Project Operator allows a Project Owner to say "I want this Helm chart deployed", but the underlying Operator is responsible for "provisioning" (deploying) the Helm chart and offering **limited view and access** of the underlying Kubernetes resources created on their behalf (based on configuring "least-privilege" Kubernetes RBAC for the Project Owners / Members in the newly created Project Release Namespace). +2. Dynamically-provisioned Persistent Volumes: in this model, a single resource (PersistentVolume) exists that allows you to specify a Storage Class that actually implements provisioning the underlying storage via a Storage Class Provisioner (e.g. Longhorn). Similarly, the ProjectHelmChart exists that allows you to specify a `spec.helmApiVersion` ("storage class") that actually implements deploying the underlying Helm chart via a Helm Project Operator (e.g. [`rancher/prometheus-federator`](https://github.com/rancher/prometheus-federator)). + +### Configuring the Helm release created by a ProjectHelmChart + +The `spec.values` of this ProjectHelmChart resources will correspond to the `values.yaml` override to be supplied to the underlying Helm chart deployed by the operator on the user's behalf; to see the underlying chart's `values.yaml` spec, either: +- View to the chart's definition located at [`rancher/helm-project-operator` under `charts/project-operator-example`](https://github.com/rancher/helm-project-operator/blob/main/charts/project-operator-example) (where the chart version will be tied to the version of this operator) +- Look for the ConfigMap named `dummy.cattle.io.v1alpha1` that is automatically created in each Project Registration Namespace, which will contain both the `values.yaml` and `questions.yaml` that was used to configure the chart (which was embedded directly into the `helm-project-operator` binary). + +### Namespaces + +All Helm Project Operators have three different classifications of namespaces that the operator looks out for: +1. **Operator / System Namespace**: this is the namespace that the operator is deployed into (e.g. `cattle-helm-system`). This namespace will contain all HelmCharts and HelmReleases for all ProjectHelmCharts watched by this operator. **Only Cluster Admins should have access to this namespace.** +2. **Project Registration Namespace (`cattle-project-`)**: this is the set of namespaces that the operator watches for ProjectHelmCharts within. The RoleBindings and ClusterRoleBindings that apply to this namespace will also be the source of truth for the auto-assigned RBAC created in the Project Release Namespace (see more details below). **Project Owners (admin), Project Members (edit), and Read-Only Members (view) should have access to this namespace**. +> Note: Project Registration Namespaces will be auto-generated by the operator and imported into the Project it is tied to if `.Values.global.cattle.projectLabel` is provided (which is set to `field.cattle.io/projectId` by default); this indicates that a Project Registration Namespace should be created by the operator if at least one namespace is observed with that label. The operator will not let these namespaces be deleted unless either all namespaces with that label are gone (e.g. this is the last namespace in that project, in which case the namespace will be marked with the label `"helm.cattle.io/helm-project-operator-orphaned": "true"`, which signals that it can be deleted) or it is no longer watching that project (because the project ID was provided under `.Values.helmProjectOperator.otherSystemProjectLabelValues`, which serves as a denylist for Projects). These namespaces will also never be auto-deleted to avoid destroying user data; it is recommended that users clean up these namespaces manually if desired on creating or deleting a project +> Note: if `.Values.global.cattle.projectLabel` is not provided, the Operator / System Namespace will also be the Project Registration Namespace +3. **Project Release Namespace (`cattle-project--dummy`)**: this is the set of namespaces that the operator deploys Helm charts within on behalf of a ProjectHelmChart; the operator will also automatically assign RBAC to Roles created in this namespace by the Helm charts based on bindings found in the Project Registration Namespace. **Only Cluster Admins should have access to this namespace; Project Owners (admin), Project Members (edit), and Read-Only Members (view) will be assigned limited access to this namespace by the deployed Helm Chart and Helm Project Operator.** +> Note: Project Release Namespaces are automatically deployed and imported into the project whose ID is specified under `.Values.helmProjectOperator.projectReleaseNamespaces.labelValue` (which defaults to the value of `.Values.global.cattle.systemProjectId` if not specified) whenever a ProjectHelmChart is specified in a Project Registration Namespace +> Note: Project Release Namespaces follow the same orphaning conventions as Project Registration Namespaces (see note above) +> Note: if `.Values.projectReleaseNamespaces.enabled` is false, the Project Release Namespace will be the same as the Project Registration Namespace + +### Helm Resources (HelmChart, HelmRelease) + +On deploying a ProjectHelmChart, the Helm Project Operator will automatically create and manage two child custom resources that manage the underlying Helm resources in turn: +- A HelmChart CR (managed via an embedded [k3s-io/helm-contoller](https://github.com/k3s-io/helm-controller) in the operator): this custom resource automatically creates a Job in the same namespace that triggers a `helm install`, `helm upgrade`, or `helm uninstall` depending on the change applied to the HelmChart CR; this CR is automatically updated on changes to the ProjectHelmChart (e.g. modifying the values.yaml) or changes to the underlying Project definition (e.g. adding or removing namespaces from a project). +> **Important Note: If a ProjectHelmChart is not deploying or updating the underlying Project Monitoring Stack for some reason, the Job created by this resource in the Operator / System namespace should be the first place you check to see if there's something wrong with the Helm operation; however, this is generally only accessible by a Cluster Admin.** +- A HelmRelease CR (managed via an embedded [rancher/helm-locker](https://github.com/rancher/helm-locker) in the operator): this custom resource automatically locks a deployed Helm release in place and automatically overwrites updates to underlying resources unless the change happens via a Helm operation (`helm install`, `helm upgrade`, or `helm uninstall` performed by the HelmChart CR). +> Note: HelmRelease CRs emit Kubernetes Events that detect when an underlying Helm release is being modified and locks it back to place; to view these events, you can use `kubectl describe helmrelease -n `; you can also view the logs on this operator to see when changes are detected and which resources were attempted to be modified + +Both of these resources are created for all Helm charts in the Operator / System namespaces to avoid escalation of privileges to underprivileged users. + +### RBAC + +As described in the section on namespaces above, Helm Project Operator expects that Project Owners, Project Members, and other users in the cluster with Project-level permissions (e.g. permissions in a certain set of namespaces identified by a single label selector) have minimal permissions in any namespaces except the Project Registration Namespace (which is imported into the project by default) and those that already comprise their projects. Therefore, in order to allow Project Owners to assign specific chart permissions to other users in their Project namespaces, the Helm Project Operator will automatically watch the following bindings: +- ClusterRoleBindings +- RoleBindings in the Project Release Namespace + +On observing a change to one of those types of bindings, the Helm Project Operator will check whether the `roleRef` that the the binding points to matches a ClusterRole with the name provided under `helmProjectOperator.releaseRoleBindings.clusterRoleRefs.admin`, `helmProjectOperator.releaseRoleBindings.clusterRoleRefs.edit`, or `helmProjectOperator.releaseRoleBindings.clusterRoleRefs.view`; by default, these roleRefs correspond will correspond to `admin`, `edit`, and `view` respectively, which are the [default Kubernetes user-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles). + +> Note: for Rancher RBAC users, these [default Kubernetes user-facing roles](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles) directly correlate to the `Project Owner`, `Project Member`, and `Read-Only` default Project Role Templates. + +If the `roleRef` matches, the Helm Project Operator will filter the `subjects` of the binding for all Users and Groups and use that to automatically construct a RoleBinding for each Role in the Project Release Namespace with the same name as the role and the following labels: +- `helm.cattle.io/project-helm-chart-role: {{ .Release.Name }}` +- `helm.cattle.io/project-helm-chart-role-aggregate-from: ` + +By default, the `project-operator-example` (the underlying chart deployed by Helm Project Operator) does not create any default roles; however, if a Cluster Admin would like to assign additional permissions to certain users, they can either directly assign RoleBindings in the Project Release Namespace to certain users or created Roles with the above two labels on them to allow Project Owners to control assigning those RBAC roles to users in their Project Registration namespaces. + +### Advanced Helm Project Operator Configuration + +|Value|Configuration| +|---|---------------------------| +|`valuesOverride`| Allows an Operator to override values that are set on each ProjectHelmChart deployment on an operator-level; user-provided options (specified on the `spec.values` of the ProjectHelmChart) are automatically overridden if operator-level values are provided. For an exmaple, see how the default value overrides `federate.targets` (note: when overriding list values like `federate.targets`, user-provided list values will **not** be concatenated) | +|`projectReleaseNamespaces.labelValues`| The value of the Project that all Project Release Namespaces should be auto-imported into (via label and annotation). Not recommended to be overridden on a Rancher setup. | +|`otherSystemProjectLabelValues`| Other namespaces that the operator should treat as a system namespace that should not be monitored. By default, all namespaces that match `global.cattle.systemProjectId` will not be matched. `kube-system` is explicitly marked as a system namespace as well, regardless of label or annotation. | +|`releaseRoleBindings.aggregate`| Whether to automatically create RBAC resources in Project Release namespaces +|`releaseRoleBindings.clusterRoleRefs.`| ClusterRoles to reference to discover subjects to create RoleBindings for in the Project Release Namespace for all corresponding Project Release Roles. See RBAC above for more information | +|`hardenedNamespaces.enabled`| Whether to automatically patch the default ServiceAccount with `automountServiceAccountToken: false` and create a default NetworkPolicy in all managed namespaces in the cluster; the default values ensure that the creation of the namespace does not break a CIS 1.16 hardened scan | +|`hardenedNamespaces.configuration`| The configuration to be supplied to the default ServiceAccount or auto-generated NetworkPolicy on managing a namespace | +|`helmController.enabled`| Whether to enable an embedded k3s-io/helm-controller instance within the Helm Project Operator. Should be disabled for RKE2 clusters since RKE2 clusters already run Helm Controller to manage internal Kubernetes components | +|`helmLocker.enabled`| Whether to enable an embedded rancher/helm-locker instance within the Helm Project Operator. | diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/app-readme.md b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/app-readme.md new file mode 100644 index 00000000..fd551467 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/app-readme.md @@ -0,0 +1,20 @@ +# Helm Project Operator + +This chart installs the example [Helm Project Operator](https://github.com/rancher/helm-project-operator) onto your cluster. + +## Upgrading to Kubernetes v1.25+ + +Starting in Kubernetes v1.25, [Pod Security Policies](https://kubernetes.io/docs/concepts/security/pod-security-policy/) have been removed from the Kubernetes API. + +As a result, **before upgrading to Kubernetes v1.25** (or on a fresh install in a Kubernetes v1.25+ cluster), users are expected to perform an in-place upgrade of this chart with `global.cattle.psp.enabled` set to `false` if it has been previously set to `true`. +​ +> **Note:** +> In this chart release, any previous field that was associated with any PSP resources have been removed in favor of a single global field: `global.cattle.psp.enabled`. + ​ +> **Note:** +> If you upgrade your cluster to Kubernetes v1.25+ before removing PSPs via a `helm upgrade` (even if you manually clean up resources), **it will leave the Helm release in a broken state within the cluster such that further Helm operations will not work (`helm uninstall`, `helm upgrade`, etc.).** +> +> If your charts get stuck in this state, please consult the Rancher docs on how to clean up your Helm release secrets. +Upon setting `global.cattle.psp.enabled` to false, the chart will remove any PSP resources deployed on its behalf from the cluster. This is the default setting for this chart. +​ +As a replacement for PSPs, [Pod Security Admission](https://kubernetes.io/docs/concepts/security/pod-security-admission/) should be used. Please consult the Rancher docs for more details on how to configure your chart release namespaces to work with the new Pod Security Admission and apply Pod Security Standards. \ No newline at end of file diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/questions.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/questions.yaml new file mode 100644 index 00000000..054361a7 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/questions.yaml @@ -0,0 +1,43 @@ +questions: +- variable: global.cattle.psp.enabled + default: "false" + description: "Flag to enable or disable the installation of PodSecurityPolicies by this chart in the target cluster. If the cluster is running Kubernetes 1.25+, you must update this value to false." + label: "Enable PodSecurityPolicies" + type: boolean + group: "Security Settings" +- variable: helmController.enabled + label: Enable Embedded Helm Controller + description: 'Note: If you are running this chart in an RKE2 cluster, this should be disabled.' + type: boolean + group: Helm Controller +- variable: helmLocker.enabled + label: Enable Embedded Helm Locker + type: boolean + group: Helm Locker +- variable: projectReleaseNamespaces.labelValue + label: Project Release Namespace Project ID + description: By default, the System Project is selected. This can be overriden to a different Project (e.g. p-xxxxx) + type: string + required: false + group: Namespaces +- variable: releaseRoleBindings.clusterRoleRefs.admin + label: Admin ClusterRole + description: By default, admin selects Project Owners. This can be overridden to a different ClusterRole (e.g. rt-xxxxx) + type: string + default: admin + required: false + group: RBAC +- variable: releaseRoleBindings.clusterRoleRefs.edit + label: Edit ClusterRole + description: By default, edit selects Project Members. This can be overridden to a different ClusterRole (e.g. rt-xxxxx) + type: string + default: edit + required: false + group: RBAC +- variable: releaseRoleBindings.clusterRoleRefs.view + label: View ClusterRole + description: By default, view selects Read-Only users. This can be overridden to a different ClusterRole (e.g. rt-xxxxx) + type: string + default: view + required: false + group: RBAC diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/NOTES.txt b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/NOTES.txt new file mode 100644 index 00000000..32baeebc --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/NOTES.txt @@ -0,0 +1,2 @@ +{{ $.Chart.Name }} has been installed. Check its status by running: + kubectl --namespace {{ template "helm-project-operator.namespace" . }} get pods -l "release={{ $.Release.Name }}" diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/_helpers.tpl b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/_helpers.tpl new file mode 100644 index 00000000..2c52e507 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/_helpers.tpl @@ -0,0 +1,73 @@ +# Rancher +{{- define "system_default_registry" -}} +{{- if .Values.global.cattle.systemDefaultRegistry -}} +{{- printf "%s/" .Values.global.cattle.systemDefaultRegistry -}} +{{- end -}} +{{- end -}} + +# Windows Support + +{{/* +Windows cluster will add default taint for linux nodes, +add below linux tolerations to workloads could be scheduled to those linux nodes +*/}} + +{{- define "linux-node-tolerations" -}} +- key: "cattle.io/os" + value: "linux" + effect: "NoSchedule" + operator: "Equal" +{{- end -}} + +{{- define "linux-node-selector" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +beta.kubernetes.io/os: linux +{{- else -}} +kubernetes.io/os: linux +{{- end -}} +{{- end -}} + +# Helm Project Operator + +{{/* vim: set filetype=mustache: */}} +{{/* Expand the name of the chart. This is suffixed with -alertmanager, which means subtract 13 from longest 63 available */}} +{{- define "helm-project-operator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 50 | trimSuffix "-" -}} +{{- end }} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts +*/}} +{{- define "helm-project-operator.namespace" -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} +{{- end -}} + +{{/* Create chart name and version as used by the chart label. */}} +{{- define "helm-project-operator.chartref" -}} +{{- replace "+" "_" .Chart.Version | printf "%s-%s" .Chart.Name -}} +{{- end }} + +{{/* Generate basic labels */}} +{{- define "helm-project-operator.labels" -}} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: "{{ replace "+" "_" .Chart.Version }}" +app.kubernetes.io/part-of: {{ template "helm-project-operator.name" . }} +chart: {{ template "helm-project-operator.chartref" . }} +release: {{ $.Release.Name | quote }} +heritage: {{ $.Release.Service | quote }} +{{- if .Values.commonLabels}} +{{ toYaml .Values.commonLabels }} +{{- end }} +{{- end -}} + +{{/* Define the image registry to use; either values, or systemdefault if set, or nothing */}} +{{- define "helm-project-operator.imageRegistry" -}} +{{- if .Values.image.registry }}{{- printf "%s/" .Values.image.registry -}} +{{- else }}{{ template "system_default_registry" . }} +{{- end }} +{{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/cleanup.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/cleanup.yaml new file mode 100644 index 00000000..98675642 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/cleanup.yaml @@ -0,0 +1,82 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: {{ template "helm-project-operator.name" . }}-cleanup + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + app: {{ template "helm-project-operator.name" . }} + annotations: + "helm.sh/hook": pre-delete + "helm.sh/hook-delete-policy": before-hook-creation, hook-succeeded, hook-failed +spec: + template: + metadata: + name: {{ template "helm-project-operator.name" . }}-cleanup + labels: {{ include "helm-project-operator.labels" . | nindent 8 }} + app: {{ template "helm-project-operator.name" . }} + spec: + serviceAccountName: {{ template "helm-project-operator.name" . }} +{{- if .Values.cleanup.securityContext }} + securityContext: {{ toYaml .Values.cleanup.securityContext | nindent 8 }} +{{- end }} + initContainers: + - name: add-cleanup-annotations + image: {{ template "system_default_registry" . }}{{ .Values.cleanup.image.repository }}:{{ .Values.cleanup.image.tag }} + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + command: + - /bin/sh + - -c + - > + echo "Labeling all ProjectHelmCharts with helm.cattle.io/helm-project-operator-cleanup=true"; + EXPECTED_HELM_API_VERSION={{ .Values.helmApiVersion }}; + IFS=$'\n'; + for namespace in $(kubectl get namespaces -l helm.cattle.io/helm-project-operated=true --no-headers -o=custom-columns=NAME:.metadata.name); do + for projectHelmChartAndHelmApiVersion in $(kubectl get projecthelmcharts -n ${namespace} --no-headers -o=custom-columns=NAME:.metadata.name,HELMAPIVERSION:.spec.helmApiVersion); do + projectHelmChartAndHelmApiVersion=$(echo ${projectHelmChartAndHelmApiVersion} | xargs); + projectHelmChart=$(echo ${projectHelmChartAndHelmApiVersion} | cut -d' ' -f1); + helmApiVersion=$(echo ${projectHelmChartAndHelmApiVersion} | cut -d' ' -f2); + if [[ ${helmApiVersion} != ${EXPECTED_HELM_API_VERSION} ]]; then + echo "Skipping marking ${namespace}/${projectHelmChart} with cleanup annotation since spec.helmApiVersion: ${helmApiVersion} is not ${EXPECTED_HELM_API_VERSION}"; + continue; + fi; + kubectl label projecthelmcharts -n ${namespace} ${projectHelmChart} helm.cattle.io/helm-project-operator-cleanup=true --overwrite; + done; + done; +{{- if .Values.cleanup.resources }} + resources: {{ toYaml .Values.cleanup.resources | nindent 12 }} +{{- end }} +{{- if .Values.cleanup.containerSecurityContext }} + securityContext: {{ toYaml .Values.cleanup.containerSecurityContext | nindent 12 }} +{{- end }} + containers: + - name: ensure-subresources-deleted + image: {{ template "system_default_registry" . }}{{ .Values.cleanup.image.repository }}:{{ .Values.cleanup.image.tag }} + imagePullPolicy: IfNotPresent + command: + - /bin/sh + - -c + - > + SYSTEM_NAMESPACE={{ .Release.Namespace }} + EXPECTED_HELM_API_VERSION={{ .Values.helmApiVersion }}; + HELM_API_VERSION_TRUNCATED=$(echo ${EXPECTED_HELM_API_VERSION} | cut -d'/' -f0); + echo "Ensuring HelmCharts and HelmReleases are deleted from ${SYSTEM_NAMESPACE}..."; + while [[ "$(kubectl get helmcharts,helmreleases -l helm.cattle.io/helm-api-version=${HELM_API_VERSION_TRUNCATED} -n ${SYSTEM_NAMESPACE} 2>&1)" != "No resources found in ${SYSTEM_NAMESPACE} namespace." ]]; do + echo "waiting for HelmCharts and HelmReleases to be deleted from ${SYSTEM_NAMESPACE}... sleeping 3 seconds"; + sleep 3; + done; + echo "Successfully deleted all HelmCharts and HelmReleases in ${SYSTEM_NAMESPACE}!"; +{{- if .Values.cleanup.resources }} + resources: {{ toYaml .Values.cleanup.resources | nindent 12 }} +{{- end }} +{{- if .Values.cleanup.containerSecurityContext }} + securityContext: {{ toYaml .Values.cleanup.containerSecurityContext | nindent 12 }} +{{- end }} + restartPolicy: OnFailure + nodeSelector: {{ include "linux-node-selector" . | nindent 8 }} + {{- if .Values.cleanup.nodeSelector }} + {{- toYaml .Values.cleanup.nodeSelector | nindent 8 }} + {{- end }} + tolerations: {{ include "linux-node-tolerations" . | nindent 8 }} + {{- if .Values.cleanup.tolerations }} + {{- toYaml .Values.cleanup.tolerations | nindent 8 }} + {{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/clusterrole.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/clusterrole.yaml new file mode 100644 index 00000000..60ed263b --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/clusterrole.yaml @@ -0,0 +1,57 @@ +{{- if and .Values.global.rbac.create .Values.global.rbac.userRoles.create }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "helm-project-operator.name" . }}-admin + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + {{- if .Values.global.rbac.userRoles.aggregateToDefaultRoles }} + rbac.authorization.k8s.io/aggregate-to-admin: "true" + {{- end }} +rules: +- apiGroups: + - helm.cattle.io + resources: + - projecthelmcharts + - projecthelmcharts/finalizers + - projecthelmcharts/status + verbs: + - '*' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "helm-project-operator.name" . }}-edit + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + {{- if .Values.global.rbac.userRoles.aggregateToDefaultRoles }} + rbac.authorization.k8s.io/aggregate-to-edit: "true" + {{- end }} +rules: +- apiGroups: + - helm.cattle.io + resources: + - projecthelmcharts + - projecthelmcharts/status + verbs: + - 'get' + - 'list' + - 'watch' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: {{ template "helm-project-operator.name" . }}-view + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + {{- if .Values.global.rbac.userRoles.aggregateToDefaultRoles }} + rbac.authorization.k8s.io/aggregate-to-view: "true" + {{- end }} +rules: +- apiGroups: + - helm.cattle.io + resources: + - projecthelmcharts + - projecthelmcharts/status + verbs: + - 'get' + - 'list' + - 'watch' +{{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/configmap.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/configmap.yaml new file mode 100644 index 00000000..d4def157 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/configmap.yaml @@ -0,0 +1,14 @@ +## Note: If you add another entry to this ConfigMap, make sure a corresponding env var is set +## in the deployment of the operator to ensure that a Helm upgrade will force the operator +## to reload the values in the ConfigMap and redeploy +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "helm-project-operator.name" . }}-config + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} +data: + hardened.yaml: |- +{{ .Values.hardenedNamespaces.configuration | toYaml | indent 4 }} + values.yaml: |- +{{ .Values.valuesOverride | toYaml | indent 4 }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml new file mode 100644 index 00000000..b31bf390 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml @@ -0,0 +1,130 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ template "helm-project-operator.name" . }} + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + app: {{ template "helm-project-operator.name" . }} +spec: + {{- if .Values.replicas }} + replicas: {{ .Values.replicas }} + {{- end }} + selector: + matchLabels: + app: {{ template "helm-project-operator.name" . }} + release: {{ $.Release.Name | quote }} + template: + metadata: + labels: {{ include "helm-project-operator.labels" . | nindent 8 }} + app: {{ template "helm-project-operator.name" . }} + spec: + containers: + - name: {{ template "helm-project-operator.name" . }} + image: "{{ template "helm-project-operator.imageRegistry" . }}{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + args: + - {{ template "helm-project-operator.name" . }} + - --namespace={{ template "helm-project-operator.namespace" . }} + - --controller-name={{ template "helm-project-operator.name" . }} + - --values-override-file=/etc/helmprojectoperator/config/values.yaml +{{- if .Values.global.cattle.systemDefaultRegistry }} + - --system-default-registry={{ .Values.global.cattle.systemDefaultRegistry }} +{{- end }} +{{- if .Values.global.cattle.url }} + - --cattle-url={{ .Values.global.cattle.url }} +{{- end }} +{{- if .Values.global.cattle.projectLabel }} + - --project-label={{ .Values.global.cattle.projectLabel }} +{{- end }} +{{- if not .Values.projectReleaseNamespaces.enabled }} + - --system-project-label-values={{ join "," (append .Values.otherSystemProjectLabelValues .Values.global.cattle.systemProjectId) }} +{{- else if and (ne (len .Values.global.cattle.systemProjectId) 0) (ne (len .Values.projectReleaseNamespaces.labelValue) 0) (ne .Values.projectReleaseNamespaces.labelValue .Values.global.cattle.systemProjectId) }} + - --system-project-label-values={{ join "," (append .Values.otherSystemProjectLabelValues .Values.global.cattle.systemProjectId) }} +{{- else if len .Values.otherSystemProjectLabelValues }} + - --system-project-label-values={{ join "," .Values.otherSystemProjectLabelValues }} +{{- end }} +{{- if .Values.projectReleaseNamespaces.enabled }} +{{- if .Values.projectReleaseNamespaces.labelValue }} + - --project-release-label-value={{ .Values.projectReleaseNamespaces.labelValue }} +{{- else if .Values.global.cattle.systemProjectId }} + - --project-release-label-value={{ .Values.global.cattle.systemProjectId }} +{{- end }} +{{- end }} +{{- if .Values.global.cattle.clusterId }} + - --cluster-id={{ .Values.global.cattle.clusterId }} +{{- end }} +{{- if .Values.releaseRoleBindings.aggregate }} +{{- if .Values.releaseRoleBindings.clusterRoleRefs }} +{{- if .Values.releaseRoleBindings.clusterRoleRefs.admin }} + - --admin-cluster-role={{ .Values.releaseRoleBindings.clusterRoleRefs.admin }} +{{- end }} +{{- if .Values.releaseRoleBindings.clusterRoleRefs.edit }} + - --edit-cluster-role={{ .Values.releaseRoleBindings.clusterRoleRefs.edit }} +{{- end }} +{{- if .Values.releaseRoleBindings.clusterRoleRefs.view }} + - --view-cluster-role={{ .Values.releaseRoleBindings.clusterRoleRefs.view }} +{{- end }} +{{- end }} +{{- end }} +{{- if .Values.hardenedNamespaces.enabled }} + - --hardening-options-file=/etc/helmprojectoperator/config/hardening.yaml +{{- else }} + - --disable-hardening +{{- end }} +{{- if .Values.debug }} + - --debug + - --debug-level={{ .Values.debugLevel }} +{{- end }} +{{- if not .Values.helmController.enabled }} + - --disable-embedded-helm-controller +{{- else }} + - --helm-job-image={{ template "system_default_registry" . }}{{ .Values.helmController.job.image.repository }}:{{ .Values.helmController.job.image.tag }} +{{- end }} +{{- if not .Values.helmLocker.enabled }} + - --disable-embedded-helm-locker +{{- end }} +{{- if .Values.additionalArgs }} +{{- toYaml .Values.additionalArgs | nindent 10 }} +{{- end }} + env: + - name: NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + ## Note: The below two values only exist to force Helm to upgrade the deployment on + ## a change to the contents of the ConfigMap during an upgrade. Neither serve + ## any practical purpose and can be removed and replaced with a configmap reloader + ## in a future change if dynamic updates are required. + - name: HARDENING_OPTIONS_SHA_256_HASH + value: {{ .Values.hardenedNamespaces.configuration | toYaml | sha256sum }} + - name: VALUES_OVERRIDE_SHA_256_HASH + value: {{ .Values.valuesOverride | toYaml | sha256sum }} + {{- if .Values.crdManagement.update }} + - name: MANAGE_CRD_UPDATES + value: "true" + {{- end }} +{{- if .Values.resources }} + resources: {{ toYaml .Values.resources | nindent 12 }} +{{- end }} +{{- if .Values.containerSecurityContext }} + securityContext: {{ toYaml .Values.containerSecurityContext | nindent 12 }} +{{- end }} + volumeMounts: + - name: config + mountPath: "/etc/helmprojectoperator/config" + serviceAccountName: {{ template "helm-project-operator.name" . }} +{{- if .Values.securityContext }} + securityContext: {{ toYaml .Values.securityContext | nindent 8 }} +{{- end }} + nodeSelector: {{ include "linux-node-selector" . | nindent 8 }} +{{- if .Values.nodeSelector }} +{{- toYaml .Values.nodeSelector | nindent 8 }} +{{- end }} + tolerations: {{ include "linux-node-tolerations" . | nindent 8 }} +{{- if .Values.tolerations }} +{{- toYaml .Values.tolerations | nindent 8 }} +{{- end }} + volumes: + - name: config + configMap: + name: {{ template "helm-project-operator.name" . }}-config diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/psp.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/psp.yaml new file mode 100644 index 00000000..73dcc456 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/psp.yaml @@ -0,0 +1,68 @@ +{{- if .Values.global.cattle.psp.enabled }} +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + name: {{ template "helm-project-operator.name" . }}-psp + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + app: {{ template "helm-project-operator.name" . }} +{{- if .Values.global.rbac.pspAnnotations }} + annotations: {{ toYaml .Values.global.rbac.pspAnnotations | nindent 4 }} +{{- end }} +spec: + privileged: false + hostNetwork: false + hostIPC: false + hostPID: false + runAsUser: + # Permits the container to run with root privileges as well. + rule: 'RunAsAny' + seLinux: + # This policy assumes the nodes are using AppArmor rather than SELinux. + rule: 'RunAsAny' + supplementalGroups: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + fsGroup: + rule: 'MustRunAs' + ranges: + # Forbid adding the root group. + - min: 0 + max: 65535 + readOnlyRootFilesystem: false +--- +kind: ClusterRole +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "helm-project-operator.name" . }}-psp + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + app: {{ template "helm-project-operator.name" . }} +rules: +{{- if semverCompare "> 1.15.0-0" .Capabilities.KubeVersion.GitVersion }} +- apiGroups: ['policy'] +{{- else }} +- apiGroups: ['extensions'] +{{- end }} + resources: ['podsecuritypolicies'] + verbs: ['use'] + resourceNames: + - {{ template "helm-project-operator.name" . }}-psp +--- +kind: ClusterRoleBinding +apiVersion: rbac.authorization.k8s.io/v1 +metadata: + name: {{ template "helm-project-operator.name" . }}-psp + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + app: {{ template "helm-project-operator.name" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: {{ template "helm-project-operator.name" . }}-psp +subjects: + - kind: ServiceAccount + name: {{ template "helm-project-operator.name" . }} + namespace: {{ template "helm-project-operator.namespace" . }} +{{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/rbac.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/rbac.yaml new file mode 100644 index 00000000..b1c40920 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/rbac.yaml @@ -0,0 +1,32 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: {{ template "helm-project-operator.name" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + app: {{ template "helm-project-operator.name" . }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: "cluster-admin" # see note below +subjects: +- kind: ServiceAccount + name: {{ template "helm-project-operator.name" . }} + namespace: {{ template "helm-project-operator.namespace" . }} +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ template "helm-project-operator.name" . }} + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} + app: {{ template "helm-project-operator.name" . }} +{{- if .Values.global.imagePullSecrets }} +imagePullSecrets: {{ toYaml .Values.global.imagePullSecrets | nindent 2 }} +{{- end }} +# --- +# NOTE: +# As of now, due to the fact that the k3s-io/helm-controller can only deploy jobs that are cluster-bound to the cluster-admin +# ClusterRole, the only way for this operator to be able to perform that binding is if it is also bound to the cluster-admin ClusterRole. +# +# As a result, this ClusterRoleBinding will be left as a work-in-progress until changes are made in k3s-io/helm-controller to allow us to grant +# only scoped down permissions to the Job that is deployed. diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/system-namespaces-configmap.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/system-namespaces-configmap.yaml new file mode 100644 index 00000000..f4c85254 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/system-namespaces-configmap.yaml @@ -0,0 +1,62 @@ +{{- if .Values.systemNamespacesConfigMap.create }} +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ template "helm-project-operator.name" . }}-system-namespaces + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} +data: + system-namespaces.json: |- + { +{{- if .Values.projectReleaseNamespaces.enabled }} +{{- if .Values.projectReleaseNamespaces.labelValue }} + "projectReleaseLabelValue": {{ .Values.projectReleaseNamespaces.labelValue | quote }}, +{{- else if .Values.global.cattle.systemProjectId }} + "projectReleaseLabelValue": {{ .Values.global.cattle.systemProjectId | quote }}, +{{- else }} + "projectReleaseLabelValue": "", +{{- end }} +{{- else }} + "projectReleaseLabelValue": "", +{{- end }} +{{- if not .Values.projectReleaseNamespaces.enabled }} + "systemProjectLabelValues": {{ append .Values.otherSystemProjectLabelValues .Values.global.cattle.systemProjectId | toJson }} +{{- else if and (ne (len .Values.global.cattle.systemProjectId) 0) (ne (len .Values.projectReleaseNamespaces.labelValue) 0) (ne .Values.projectReleaseNamespaces.labelValue .Values.global.cattle.systemProjectId) }} + "systemProjectLabelValues": {{ append .Values.otherSystemProjectLabelValues .Values.global.cattle.systemProjectId | toJson }} +{{- else if len .Values.otherSystemProjectLabelValues }} + "systemProjectLabelValues": {{ .Values.otherSystemProjectLabelValues | toJson }} +{{- else }} + "systemProjectLabelValues": [] +{{- end }} + } +--- +{{- if (and .Values.systemNamespacesConfigMap.rbac.enabled .Values.systemNamespacesConfigMap.rbac.subjects) }} +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ template "helm-project-operator.name" . }}-system-namespaces + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} +rules: +- apiGroups: + - "" + resources: + - configmaps + resourceNames: + - "{{ template "helm-project-operator.name" . }}-system-namespaces" + verbs: + - 'get' +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ template "helm-project-operator.name" . }}-system-namespaces + namespace: {{ template "helm-project-operator.namespace" . }} + labels: {{ include "helm-project-operator.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ template "helm-project-operator.name" . }}-system-namespaces +subjects: {{ .Values.systemNamespacesConfigMap.rbac.subjects | toYaml | nindent 2 }} +{{- end }} +{{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/validate-psp-install.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/validate-psp-install.yaml new file mode 100644 index 00000000..a30c59d3 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/validate-psp-install.yaml @@ -0,0 +1,7 @@ +#{{- if gt (len (lookup "rbac.authorization.k8s.io/v1" "ClusterRole" "" "")) 0 -}} +#{{- if .Values.global.cattle.psp.enabled }} +#{{- if not (.Capabilities.APIVersions.Has "policy/v1beta1/PodSecurityPolicy") }} +#{{- fail "The target cluster does not have the PodSecurityPolicy API resource. Please disable PSPs in this chart before proceeding." -}} +#{{- end }} +#{{- end }} +#{{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml new file mode 100644 index 00000000..7dad4847 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml @@ -0,0 +1,236 @@ +# Default values for helm-project-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Helm Project Operator Configuration + +global: + cattle: + clusterId: "" + psp: + enabled: false + projectLabel: field.cattle.io/projectId + systemDefaultRegistry: "" + systemProjectId: "" + url: "" + rbac: + ## Create RBAC resources for ServiceAccounts and users + ## + create: true + + userRoles: + ## Create default user ClusterRoles to allow users to interact with ProjectHelmCharts + create: true + ## Aggregate default user ClusterRoles into default k8s ClusterRoles + aggregateToDefaultRoles: true + + pspAnnotations: {} + ## Specify pod annotations + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#apparmor + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#sysctl + ## + # seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default' + # apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + + ## Reference to one or more secrets to be used when pulling images + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + imagePullSecrets: [] + # - name: "image-pull-secret" + +helmApiVersion: dummy.cattle.io/v1alpha1 + +## valuesOverride overrides values that are set on each ProjectHelmChart deployment on an operator-level +## User-provided values will be overwritten based on the values provided here +valuesOverride: {} + +## projectReleaseNamespaces are auto-generated namespaces that are created to host Helm Releases +## managed by this operator on behalf of a ProjectHelmChart +projectReleaseNamespaces: + ## Enabled determines whether Project Release Namespaces should be created. If false, the underlying + ## Helm release will be deployed in the Project Registration Namespace + enabled: true + ## labelValue is the value of the Project that the projectReleaseNamespace should be created within + ## If empty, this will be set to the value of global.cattle.systemProjectId + ## If global.cattle.systemProjectId is also empty, project release namespaces will be disabled + labelValue: "" + +## otherSystemProjectLabelValues are project labels that identify namespaces as those that should be treated as system projects +## i.e. they will be entirely ignored by the operator +## By default, the global.cattle.systemProjectId will be in this list +otherSystemProjectLabelValues: [] + +## releaseRoleBindings configures RoleBindings automatically created by the Helm Project Operator +## in Project Release Namespaces where underlying Helm charts are deployed +releaseRoleBindings: + ## aggregate enables creating these RoleBindings off aggregating RoleBindings in the + ## Project Registration Namespace or ClusterRoleBindings that bind users to the ClusterRoles + ## specified under clusterRoleRefs + aggregate: true + + ## clusterRoleRefs are the ClusterRoles whose RoleBinding or ClusterRoleBindings should determine + ## the RoleBindings created in the Project Release Namespace + ## + ## By default, these are set to create RoleBindings based on the RoleBindings / ClusterRoleBindings + ## attached to the default K8s user-facing ClusterRoles of admin, edit, and view. + ## ref: https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles + ## + clusterRoleRefs: + admin: admin + edit: edit + view: view + +hardenedNamespaces: + # Whether to automatically manage the configuration of the default ServiceAccount and + # auto-create a NetworkPolicy for each namespace created by this operator + enabled: true + + configuration: + # Values to be applied to each default ServiceAccount created in a managed namespace + serviceAccountSpec: + secrets: [] + imagePullSecrets: [] + automountServiceAccountToken: false + # Values to be applied to each default generated NetworkPolicy created in a managed namespace + networkPolicySpec: + podSelector: {} + egress: [] + ingress: [] + policyTypes: ["Ingress", "Egress"] + +## systemNamespacesConfigMap is a ConfigMap created to allow users to see valid entries +## for registering a ProjectHelmChart for a given Project on the Rancher Dashboard UI. +## It does not need to be enabled for a non-Rancher use case. +systemNamespacesConfigMap: + ## Create indicates whether the system namespaces configmap should be created + ## This is a required value for integration with Rancher Dashboard + create: true + + ## RBAC provides options around the RBAC created to allow users to be able to view + ## the systemNamespacesConfigMap; if not specified, only users with the ability to + ## view ConfigMaps in the namespace where this chart is deployed will be able to + ## properly view the system namespaces on the Rancher Dashboard UI + rbac: + ## enabled indicates that we should deploy a RoleBinding and Role to view this ConfigMap + enabled: true + ## subjects are the subjects that should be bound to this default RoleBinding + ## By default, we allow anyone who is authenticated to the system to be able to view + ## this ConfigMap in the deployment namespace + subjects: + - kind: Group + name: system:authenticated + +nameOverride: "" + +namespaceOverride: "" + +replicas: 1 + +# Configure how the operator will manage the CustomResourceDefinitions (CRDs) it needs to function. +crdManagement: + # Enable or disable automatic updates of CRDs during startup. + # When true, all CRDs will be udpated to the version the operator provides. + # When false, only missing CRDs will be installed, and existing ones will not be updated. + update: true + +image: + registry: ghcr.io + repository: rancher/prometheus-federator/helm-project-operator + tag: '' + pullPolicy: IfNotPresent + +helmController: + # Note: should be disabled for RKE2 clusters since they already run Helm Controller to manage internal Kubernetes components + enabled: true + + job: + image: + repository: rancher/klipper-helm + tag: v0.7.0-build20220315 + +helmLocker: + enabled: true + +# Additional arguments to be passed into the Helm Project Operator image +additionalArgs: [] + +## Define which Nodes the Pods are scheduled on. +## ref: https://kubernetes.io/docs/user-guide/node-selection/ +## +nodeSelector: {} + +## Tolerations for use with node taints +## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ +## +tolerations: [] +# - key: "key" +# operator: "Equal" +# value: "value" +# effect: "NoSchedule" + +resources: {} + # limits: + # memory: 500Mi + # cpu: 1000m + # requests: + # memory: 100Mi + # cpu: 100m + +containerSecurityContext: {} + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - ALL + # privileged: false + # readOnlyRootFilesystem: true + +securityContext: {} + # runAsGroup: 1000 + # runAsUser: 1000 + # supplementalGroups: + # - 1000 + +debug: false +debugLevel: 0 + +cleanup: + image: + repository: rancher/shell + tag: v0.1.19 + pullPolicy: IfNotPresent + + ## Define which Nodes the Pods are scheduled on. + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for use with node taints + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal" + # value: "value" + # effect: "NoSchedule" + + containerSecurityContext: {} + # allowPrivilegeEscalation: false + # capabilities: + # drop: + # - ALL + # privileged: false + # readOnlyRootFilesystem: true + + securityContext: + runAsNonRoot: false + runAsUser: 0 + + resources: {} + # limits: + # memory: 500Mi + # cpu: 1000m + # requests: + # memory: 100Mi + # cpu: 100m diff --git a/charts/prometheus-federator/0.4.5-rc.1/questions.yaml b/charts/prometheus-federator/0.4.5-rc.1/questions.yaml new file mode 100644 index 00000000..87cf1339 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/questions.yaml @@ -0,0 +1,43 @@ +questions: +- variable: global.cattle.psp.enabled + default: "false" + description: "Flag to enable or disable the installation of PodSecurityPolicies by this chart in the target cluster. If the cluster is running Kubernetes 1.25+, you must update this value to false." + label: "Enable PodSecurityPolicies" + type: boolean + group: "Security Settings" +- variable: helmProjectOperator.helmController.enabled + label: Enable Embedded Helm Controller + description: 'Note: If you are running Prometheus Federator in an RKE2 / K3s cluster before v1.23.14 / v1.24.8 / v1.25.4, this should be disabled.' + type: boolean + group: Helm Controller +- variable: helmProjectOperator.helmLocker.enabled + label: Enable Embedded Helm Locker + type: boolean + group: Helm Locker +- variable: helmProjectOperator.projectReleaseNamespaces.labelValue + label: Project Release Namespace Project ID + description: By default, the System Project is selected. This can be overriden to a different Project (e.g. p-xxxxx) + type: string + required: false + group: Namespaces +- variable: helmProjectOperator.releaseRoleBindings.clusterRoleRefs.admin + label: Admin ClusterRole + description: By default, admin selects Project Owners. This can be overridden to a different ClusterRole (e.g. rt-xxxxx) + type: string + default: admin + required: false + group: RBAC +- variable: helmProjectOperator.releaseRoleBindings.clusterRoleRefs.edit + label: Edit ClusterRole + description: By default, edit selects Project Members. This can be overridden to a different ClusterRole (e.g. rt-xxxxx) + type: string + default: edit + required: false + group: RBAC +- variable: helmProjectOperator.releaseRoleBindings.clusterRoleRefs.view + label: View ClusterRole + description: By default, view selects Read-Only users. This can be overridden to a different ClusterRole (e.g. rt-xxxxx) + type: string + default: view + required: false + group: RBAC \ No newline at end of file diff --git a/charts/prometheus-federator/0.4.5-rc.1/templates/NOTES.txt b/charts/prometheus-federator/0.4.5-rc.1/templates/NOTES.txt new file mode 100644 index 00000000..f551f366 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/templates/NOTES.txt @@ -0,0 +1,3 @@ +{{ $.Chart.Name }} has been installed. Check its status by running: + kubectl --namespace {{ template "prometheus-federator.namespace" . }} get pods -l "release={{ $.Release.Name }}" + diff --git a/charts/prometheus-federator/0.4.5-rc.1/templates/_helpers.tpl b/charts/prometheus-federator/0.4.5-rc.1/templates/_helpers.tpl new file mode 100644 index 00000000..15ea4e5c --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/templates/_helpers.tpl @@ -0,0 +1,66 @@ +# Rancher +{{- define "system_default_registry" -}} +{{- if .Values.global.cattle.systemDefaultRegistry -}} +{{- printf "%s/" .Values.global.cattle.systemDefaultRegistry -}} +{{- end -}} +{{- end -}} + +# Windows Support + +{{/* +Windows cluster will add default taint for linux nodes, +add below linux tolerations to workloads could be scheduled to those linux nodes +*/}} + +{{- define "linux-node-tolerations" -}} +- key: "cattle.io/os" + value: "linux" + effect: "NoSchedule" + operator: "Equal" +{{- end -}} + +{{- define "linux-node-selector" -}} +{{- if semverCompare "<1.14-0" .Capabilities.KubeVersion.GitVersion -}} +beta.kubernetes.io/os: linux +{{- else -}} +kubernetes.io/os: linux +{{- end -}} +{{- end -}} + +# Helm Project Operator + +{{/* vim: set filetype=mustache: */}} +{{/* Expand the name of the chart. This is suffixed with -alertmanager, which means subtract 13 from longest 63 available */}} +{{- define "prometheus-federator.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 50 | trimSuffix "-" -}} +{{- end }} + +{{/* +Allow the release namespace to be overridden for multi-namespace deployments in combined charts +*/}} +{{- define "prometheus-federator.namespace" -}} + {{- if .Values.namespaceOverride -}} + {{- .Values.namespaceOverride -}} + {{- else -}} + {{- .Release.Namespace -}} + {{- end -}} +{{- end -}} + +{{/* Create chart name and version as used by the chart label. */}} +{{- define "prometheus-federator.chartref" -}} +{{- replace "+" "_" .Chart.Version | printf "%s-%s" .Chart.Name -}} +{{- end }} + +{{/* Generate basic labels */}} +{{- define "prometheus-federator.labels" }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +app.kubernetes.io/instance: {{ .Release.Name }} +app.kubernetes.io/version: "{{ replace "+" "_" .Chart.Version }}" +app.kubernetes.io/part-of: {{ template "prometheus-federator.name" . }} +chart: {{ template "prometheus-federator.chartref" . }} +release: {{ $.Release.Name | quote }} +heritage: {{ $.Release.Service | quote }} +{{- if .Values.commonLabels}} +{{ toYaml .Values.commonLabels }} +{{- end }} +{{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/values.yaml b/charts/prometheus-federator/0.4.5-rc.1/values.yaml new file mode 100644 index 00000000..7bb63e51 --- /dev/null +++ b/charts/prometheus-federator/0.4.5-rc.1/values.yaml @@ -0,0 +1,100 @@ +# Default values for helm-project-operator. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +# Prometheus Federator Configuration + +global: + cattle: + psp: + enabled: false + systemDefaultRegistry: "" + projectLabel: field.cattle.io/projectId + clusterId: "" + systemProjectId: "" + url: "" + rbac: + pspEnabled: true + pspAnnotations: {} + ## Specify pod annotations + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#apparmor + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp + ## Ref: https://kubernetes.io/docs/concepts/policy/pod-security-policy/#sysctl + ## + # seccomp.security.alpha.kubernetes.io/allowedProfileNames: '*' + # seccomp.security.alpha.kubernetes.io/defaultProfileName: 'docker/default' + # apparmor.security.beta.kubernetes.io/defaultProfileName: 'runtime/default' + + ## Reference to one or more secrets to be used when pulling images + ## ref: https://kubernetes.io/docs/tasks/configure-pod-container/pull-image-private-registry/ + ## + imagePullSecrets: [] + # - name: "image-pull-secret" + +helmProjectOperator: + # ensures that all resources created by subchart show up as prometheus-federator + helmApiVersion: monitoring.cattle.io/v1alpha1 + + nameOverride: prometheus-federator + + # Configure how the operator will manage the CustomResourceDefinitions (CRDs) it needs to function. + crdManagement: + # Enable or disable automatic updates of CRDs during startup. + # When true, all CRDs will be udpated to the version the operator provides. + # When false, only missing CRDs will be installed, and existing ones will not be updated. + update: true + + helmController: + # Note: should be disabled for RKE2 clusters since they already run Helm Controller to manage internal Kubernetes components + enabled: true + + helmLocker: + enabled: true + + ## valuesOverride overrides values that are set on each Project Prometheus Stack Helm Chart deployment on an operator level + ## all values provided here will override any user-provided values automatically + valuesOverride: + + federate: + # Change this to point at all Prometheuses you want all your Project Prometheus Stacks to federate from + # By default, this matches the default deployment of Rancher Monitoring + targets: + - rancher-monitoring-prometheus.cattle-monitoring-system.svc:9090 + + image: + registry: '' + repository: rancher/prometheus-federator + tag: '' + pullPolicy: IfNotPresent + + # Additional arguments to be passed into the Prometheus Federator image + additionalArgs: [] + + ## Define which Nodes the Pods are scheduled on. + ## ref: https://kubernetes.io/docs/user-guide/node-selection/ + ## + nodeSelector: {} + + ## Tolerations for use with node taints + ## ref: https://kubernetes.io/docs/concepts/configuration/taint-and-toleration/ + ## + tolerations: [] + # - key: "key" + # operator: "Equal" + # value: "value" + # effect: "NoSchedule" + + resources: {} + # limits: + # memory: 500Mi + # cpu: 1000m + # requests: + # memory: 100Mi + # cpu: 100m + + securityContext: {} + # allowPrivilegeEscalation: false + # readOnlyRootFilesystem: true + + debug: false + debugLevel: 0 \ No newline at end of file diff --git a/index.yaml b/index.yaml index bb6c9ece..45462f95 100755 --- a/index.yaml +++ b/index.yaml @@ -1,6 +1,28 @@ apiVersion: v1 entries: prometheus-federator: + - annotations: + catalog.cattle.io/certified: rancher + catalog.cattle.io/display-name: Prometheus Federator + catalog.cattle.io/namespace: cattle-monitoring-system + catalog.cattle.io/os: linux,windows + catalog.cattle.io/permits-os: linux,windows + catalog.cattle.io/provides-gvr: helm.cattle.io.projecthelmchart/v1alpha1 + catalog.cattle.io/release-name: prometheus-federator + apiVersion: v2 + appVersion: 0.4.3 + created: "2024-12-11T17:17:02.602449-05:00" + dependencies: + - name: helmProjectOperator + repository: file://./charts/helmProjectOperator + version: 0.3.2 + description: Prometheus Federator + digest: 7b618916a729cd82b5e4617bf4ee18ea4e96498986e512e8aa5c41d889c49a18 + icon: https://raw.githubusercontent.com/rancher/prometheus-federator/main/assets/logos/prometheus-federator.svg + name: prometheus-federator + urls: + - assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz + version: 0.4.5-rc.1 - annotations: catalog.cattle.io/certified: rancher catalog.cattle.io/display-name: Prometheus Federator From 1566b2289ff459fc8ecaf6c5e320e21ca2eea3a1 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Wed, 11 Dec 2024 16:51:09 -0500 Subject: [PATCH 11/20] Add ability to determine if PromFed should manage HelmController CRDs For now we indirectly rely on checking nodes for k8s runtime type, then based on if we find k3s/rke2 we skip them. In the future, we'd like to directly be able to query the CRD on the cluster and see a clear annotation/label citing the CRDs owner. --- .../charts/templates/deployment.yaml | 4 ++ .../helm-project-operator/charts/values.yaml | 4 ++ .../prometheus-federator/charts/values.yaml | 4 ++ pkg/helm-project-operator/crd/crds.go | 58 ++++++++++++++++++- 4 files changed, 69 insertions(+), 1 deletion(-) diff --git a/packages/helm-project-operator/charts/templates/deployment.yaml b/packages/helm-project-operator/charts/templates/deployment.yaml index b31bf390..e064ca86 100644 --- a/packages/helm-project-operator/charts/templates/deployment.yaml +++ b/packages/helm-project-operator/charts/templates/deployment.yaml @@ -103,6 +103,10 @@ spec: - name: MANAGE_CRD_UPDATES value: "true" {{- end }} + {{- if .Values.crdManagement.detectK3sRke2 }} + - name: DETECT_K3S_RKE2 + value: "true" + {{- end }} {{- if .Values.resources }} resources: {{ toYaml .Values.resources | nindent 12 }} {{- end }} diff --git a/packages/helm-project-operator/charts/values.yaml b/packages/helm-project-operator/charts/values.yaml index 7dad4847..e1c2a7e2 100644 --- a/packages/helm-project-operator/charts/values.yaml +++ b/packages/helm-project-operator/charts/values.yaml @@ -134,6 +134,10 @@ crdManagement: # When true, all CRDs will be udpated to the version the operator provides. # When false, only missing CRDs will be installed, and existing ones will not be updated. update: true + # Specify whether the operator should detect K3s and RKE2 clusters and exclude `helm-controller` CRDs from management. + # When true, `helm-controller` CRDs will not be managed by the operator in these environments, as K3s/RKE2 handle them internally. + # When false, the operator will manage `helm-controller` CRDs regardless of the runtime environment. + detectK3sRke2: false image: registry: ghcr.io diff --git a/packages/prometheus-federator/charts/values.yaml b/packages/prometheus-federator/charts/values.yaml index 7bb63e51..5615ccf8 100755 --- a/packages/prometheus-federator/charts/values.yaml +++ b/packages/prometheus-federator/charts/values.yaml @@ -43,6 +43,10 @@ helmProjectOperator: # When true, all CRDs will be udpated to the version the operator provides. # When false, only missing CRDs will be installed, and existing ones will not be updated. update: true + # Specify whether the operator should detect K3s and RKE2 clusters and exclude `helm-controller` CRDs from management. + # When true, `helm-controller` CRDs will not be managed by the operator in these environments, as K3s/RKE2 handle them internally. + # When false, the operator will manage `helm-controller` CRDs regardless of the runtime environment. + detectK3sRke2: false helmController: # Note: should be disabled for RKE2 clusters since they already run Helm Controller to manage internal Kubernetes components diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 005f8e0a..5931b11c 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -2,7 +2,9 @@ package crd import ( "context" + "errors" "fmt" + "github.com/rancher/wrangler/pkg/clients" "io" "os" "path/filepath" @@ -199,7 +201,9 @@ func Create(ctx context.Context, cfg *rest.Config, shouldUpdateCRDs bool) error } crdDefs = append(crdDefs, helmLockerCrdDefs...) - crdDefs = append(crdDefs, helmControllerCrdDefs...) + if shouldManageHelmControllerCRDs(cfg) { + crdDefs = append(crdDefs, helmControllerCrdDefs...) + } return factory.BatchCreateCRDs(ctx, crdDefs...).BatchWait() } @@ -244,3 +248,55 @@ func filterMissingCRDs(apiExtClient *clientset.Clientset, expectedCRDs *[]crd.CR return nil } + +func shouldManageHelmControllerCRDs(cfg *rest.Config) bool { + if os.Getenv("DETECT_K3S_RKE2") != "true" { + logrus.Debug("k3s/rke2 detection feature is disabled; `helm-controller` CRDs will be managed") + return true + } + + k8sRuntimeType, err := identifyKubernetesRuntimeType(cfg) + if err != nil { + logrus.Error(err) + } + + onK3sRke2 := k8sRuntimeType == "k3s" || k8sRuntimeType == "rke2" + if onK3sRke2 { + logrus.Debug("the cluster is running on k3s (or rke2), `helm-controller` CRDs will not be managed") + } + + return !onK3sRke2 +} + +func identifyKubernetesRuntimeType(clientConfig *rest.Config) (string, error) { + client, err := clients.NewFromConfig(clientConfig, nil) + if err != nil { + return "", err + } + + nodes, err := client.Core.Node().List(metav1.ListOptions{}) + if err != nil { + logrus.Fatalf("Failed to list nodes: %v", err) + } + instanceTypes := make(map[string]int) + for _, node := range nodes.Items { + instanceType, exists := node.Labels["node.kubernetes.io/instance-type"] + if exists { + instanceTypes[instanceType]++ + } else { + logrus.Debugf("Cannot find `node.kubernetes.io/instance-type` label on node `%s`", node.Name) + } + } + + if len(instanceTypes) == 0 { + return "", errors.New("cannot identify k8s runtime type; no nodes in cluster have expected label") + } + + var k8sRuntimeType string + for instanceType := range instanceTypes { + k8sRuntimeType = instanceType + break + } + + return k8sRuntimeType, nil +} From d36110dee057553bb470aa35a46c4463059cf620 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Wed, 11 Dec 2024 16:51:35 -0500 Subject: [PATCH 12/20] Adjust HelmChart managedBy value to respect the disable Helm Controller value --- pkg/helm-project-operator/controllers/project/resources.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pkg/helm-project-operator/controllers/project/resources.go b/pkg/helm-project-operator/controllers/project/resources.go index cb870814..65065f4c 100644 --- a/pkg/helm-project-operator/controllers/project/resources.go +++ b/pkg/helm-project-operator/controllers/project/resources.go @@ -34,8 +34,12 @@ func (h *handler) getHelmChart(projectID string, valuesContent string, projectHe }, }) helmChart.SetLabels(common2.GetHelmResourceLabels(projectID, projectHelmChart.Spec.HelmAPIVersion)) + helmChartManagedByName := h.opts.ControllerName + if h.opts.DisableEmbeddedHelmController { + helmChartManagedByName = "helm-controller" + } helmChart.SetAnnotations(map[string]string{ - chart.ManagedBy: h.opts.ControllerName, + chart.ManagedBy: helmChartManagedByName, }) return helmChart } From 54fef578e89a95d93b477864e832dd60b1549e74 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Wed, 11 Dec 2024 16:53:46 -0500 Subject: [PATCH 13/20] golang lint --- cmd/helm-project-operator/main.go | 1 + cmd/prometheus-federator/main.go | 9 +++++---- go.mod | 1 + go.sum | 1 + pkg/helm-project-operator/crd/crds.go | 3 ++- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/cmd/helm-project-operator/main.go b/cmd/helm-project-operator/main.go index b5d05549..24d02b8e 100644 --- a/cmd/helm-project-operator/main.go +++ b/cmd/helm-project-operator/main.go @@ -7,6 +7,7 @@ import ( "log" "net/http" _ "net/http/pprof" + "os" "github.com/rancher/prometheus-federator/pkg/helm-project-operator/controllers/common" "github.com/rancher/prometheus-federator/pkg/helm-project-operator/operator" diff --git a/cmd/prometheus-federator/main.go b/cmd/prometheus-federator/main.go index 6dabe90c..25ff2550 100644 --- a/cmd/prometheus-federator/main.go +++ b/cmd/prometheus-federator/main.go @@ -4,6 +4,11 @@ package main import ( _ "embed" + "log" + "net/http" + _ "net/http/pprof" + "os" + "github.com/rancher/prometheus-federator/pkg/helm-project-operator/controllers/common" "github.com/rancher/prometheus-federator/pkg/helm-project-operator/operator" "github.com/rancher/prometheus-federator/pkg/version" @@ -12,10 +17,6 @@ import ( _ "github.com/rancher/wrangler/pkg/generated/controllers/networking.k8s.io" "github.com/rancher/wrangler/pkg/kubeconfig" "github.com/spf13/cobra" - "log" - "net/http" - _ "net/http/pprof" - "os" ) const ( diff --git a/go.mod b/go.mod index b1fa2201..e4e12ebc 100644 --- a/go.mod +++ b/go.mod @@ -87,6 +87,7 @@ require ( k8s.io/gengo v0.0.0-20210813121822-485abfe95c7c // indirect k8s.io/klog v1.0.0 // indirect k8s.io/klog/v2 v2.30.0 // indirect + k8s.io/kube-aggregator v0.18.0 // indirect k8s.io/kube-openapi v0.0.0-20211115234752-e816edb12b65 // indirect k8s.io/utils v0.0.0-20220210201930-3a6ce19ff2f9 // indirect sigs.k8s.io/structured-merge-diff/v4 v4.2.1 // indirect diff --git a/go.sum b/go.sum index f1a3f30a..eb458712 100644 --- a/go.sum +++ b/go.sum @@ -1777,6 +1777,7 @@ k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= k8s.io/klog/v2 v2.30.0 h1:bUO6drIvCIsvZ/XFgfxoGFQU/a4Qkh0iAlvUR7vlHJw= k8s.io/klog/v2 v2.30.0/go.mod h1:y1WjHnz7Dj687irZUWR/WLkLc5N1YHtjLdmgWjndZn0= +k8s.io/kube-aggregator v0.18.0 h1:J+wa9FDQ3SbgyA8wQBNg2m2FMSm+mMQfs2A58500hs0= k8s.io/kube-aggregator v0.18.0/go.mod h1:ateewQ5QbjMZF/dihEFXwaEwoA4v/mayRvzfmvb6eqI= k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E= k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E= diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 5931b11c..0d879148 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -4,13 +4,14 @@ import ( "context" "errors" "fmt" - "github.com/rancher/wrangler/pkg/clients" "io" "os" "path/filepath" "strings" "sync" + "github.com/rancher/wrangler/pkg/clients" + "github.com/rancher/wrangler/pkg/name" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apimachineerrors "k8s.io/apimachinery/pkg/api/errors" From 94d7ddfcd1764531e87e3fe6dc276a5b6b9349f8 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Wed, 11 Dec 2024 17:50:34 -0500 Subject: [PATCH 14/20] make charts --- .../prometheus-federator-0.4.5-rc.1.tgz | Bin 20990 -> 21257 bytes .../templates/deployment.yaml | 4 ++++ .../charts/helmProjectOperator/values.yaml | 4 ++++ .../0.4.5-rc.1/values.yaml | 4 ++++ index.yaml | 4 ++-- 5 files changed, 14 insertions(+), 2 deletions(-) diff --git a/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz b/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz index 2e40291e8ce16df8f2578bbbd495ceb235f35d8d..082fd68700abcbdd9f0ad59065766d87a8acff2f 100644 GIT binary patch delta 21097 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 rf#GETP()am*CXOf^U<%Lub;19(~bcXDSr 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 O|Np%QXuOdCegOb5P>FB= diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml index b31bf390..e064ca86 100644 --- a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/templates/deployment.yaml @@ -103,6 +103,10 @@ spec: - name: MANAGE_CRD_UPDATES value: "true" {{- end }} + {{- if .Values.crdManagement.detectK3sRke2 }} + - name: DETECT_K3S_RKE2 + value: "true" + {{- end }} {{- if .Values.resources }} resources: {{ toYaml .Values.resources | nindent 12 }} {{- end }} diff --git a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml index 7dad4847..e1c2a7e2 100644 --- a/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml +++ b/charts/prometheus-federator/0.4.5-rc.1/charts/helmProjectOperator/values.yaml @@ -134,6 +134,10 @@ crdManagement: # When true, all CRDs will be udpated to the version the operator provides. # When false, only missing CRDs will be installed, and existing ones will not be updated. update: true + # Specify whether the operator should detect K3s and RKE2 clusters and exclude `helm-controller` CRDs from management. + # When true, `helm-controller` CRDs will not be managed by the operator in these environments, as K3s/RKE2 handle them internally. + # When false, the operator will manage `helm-controller` CRDs regardless of the runtime environment. + detectK3sRke2: false image: registry: ghcr.io diff --git a/charts/prometheus-federator/0.4.5-rc.1/values.yaml b/charts/prometheus-federator/0.4.5-rc.1/values.yaml index 7bb63e51..5615ccf8 100644 --- a/charts/prometheus-federator/0.4.5-rc.1/values.yaml +++ b/charts/prometheus-federator/0.4.5-rc.1/values.yaml @@ -43,6 +43,10 @@ helmProjectOperator: # When true, all CRDs will be udpated to the version the operator provides. # When false, only missing CRDs will be installed, and existing ones will not be updated. update: true + # Specify whether the operator should detect K3s and RKE2 clusters and exclude `helm-controller` CRDs from management. + # When true, `helm-controller` CRDs will not be managed by the operator in these environments, as K3s/RKE2 handle them internally. + # When false, the operator will manage `helm-controller` CRDs regardless of the runtime environment. + detectK3sRke2: false helmController: # Note: should be disabled for RKE2 clusters since they already run Helm Controller to manage internal Kubernetes components diff --git a/index.yaml b/index.yaml index 45462f95..bd9c0782 100755 --- a/index.yaml +++ b/index.yaml @@ -11,13 +11,13 @@ entries: catalog.cattle.io/release-name: prometheus-federator apiVersion: v2 appVersion: 0.4.3 - created: "2024-12-11T17:17:02.602449-05:00" + created: "2024-12-11T17:50:20.552678-05:00" dependencies: - name: helmProjectOperator repository: file://./charts/helmProjectOperator version: 0.3.2 description: Prometheus Federator - digest: 7b618916a729cd82b5e4617bf4ee18ea4e96498986e512e8aa5c41d889c49a18 + digest: 5660b1bbf16753fea853ed862dd9fd982a558ec3d2f393e470ee82a671e00777 icon: https://raw.githubusercontent.com/rancher/prometheus-federator/main/assets/logos/prometheus-federator.svg name: prometheus-federator urls: From 53dab3607e2e6f314b6239f3423cc1f31ce9c9f3 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 12 Dec 2024 14:01:58 -0500 Subject: [PATCH 15/20] Correct chart appVersion --- packages/prometheus-federator/charts/Chart.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/prometheus-federator/charts/Chart.yaml b/packages/prometheus-federator/charts/Chart.yaml index 3f7867a4..7a23b170 100755 --- a/packages/prometheus-federator/charts/Chart.yaml +++ b/packages/prometheus-federator/charts/Chart.yaml @@ -7,7 +7,7 @@ annotations: catalog.cattle.io/provides-gvr: helm.cattle.io.projecthelmchart/v1alpha1 catalog.cattle.io/release-name: prometheus-federator apiVersion: v2 -appVersion: 0.4.3 +appVersion: v0.4.3 dependencies: - name: helmProjectOperator repository: file://./charts/helmProjectOperator From 104c3d3c3afa07995a63b2a20ceb7480b2ba07cc Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 12 Dec 2024 14:02:17 -0500 Subject: [PATCH 16/20] Add tag back to PromFed chart for rancher-charts image tooling --- packages/prometheus-federator/charts/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/prometheus-federator/charts/values.yaml b/packages/prometheus-federator/charts/values.yaml index 5615ccf8..f4495bc0 100755 --- a/packages/prometheus-federator/charts/values.yaml +++ b/packages/prometheus-federator/charts/values.yaml @@ -68,7 +68,7 @@ helmProjectOperator: image: registry: '' repository: rancher/prometheus-federator - tag: '' + tag: v0.4.3 pullPolicy: IfNotPresent # Additional arguments to be passed into the Prometheus Federator image From 9283a3d2897d291207aeda8592132c872acaae7b Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 12 Dec 2024 14:02:44 -0500 Subject: [PATCH 17/20] make charts --- .../prometheus-federator-0.4.5-rc.1.tgz | Bin 21257 -> 21261 bytes .../0.4.5-rc.1/Chart.yaml | 2 +- .../0.4.5-rc.1/values.yaml | 2 +- index.yaml | 6 +++--- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz b/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz index 082fd68700abcbdd9f0ad59065766d87a8acff2f..9bd210bac707a7d1e71961ef68ae4727c09d9482 100644 GIT binary patch delta 20120 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 Date: Thu, 12 Dec 2024 16:11:31 -0500 Subject: [PATCH 18/20] Refactor temporary fix to experimental module --- pkg/helm-project-operator/crd/crds.go | 47 ++++--------------- .../experemental/runtime.go | 46 ++++++++++++++++++ 2 files changed, 55 insertions(+), 38 deletions(-) create mode 100644 pkg/helm-project-operator/experemental/runtime.go diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 0d879148..994f3fc7 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -2,16 +2,14 @@ package crd import ( "context" - "errors" "fmt" + "github.com/rancher/prometheus-federator/pkg/helm-project-operator/experemental" "io" "os" "path/filepath" "strings" "sync" - "github.com/rancher/wrangler/pkg/clients" - "github.com/rancher/wrangler/pkg/name" "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" apimachineerrors "k8s.io/apimachinery/pkg/api/errors" @@ -155,6 +153,8 @@ func objects(v1beta1 bool, crdDefs []crd.CRD) (crds []runtime.Object, err error) // List returns the list of CRDs and dependent CRDs for this operator func List() ([]crd.CRD, []crd.CRD, []crd.CRD) { + // TODO: The underlying crd.CRD is deprectated and will eventually be removed + // We simply do what `helm-controller` does, so we should work with them to update in tandem. crds := []crd.CRD{ newCRD( "ProjectHelmChart.helm.cattle.io/v1alpha1", @@ -250,54 +250,25 @@ func filterMissingCRDs(apiExtClient *clientset.Clientset, expectedCRDs *[]crd.CR return nil } +// shouldManageHelmControllerCRDs determines if the controller should manage the CRDs for Helm Controller. func shouldManageHelmControllerCRDs(cfg *rest.Config) bool { if os.Getenv("DETECT_K3S_RKE2") != "true" { logrus.Debug("k3s/rke2 detection feature is disabled; `helm-controller` CRDs will be managed") return true } - k8sRuntimeType, err := identifyKubernetesRuntimeType(cfg) + // TODO: In the future, this should not rely on detecting k8s runtime type + // The root question is "what component 'owns' this CRD" - and therefore updates it. + // Instead we should rely on verifiable details directly on the CRDs in question. + k8sRuntimeType, err := experemental.IdentifyKubernetesRuntimeType(cfg) if err != nil { logrus.Error(err) } onK3sRke2 := k8sRuntimeType == "k3s" || k8sRuntimeType == "rke2" if onK3sRke2 { - logrus.Debug("the cluster is running on k3s (or rke2), `helm-controller` CRDs will not be managed") + logrus.Debug("the cluster is running on k3s (or rke2), `helm-controller` CRDs will not be managed by `prometheus-federator`") } return !onK3sRke2 } - -func identifyKubernetesRuntimeType(clientConfig *rest.Config) (string, error) { - client, err := clients.NewFromConfig(clientConfig, nil) - if err != nil { - return "", err - } - - nodes, err := client.Core.Node().List(metav1.ListOptions{}) - if err != nil { - logrus.Fatalf("Failed to list nodes: %v", err) - } - instanceTypes := make(map[string]int) - for _, node := range nodes.Items { - instanceType, exists := node.Labels["node.kubernetes.io/instance-type"] - if exists { - instanceTypes[instanceType]++ - } else { - logrus.Debugf("Cannot find `node.kubernetes.io/instance-type` label on node `%s`", node.Name) - } - } - - if len(instanceTypes) == 0 { - return "", errors.New("cannot identify k8s runtime type; no nodes in cluster have expected label") - } - - var k8sRuntimeType string - for instanceType := range instanceTypes { - k8sRuntimeType = instanceType - break - } - - return k8sRuntimeType, nil -} diff --git a/pkg/helm-project-operator/experemental/runtime.go b/pkg/helm-project-operator/experemental/runtime.go new file mode 100644 index 00000000..a20cf6a4 --- /dev/null +++ b/pkg/helm-project-operator/experemental/runtime.go @@ -0,0 +1,46 @@ +package experemental + +import ( + "errors" + + "github.com/rancher/wrangler/pkg/clients" + "github.com/sirupsen/logrus" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/rest" +) + +// IdentifyKubernetesRuntimeType provides the k8s runtime used on nodes in the cluster. +// Deprecated: This feature is a stop gap not expected to be maintained long-term. +// A more robust solution should be implemented, either in the `helm-controller` or in `wrangler` CRD frameworks. +func IdentifyKubernetesRuntimeType(clientConfig *rest.Config) (string, error) { + client, err := clients.NewFromConfig(clientConfig, nil) + if err != nil { + return "", err + } + + nodes, err := client.Core.Node().List(metav1.ListOptions{}) + if err != nil { + logrus.Fatalf("Failed to list nodes: %v", err) + } + instanceTypes := make(map[string]int) + for _, node := range nodes.Items { + instanceType, exists := node.Labels["node.kubernetes.io/instance-type"] + if exists { + instanceTypes[instanceType]++ + } else { + logrus.Debugf("Cannot find `node.kubernetes.io/instance-type` label on node `%s`", node.Name) + } + } + + if len(instanceTypes) == 0 { + return "", errors.New("cannot identify k8s runtime type; no nodes in cluster have expected label") + } + + var k8sRuntimeType string + for instanceType := range instanceTypes { + k8sRuntimeType = instanceType + break + } + + return k8sRuntimeType, nil +} From ba0547600c308e66a5fe1f3a15b45f168907dd47 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 12 Dec 2024 16:11:58 -0500 Subject: [PATCH 19/20] Improve values comments on experimental setting --- packages/prometheus-federator/charts/values.yaml | 6 +++++- pkg/helm-project-operator/crd/crds.go | 7 +++---- .../{experemental => experimental}/runtime.go | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) rename pkg/helm-project-operator/{experemental => experimental}/runtime.go (98%) diff --git a/packages/prometheus-federator/charts/values.yaml b/packages/prometheus-federator/charts/values.yaml index f4495bc0..d8abce42 100755 --- a/packages/prometheus-federator/charts/values.yaml +++ b/packages/prometheus-federator/charts/values.yaml @@ -40,9 +40,13 @@ helmProjectOperator: # Configure how the operator will manage the CustomResourceDefinitions (CRDs) it needs to function. crdManagement: # Enable or disable automatic updates of CRDs during startup. - # When true, all CRDs will be udpated to the version the operator provides. + # When true, all CRDs will be updated to the version the operator provides. # When false, only missing CRDs will be installed, and existing ones will not be updated. update: true + + # !! EXPERIMENTAL OPTION !! - Use this feature with caution and careful consideration. + # This feature is a stopgap solution and is already expected to be removed in the future. + # # Specify whether the operator should detect K3s and RKE2 clusters and exclude `helm-controller` CRDs from management. # When true, `helm-controller` CRDs will not be managed by the operator in these environments, as K3s/RKE2 handle them internally. # When false, the operator will manage `helm-controller` CRDs regardless of the runtime environment. diff --git a/pkg/helm-project-operator/crd/crds.go b/pkg/helm-project-operator/crd/crds.go index 994f3fc7..1f3e2541 100644 --- a/pkg/helm-project-operator/crd/crds.go +++ b/pkg/helm-project-operator/crd/crds.go @@ -3,7 +3,6 @@ package crd import ( "context" "fmt" - "github.com/rancher/prometheus-federator/pkg/helm-project-operator/experemental" "io" "os" "path/filepath" @@ -15,10 +14,10 @@ import ( apimachineerrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "github.com/rancher/prometheus-federator/pkg/helm-project-operator/apis/helm.cattle.io/v1alpha1" - helmcontrollercrd "github.com/k3s-io/helm-controller/pkg/crd" helmlockercrd "github.com/rancher/prometheus-federator/pkg/helm-locker/crd" + "github.com/rancher/prometheus-federator/pkg/helm-project-operator/apis/helm.cattle.io/v1alpha1" + "github.com/rancher/prometheus-federator/pkg/helm-project-operator/experimental" "github.com/rancher/wrangler/pkg/crd" "github.com/rancher/wrangler/pkg/yaml" "github.com/sirupsen/logrus" @@ -260,7 +259,7 @@ func shouldManageHelmControllerCRDs(cfg *rest.Config) bool { // TODO: In the future, this should not rely on detecting k8s runtime type // The root question is "what component 'owns' this CRD" - and therefore updates it. // Instead we should rely on verifiable details directly on the CRDs in question. - k8sRuntimeType, err := experemental.IdentifyKubernetesRuntimeType(cfg) + k8sRuntimeType, err := experimental.IdentifyKubernetesRuntimeType(cfg) if err != nil { logrus.Error(err) } diff --git a/pkg/helm-project-operator/experemental/runtime.go b/pkg/helm-project-operator/experimental/runtime.go similarity index 98% rename from pkg/helm-project-operator/experemental/runtime.go rename to pkg/helm-project-operator/experimental/runtime.go index a20cf6a4..f1ad009f 100644 --- a/pkg/helm-project-operator/experemental/runtime.go +++ b/pkg/helm-project-operator/experimental/runtime.go @@ -1,4 +1,4 @@ -package experemental +package experimental import ( "errors" From 2cff4603ea2682cefae8d3dac12f9d8da4879b54 Mon Sep 17 00:00:00 2001 From: Dan Pock Date: Thu, 12 Dec 2024 16:12:12 -0500 Subject: [PATCH 20/20] make charts --- .../prometheus-federator-0.4.5-rc.1.tgz | Bin 21261 -> 21351 bytes .../0.4.5-rc.1/values.yaml | 6 +++++- index.yaml | 4 ++-- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz b/assets/prometheus-federator/prometheus-federator-0.4.5-rc.1.tgz index 9bd210bac707a7d1e71961ef68ae4727c09d9482..9f513b3b77cbf554825c4c33f7747cd0b56b8cf9 100644 GIT binary patch delta 20868 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 R0RV7)-6KN$9DpVO{13^#so($r delta 20851 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