From edec3853e3ae2cf85b348c07207ec43b8bebd6aa Mon Sep 17 00:00:00 2001 From: zerospiel Date: Wed, 11 Dec 2024 17:26:55 +0100 Subject: [PATCH] Backup implementation part 1 * new component labels on most components * wide range of new RBAC permission for the backup ctrl * velero stack reconciliation * adjusted backup user-facing roles * added backup ctrl configuration in the hmc chart * removed schema defaults in favor of mutation --- .golangci.yml | 2 +- api/v1alpha1/backup_types.go | 10 +- api/v1alpha1/management_types.go | 4 - api/v1alpha1/zz_generated.deepcopy.go | 2 +- cmd/main.go | 14 +- config/dev/aws-credentials.yaml | 6 + config/dev/aws-managedcluster.yaml | 2 + config/dev/azure-credentials.yaml | 5 + config/dev/azure-managedcluster.yaml | 2 + config/dev/eks-managedcluster.yaml | 2 + config/dev/vsphere-credentials.yaml | 6 + config/dev/vsphere-managedcluster.yaml | 2 + go.mod | 28 +- go.sum | 89 ++-- hack/templates.sh | 2 + .../controller/accessmanagement_controller.go | 6 + internal/controller/backup/install.go | 445 ++++++++++++++++++ internal/controller/backup/oneshot.go | 30 ++ internal/controller/backup/schedule.go | 30 ++ internal/controller/backup/type.go | 62 +++ internal/controller/backup_controller.go | 146 +++++- internal/controller/backup_controller_test.go | 20 +- internal/controller/credential_controller.go | 6 + .../controller/managedcluster_controller.go | 6 + internal/controller/management_controller.go | 5 + .../multiclusterservice_controller.go | 8 + .../multiclusterservice_controller_test.go | 7 +- internal/controller/release_controller.go | 6 + internal/controller/template_controller.go | 23 + .../controller/template_controller_test.go | 1 + .../controller/templatechain_controller.go | 5 + .../templatechain_controller_test.go | 10 + internal/utils/label.go | 53 +++ internal/utils/label_test.go | 92 ++++ internal/webhook/management_webhook.go | 11 +- internal/webhook/management_webhook_test.go | 54 +++ .../provider/hmc-templates/files/release.yaml | 2 + .../files/templates/aws-eks-0-0-2.yaml | 2 + .../files/templates/aws-hosted-cp-0-0-3.yaml | 2 + .../templates/aws-standalone-cp-0-0-4.yaml | 2 + .../templates/azure-hosted-cp-0-0-3.yaml | 2 + .../templates/azure-standalone-cp-0-0-4.yaml | 2 + .../files/templates/cert-manager-1-16-2.yaml | 2 + .../templates/cluster-api-provider-aws.yaml | 2 + .../templates/cluster-api-provider-azure.yaml | 2 + .../cluster-api-provider-vsphere.yaml | 2 + .../files/templates/cluster-api.yaml | 2 + .../files/templates/dex-0-19-1.yaml | 2 + .../templates/external-secrets-0-11-0.yaml | 2 + .../hmc-templates/files/templates/hmc.yaml | 2 + .../files/templates/ingress-nginx-4-11-0.yaml | 2 + .../files/templates/ingress-nginx-4-11-3.yaml | 2 + .../files/templates/k0smotron.yaml | 2 + .../files/templates/kyverno-3-2-6.yaml | 2 + .../files/templates/projectsveltos.yaml | 2 + .../files/templates/velero-8-1-0.yaml | 2 + .../templates/vsphere-hosted-cp-0-0-3.yaml | 2 + .../vsphere-standalone-cp-0-0-3.yaml | 2 + templates/provider/hmc/templates/_helpers.tpl | 9 + .../crds/hmc.mirantis.com_managements.yaml | 2 - .../provider/hmc/templates/deployment.yaml | 8 + .../hmc/templates/rbac/controller/roles.yaml | 48 +- .../rbac/user-facing/backup-editor.yaml | 5 + .../rbac/user-facing/backup-viewer.yaml | 5 + templates/provider/hmc/values.yaml | 8 + test/objects/management/management.go | 6 + 66 files changed, 1249 insertions(+), 88 deletions(-) create mode 100644 internal/controller/backup/install.go create mode 100644 internal/controller/backup/oneshot.go create mode 100644 internal/controller/backup/schedule.go create mode 100644 internal/controller/backup/type.go create mode 100644 internal/utils/label.go create mode 100644 internal/utils/label_test.go diff --git a/.golangci.yml b/.golangci.yml index ee80ea33b..6221b297d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -24,7 +24,7 @@ issues: path: (.*_test\.go|test\/|api\/?.*\/.*_types\.go) # ignore tests, and k8s API-specific files linters: - govet - - text: "max-public-structs: you have exceeded the maximum number of public struct declarations" + - text: "max-public-structs: you have exceeded the maximum number.*of public struct declarations" linters: - revive path: api/ # the api/ pkgs have lots of structs diff --git a/api/v1alpha1/backup_types.go b/api/v1alpha1/backup_types.go index 71dc17fc5..561a6c273 100644 --- a/api/v1alpha1/backup_types.go +++ b/api/v1alpha1/backup_types.go @@ -15,11 +15,19 @@ package v1alpha1 import ( - velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov1 "github.com/zerospiel/velero/pkg/apis/velero/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +const ( + // Name to label most of the HMC-related components. + // Mostly utilized by the backup feature. + GenericComponentLabelName = "hmc.mirantis.com/component" + // Component label value for the HMC-related components. + GenericComponentLabelValueHMC = "hmc" +) + // BackupSpec defines the desired state of Backup type BackupSpec struct { // Oneshot indicates whether the Backup should not be scheduled diff --git a/api/v1alpha1/management_types.go b/api/v1alpha1/management_types.go index 542a0def5..6a0c15598 100644 --- a/api/v1alpha1/management_types.go +++ b/api/v1alpha1/management_types.go @@ -57,14 +57,10 @@ type Core struct { // ManagementBackup enables a feature to backup HMC objects into a cloud. type ManagementBackup struct { - // +kubebuilder:default="0 */6 * * *" - // Schedule is a Cron expression defining when to run the scheduled Backup. // Default value is to backup every 6 hours. Schedule string `json:"schedule,omitempty"` - // +kubebuilder:default=false - // Flag to indicate whether the backup feature is enabled. // If set to true, [Velero] platform will be installed. // If set to false, creation or modification of Backups/Restores will be blocked. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 059d60379..51cc27a10 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -21,7 +21,7 @@ package v1alpha1 import ( "github.com/fluxcd/helm-controller/api/v2" apiv1 "github.com/fluxcd/source-controller/api/v1" - velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + velerov1 "github.com/zerospiel/velero/pkg/apis/velero/v1" corev1 "k8s.io/api/core/v1" apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/cmd/main.go b/cmd/main.go index 35c8e2c2a..cc614fc04 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -22,6 +22,10 @@ import ( hcv2 "github.com/fluxcd/helm-controller/api/v2" sourcev1 "github.com/fluxcd/source-controller/api/v1" sveltosv1beta1 "github.com/projectsveltos/addon-controller/api/v1beta1" + velerov1api "github.com/zerospiel/velero/pkg/apis/velero/v1" + velerov2alpha1api "github.com/zerospiel/velero/pkg/apis/velero/v2alpha1" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apiextv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/client-go/dynamic" @@ -51,6 +55,15 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + // velero deps + utilruntime.Must(velerov1api.AddToScheme(scheme)) + utilruntime.Must(velerov2alpha1api.AddToScheme(scheme)) + utilruntime.Must(apiextv1.AddToScheme(scheme)) + utilruntime.Must(apiextv1beta1.AddToScheme(scheme)) + // WARN: if snapshot is to be used, then the following resources should also be added to the scheme + // snapshotv1api.AddToScheme(scheme) // snapshotv1api "github.com/kubernetes-csi/external-snapshotter/client/v7/apis/volumesnapshot/v1" + // velero deps + utilruntime.Must(hmcmirantiscomv1alpha1.AddToScheme(scheme)) utilruntime.Must(sourcev1.AddToScheme(scheme)) utilruntime.Must(hcv2.AddToScheme(scheme)) @@ -305,7 +318,6 @@ func main() { // TODO (zerospiel): disabled until the #605 // if err = (&controller.BackupReconciler{ // Client: mgr.GetClient(), - // Scheme: mgr.GetScheme(), // }).SetupWithManager(mgr); err != nil { // setupLog.Error(err, "unable to create controller", "controller", "Backup") // os.Exit(1) diff --git a/config/dev/aws-credentials.yaml b/config/dev/aws-credentials.yaml index c85d6c8bc..772c94117 100644 --- a/config/dev/aws-credentials.yaml +++ b/config/dev/aws-credentials.yaml @@ -4,6 +4,8 @@ kind: AWSClusterStaticIdentity metadata: name: aws-cluster-identity namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: secretRef: aws-cluster-identity-secret allowedNamespaces: @@ -15,6 +17,8 @@ kind: Secret metadata: name: aws-cluster-identity-secret namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc type: Opaque stringData: AccessKeyID: ${AWS_ACCESS_KEY_ID} @@ -26,6 +30,8 @@ kind: Credential metadata: name: aws-cluster-identity-cred namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: description: AWS credentials identityRef: diff --git a/config/dev/aws-managedcluster.yaml b/config/dev/aws-managedcluster.yaml index e0eaa9502..bc99c2ecb 100644 --- a/config/dev/aws-managedcluster.yaml +++ b/config/dev/aws-managedcluster.yaml @@ -3,6 +3,8 @@ kind: ManagedCluster metadata: name: aws-dev namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: template: aws-standalone-cp-0-0-4 credential: aws-cluster-identity-cred diff --git a/config/dev/azure-credentials.yaml b/config/dev/azure-credentials.yaml index e2416dc41..e3c72f4e4 100644 --- a/config/dev/azure-credentials.yaml +++ b/config/dev/azure-credentials.yaml @@ -4,6 +4,7 @@ kind: AzureClusterIdentity metadata: labels: clusterctl.cluster.x-k8s.io/move-hierarchy: "true" + hmc.mirantis.com/component: hmc name: azure-cluster-identity namespace: ${NAMESPACE} spec: @@ -20,6 +21,8 @@ kind: Secret metadata: name: azure-cluster-identity-secret namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc stringData: clientSecret: "${AZURE_CLIENT_SECRET}" type: Opaque @@ -29,6 +32,8 @@ kind: Credential metadata: name: azure-cluster-identity-cred namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: description: Azure credentials identityRef: diff --git a/config/dev/azure-managedcluster.yaml b/config/dev/azure-managedcluster.yaml index 65b8aad11..0b95ccea3 100644 --- a/config/dev/azure-managedcluster.yaml +++ b/config/dev/azure-managedcluster.yaml @@ -3,6 +3,8 @@ kind: ManagedCluster metadata: name: azure-dev namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: template: azure-standalone-cp-0-0-4 credential: azure-cluster-identity-cred diff --git a/config/dev/eks-managedcluster.yaml b/config/dev/eks-managedcluster.yaml index d17cc48d9..b88f4b723 100644 --- a/config/dev/eks-managedcluster.yaml +++ b/config/dev/eks-managedcluster.yaml @@ -3,6 +3,8 @@ kind: ManagedCluster metadata: name: eks-dev namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: template: aws-eks-0-0-2 credential: "aws-cluster-identity-cred" diff --git a/config/dev/vsphere-credentials.yaml b/config/dev/vsphere-credentials.yaml index 98f065d16..1653ea940 100644 --- a/config/dev/vsphere-credentials.yaml +++ b/config/dev/vsphere-credentials.yaml @@ -4,6 +4,8 @@ kind: VSphereClusterIdentity metadata: name: vsphere-cluster-identity namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: secretName: vsphere-cluster-identity-secret allowedNamespaces: @@ -15,6 +17,8 @@ kind: Secret metadata: name: vsphere-cluster-identity-secret namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc stringData: username: ${VSPHERE_USER} password: ${VSPHERE_PASSWORD} @@ -24,6 +28,8 @@ kind: Credential metadata: name: vsphere-cluster-identity-cred namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: description: vSphere credentials identityRef: diff --git a/config/dev/vsphere-managedcluster.yaml b/config/dev/vsphere-managedcluster.yaml index 48ceae711..676d80fe6 100644 --- a/config/dev/vsphere-managedcluster.yaml +++ b/config/dev/vsphere-managedcluster.yaml @@ -3,6 +3,8 @@ kind: ManagedCluster metadata: name: vsphere-dev namespace: ${NAMESPACE} + labels: + hmc.mirantis.com/component: hmc spec: template: vsphere-standalone-cp-0-0-3 credential: vsphere-cluster-identity-cred diff --git a/go.mod b/go.mod index b96923d15..cafe3d5d3 100644 --- a/go.mod +++ b/go.mod @@ -19,7 +19,7 @@ require ( github.com/projectsveltos/libsveltos v0.44.0 github.com/segmentio/analytics-go v3.1.0+incompatible github.com/stretchr/testify v1.10.0 - github.com/vmware-tanzu/velero v1.15.0 + github.com/zerospiel/velero v0.0.0-20241213181215-1eaa894d12b8 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.16.4 k8s.io/api v0.31.3 @@ -38,7 +38,7 @@ require ( require ( dario.cat/mergo v1.0.1 // indirect github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 // indirect github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect @@ -108,7 +108,10 @@ require ( github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect github.com/hashicorp/errwrap v1.1.0 // indirect github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.6.0 // indirect + github.com/hashicorp/yamux v0.1.1 // indirect github.com/hexops/gotextdiff v1.0.3 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/imdario/mergo v0.3.16 // indirect @@ -116,8 +119,9 @@ require ( github.com/jmoiron/sqlx v1.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.10 // indirect + github.com/klauspost/compress v1.17.11 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 // indirect github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect github.com/lib/pq v1.10.9 // indirect @@ -127,6 +131,7 @@ require ( github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.0.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/moby/locker v1.0.1 // indirect @@ -137,6 +142,7 @@ require ( github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect + github.com/oklog/run v1.0.0 // indirect github.com/opencontainers/go-digest/blake3 v0.0.0-20240426182413-22b78e47854a // indirect github.com/opencontainers/image-spec v1.1.0 // indirect github.com/peterbourgon/diskv v2.0.1+incompatible // indirect @@ -144,7 +150,7 @@ require ( github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_golang v1.20.5 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.60.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/rubenv/sql-migrate v1.7.0 // indirect @@ -165,14 +171,14 @@ require ( github.com/yuin/gopher-lua v1.1.1 // indirect github.com/zeebo/blake3 v0.2.4 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect - go.opentelemetry.io/otel v1.30.0 // indirect - go.opentelemetry.io/otel/metric v1.30.0 // indirect - go.opentelemetry.io/otel/trace v1.30.0 // indirect + go.opentelemetry.io/otel v1.32.0 // indirect + go.opentelemetry.io/otel/metric v1.32.0 // indirect + go.opentelemetry.io/otel/trace v1.32.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20241004190924-225e2abe05e6 // indirect - golang.org/x/mod v0.21.0 // indirect + golang.org/x/mod v0.22.0 // indirect golang.org/x/net v0.32.0 // indirect golang.org/x/oauth2 v0.24.0 // indirect golang.org/x/sync v0.10.0 // indirect @@ -182,9 +188,9 @@ require ( golang.org/x/time v0.8.0 // indirect golang.org/x/tools v0.26.0 // indirect gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.35.1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 // indirect + google.golang.org/grpc v1.68.0 // indirect + google.golang.org/protobuf v1.35.2 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect diff --git a/go.sum b/go.sum index 9a1e3f832..d26afc1e8 100644 --- a/go.sum +++ b/go.sum @@ -5,10 +5,10 @@ filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4 github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/azure-sdk-for-go v68.0.0+incompatible h1:fcYLmCpyNYRnvJbPerq7U0hS+6+I79yEDJBqVNcqUzU= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0 h1:nyQWyZvwGTvunIMxi1Y9uXkcyr+I7TeNrr/foo4Kpk8= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.14.0/go.mod h1:l38EPgmsp71HHLq9j7De57JcKOWPyhrsW1Awm1JS6K0= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0 h1:tfLQ34V6F7tVSwoTf/4lH5sE0o6eCJuNDTmH09nDpbc= -github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.7.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0 h1:JZg6HRh6W6U4OLl6lk7BZ7BLisIzM9dG1R50zUk9C/M= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.16.0/go.mod h1:YL1xnZ6QejvQHWJrX/AvhFl4WW4rqHVoKspWNVwFk0M= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY= github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/compute/armcompute/v5 v5.7.0 h1:LkHbJbgF3YyvC53aqYGR+wWQDn2Rdp9AQdGndf9QvY4= @@ -65,6 +65,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cert-manager/cert-manager v1.16.2 h1:c9UU2E+8XWGruyvC/mdpc1wuLddtgmNr8foKdP7a8Jg= @@ -133,6 +135,7 @@ github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0 github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= @@ -252,6 +255,8 @@ github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB1 github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.0 h1:wgd4KxHJTVGGqWBq4QPB1i5BZNEx9BR8+OFmHDmTk8A= +github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/8pwz68aMkCI= github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= @@ -262,6 +267,8 @@ github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGN github.com/hashicorp/golang-lru/arc/v2 v2.0.5/go.mod h1:ny6zBSQZi2JxIeYcv7kt2sH2PXJtirBN7RDhRpxPkxU= github.com/hashicorp/golang-lru/v2 v2.0.5 h1:wW7h1TG88eUIJ2i69gaE3uNVtEPIagzhGvHgwfx2Vm4= github.com/hashicorp/golang-lru/v2 v2.0.5/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= @@ -282,6 +289,8 @@ github.com/jcmturner/gokrb5/v8 v8.4.4 h1:x1Sv4HaTpepFkXbt2IkL29DXRf8sOfZXo8eRKh6 github.com/jcmturner/gokrb5/v8 v8.4.4/go.mod h1:1btQEpgT6k+unzCwX1KdWMEwPPkkgBtP+F6aCACiMrs= github.com/jcmturner/rpc/v2 v2.0.3 h1:7FXXj8Ti1IaVFpSAziCZWNzbNuZmnvw/i6CqLNdWfZY= github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= @@ -293,8 +302,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.10 h1:oXAz+Vh0PMUvJczoi+flxpnBEPxoER1IaAnU/NMPtT0= -github.com/klauspost/compress v1.17.10/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= +github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc= +github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0= github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= @@ -306,6 +315,8 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0 h1:j3YK74myEQRxR/srciTpOrm221SAvz6J5OVWbyfeXFo= +github.com/kubernetes-csi/external-snapshotter/client/v7 v7.0.0/go.mod h1:FlyYFe32mPxKEPaRXKNxfX576d1AoCzstYDoOOnyMA4= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw= @@ -318,8 +329,12 @@ github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhn github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= @@ -332,6 +347,8 @@ github.com/miekg/dns v1.1.62 h1:cN8OuEF1/x5Rq6Np+h1epln8OiyPWV+lROx9LxcGgIQ= github.com/miekg/dns v1.1.62/go.mod h1:mvDlcItzm+br7MToIKqkglaGhlFMHJ9DTNNWONWXbNQ= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= @@ -360,6 +377,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f h1:y5//uYreIhSUg3J1GEMiLbxo1LJaP8RfCpH6pymGZus= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/onsi/ginkgo/v2 v2.22.0 h1:Yed107/8DjTr0lKCNt7Dn8yQ6ybuDRQoMGrNFKzMfHg= github.com/onsi/ginkgo/v2 v2.22.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo= github.com/onsi/gomega v1.36.1 h1:bJDPBO7ibjxcbHMgSCoo4Yj18UWbKDlLwX1x9sybDcw= @@ -399,8 +418,8 @@ github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.60.0 h1:+V9PAREWNvJMAuJ1x1BaWl9dewMW4YrHZQbx0sJNllA= -github.com/prometheus/common v0.60.0/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= @@ -432,6 +451,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= @@ -453,12 +474,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/vmware-tanzu/velero v1.15.0 h1:+S/lNSDwQqlROGWfmNuZnnabopGmco978COIt3AP09c= -github.com/vmware-tanzu/velero v1.15.0/go.mod h1:28VhzPJRBo91GBRkgs4Ird0fx2vCpepBWmhF+5Pn/WQ= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= @@ -483,14 +503,16 @@ github.com/zeebo/blake3 v0.2.4 h1:KYQPkhpRtcqh0ssGYcKLG1JYvddkEA8QwCM/yBqhaZI= github.com/zeebo/blake3 v0.2.4/go.mod h1:7eeQ6d2iXWRGF6npfaxl2CU+xy2Fjo2gxeyZGCRUjcE= github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= +github.com/zerospiel/velero v0.0.0-20241213181215-1eaa894d12b8 h1:26qmmcPx/o0wAxJY/q6u9atuJ8joHf523SeRi+24u/o= +github.com/zerospiel/velero v0.0.0-20241213181215-1eaa894d12b8/go.mod h1:k7TYpNEgFVIXGHwtK26LnGefF9r62pSams2/pPjhq/E= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1 h1:ysCfPZB9AjUlMa1UHYup3c9dAOCMQX/6sxSfPBUoxHw= go.opentelemetry.io/contrib/exporters/autoexport v0.46.1/go.mod h1:ha0aiYm+DOPsLHjh0zoQ8W8sLT+LJ58J3j47lGpSLrU= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/aQ7AfKqdwp7ECpOK6vHqquXXuyTjIO8ZdmPs= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI= -go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts= -go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc= +go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U= +go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 h1:jd0+5t/YynESZqsSyPz+7PAFdEop0dlN0+PkyHYo8oI= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0/go.mod h1:U707O40ee1FpQGyhvqnzmCJm1Wh6OX6GGBVn0E6Uyyk= go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v0.44.0 h1:bflGWrfYyuulcdxf14V6n9+CoQcu5SAAdHmDPAJnlps= @@ -507,14 +529,14 @@ go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0 h1:dEZWPjVN22urgY go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v0.44.0/go.mod h1:sTt30Evb7hJB/gEk27qLb1+l9n4Tb8HvHkR0Wx3S6CU= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 h1:VhlEQAPp9R1ktYfrPk5SOryw1e9LDDTZCbIPFrho0ec= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0/go.mod h1:kB3ufRbfU+CQ4MlUcqtW8Z7YEOBeK2DJ6CmR5rYYF3E= -go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w= -go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ= -go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE= -go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg= -go.opentelemetry.io/otel/sdk/metric v1.27.0 h1:5uGNOlpXi+Hbo/DRoI31BSb1v+OGcpv2NemcCrOL8gI= -go.opentelemetry.io/otel/sdk/metric v1.27.0/go.mod h1:we7jJVrYN2kh3mVBlswtPU22K0SA+769l93J6bsyvqw= -go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc= -go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o= +go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M= +go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8= +go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4= +go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU= +go.opentelemetry.io/otel/sdk/metric v1.29.0 h1:K2CfmJohnRgvZ9UAj2/FhIf/okdWcNdBwe1m8xFXiSY= +go.opentelemetry.io/otel/sdk/metric v1.29.0/go.mod h1:6zZLdCl2fkauYoZIOn/soQIDSWFmNSRcICarHfuhNJQ= +go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM= +go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8= go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= @@ -539,8 +561,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0= -golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -573,10 +595,15 @@ golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -620,15 +647,15 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f h1:jTm13A2itBi3La6yTGqn8bVSrc3ZZ1r8ENHlIXBfnRA= -google.golang.org/genproto/googleapis/api v0.0.0-20240930140551-af27646dc61f/go.mod h1:CLGoBuH1VHxAUXVPP8FfPwPEVJB6lz3URE5mY2SuayE= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61 h1:N9BgCIAUvn/M+p4NJccWPWb3BWh88+zyL0ll9HgbEeM= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240924160255-9d4c2d233b61/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= -google.golang.org/grpc v1.67.1 h1:zWnc1Vrcno+lHZCOofnIMvycFcc0QRGIzm9dhnDX68E= -google.golang.org/grpc v1.67.1/go.mod h1:1gLDyUQU7CTLJI90u3nXZ9ekeghjeM7pTDZlqFNg2AA= -google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA= -google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +google.golang.org/genproto v0.0.0-20241104194629-dd2ea8efbc28 h1:KJjNNclfpIkVqrZlTWcgOOaVQ00LdBnoEaRfkUx760s= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 h1:M0KvPgPmDZHPlbRbaNU1APr28TvwvvdUPlSv7PUvy8g= +google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:dguCy7UOdZhTvLzDyt15+rOrawrpM4q7DD9dQ1P11P4= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28 h1:XVhgTWWV3kGQlwJHR3upFWZeTsei6Oks1apkZSeonIE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241104194629-dd2ea8efbc28/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= +google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= +google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= +google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/hack/templates.sh b/hack/templates.sh index ac73ed156..da1f2b6dd 100755 --- a/hack/templates.sh +++ b/hack/templates.sh @@ -45,6 +45,8 @@ metadata: name: $template_name annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/internal/controller/accessmanagement_controller.go b/internal/controller/accessmanagement_controller.go index 7f0c53e2d..b11629bc8 100644 --- a/internal/controller/accessmanagement_controller.go +++ b/internal/controller/accessmanagement_controller.go @@ -28,6 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" hmc "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/internal/utils" ) // AccessManagementReconciler reconciles an AccessManagement object @@ -52,6 +53,11 @@ func (r *AccessManagementReconciler) Reconcile(ctx context.Context, req ctrl.Req return ctrl.Result{}, err } + if err := utils.AddHMCComponentLabel(ctx, r.Client, accessMgmt); err != nil { + l.Error(err, "adding component label") + return ctrl.Result{}, err + } + defer func() { statusErr := "" if err != nil { diff --git a/internal/controller/backup/install.go b/internal/controller/backup/install.go new file mode 100644 index 000000000..1846455f9 --- /dev/null +++ b/internal/controller/backup/install.go @@ -0,0 +1,445 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package backup + +import ( + "context" + "fmt" + "io" + "time" + + velerov1api "github.com/zerospiel/velero/pkg/apis/velero/v1" + veleroclient "github.com/zerospiel/velero/pkg/client" + veleroinstall "github.com/zerospiel/velero/pkg/install" + "github.com/zerospiel/velero/pkg/uploader" + "github.com/zerospiel/velero/pkg/util/kube" + appsv1 "k8s.io/api/apps/v1" + corev1 "k8s.io/api/core/v1" + rbacv1 "k8s.io/api/rbac/v1" + apiextv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/rest" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" +) + +type Config struct { + kubeRestConfig *rest.Config + cl client.Client + + image string + systemNamespace string + features []string + + requeueAfter time.Duration +} + +const veleroName = "velero" + +type ConfigOpt func(*Config) + +func NewConfig(cl client.Client, kc *rest.Config, opts ...ConfigOpt) *Config { + c := newWithDefaults() + + for _, o := range opts { + o(c) + } + + c.cl = cl + c.kubeRestConfig = kc + + return c +} + +func WithRequeueAfter(d time.Duration) ConfigOpt { + return func(c *Config) { + if d == 0 { + return + } + c.requeueAfter = d + } +} + +func WithVeleroSystemNamespace(ns string) ConfigOpt { + return func(c *Config) { + if len(ns) == 0 { + return + } + c.systemNamespace = ns + } +} + +func WithVeleroImage(image string) ConfigOpt { + return func(c *Config) { + if len(image) == 0 { + return + } + c.image = image + } +} + +func WithFeatures(features ...string) ConfigOpt { + return func(c *Config) { + if len(features) == 0 { + return + } + c.features = features + } +} + +func newWithDefaults() *Config { + return &Config{ + requeueAfter: 5 * time.Second, + systemNamespace: veleroName, + image: fmt.Sprintf("%s/%s:%s", veleroName, veleroName, "v1.15.0"), // velero/velero:v1.15.0 + } +} + +// ReconcileVeleroInstallation reconciles installation of velero stack within a management cluster. +func (c *Config) ReconcileVeleroInstallation(ctx context.Context) (ctrl.Result, error) { + deployState, err := c.checkVeleroDeployIsInstalled(ctx) + if err != nil { + return ctrl.Result{}, fmt.Errorf("failed to determine if velero is installed: %w", err) + } + + if deployState.needInstallation { + ctrl.LoggerFrom(ctx).Info("Installing velero stack") + if err := c.installVelero(ctx); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to perform velero stack installation: %w", err) + } + + return ctrl.Result{}, nil + } + + if deployState.needRequeue || deployState.needInstallation { + return ctrl.Result{Requeue: true, RequeueAfter: c.requeueAfter}, nil // either the installation has happened or direct requeue is required + } + + return ctrl.Result{}, nil +} + +// installVelero installs velero stack with all the required components. +func (c *Config) installVelero(ctx context.Context) error { + saName, err := c.ensureVeleroRBAC(ctx) + if err != nil { + return fmt.Errorf("failed to ensure velero RBAC: %w", err) + } + + options := &veleroinstall.VeleroOptions{ + Namespace: c.systemNamespace, + Image: c.image, + Features: c.features, + + ServiceAccountName: saName, + NoDefaultBackupLocation: true, // no need (explicit BSL) + + DefaultRepoMaintenanceFrequency: time.Hour, // default + GarbageCollectionFrequency: time.Hour, // default + PodVolumeOperationTimeout: 4 * time.Hour, // default + UploaderType: uploader.KopiaType, // the only supported + + // TODO: skip null params? + ProviderName: "", // no need, provided through the explicit BSL object + Bucket: "", // no need, provided through the explicit BSL object + + Prefix: "", // no need when out-of-tree + PodAnnotations: nil, // no need, default comes from velero + PodLabels: nil, // no need, default comes from velero + ServiceAccountAnnotations: nil, // customizable through the config? + + VeleroPodResources: corev1.ResourceRequirements{}, // unbounded + NodeAgentPodResources: corev1.ResourceRequirements{}, // not used + PodResources: kube.PodResources{}, // maintenance job resources, unlimited ok + + SecretData: nil, // no need, provided through the explicit BSL object + UseNodeAgent: false, // no need + RestoreOnly: false, // no need + PrivilegedNodeAgent: false, // no need + UseVolumeSnapshots: false, // no need + BSLConfig: nil, // backupstoragelocation + VSLConfig: nil, // volumesnapshotlocation + Plugins: nil, // should be installed on-demand (BSL object) + CACertData: nil, // no need (explicit BSL) + DefaultVolumesToFsBackup: false, // no volume backups, no need + DefaultSnapshotMoveData: false, // no snapshots, no need + DisableInformerCache: false, // dangerous + ScheduleSkipImmediately: false, // might be useful, but easy to customize directly through the deploy + KeepLatestMaintenanceJobs: 0, // optional + BackupRepoConfigMap: "", // no need, backup config through a CM + RepoMaintenanceJobConfigMap: "", // no need, main job config through a CM + NodeAgentConfigMap: "", // no need, node-agent config through a CM + } + + resources := veleroinstall.AllResources(options) + + dc, err := dynamic.NewForConfig(c.kubeRestConfig) + if err != nil { + return fmt.Errorf("failed to construct dynamic client: %w", err) + } + + return veleroinstall.Install(veleroclient.NewDynamicFactory(dc), c.cl, resources, io.Discard) +} + +// ensureVeleroRBAC creates required RBAC objects for velero to be functional +// with the minimal required set of permissions. +// Returns the name of created ServiceAccount referenced by created bindings. +func (c *Config) ensureVeleroRBAC(ctx context.Context) (string, error) { + crbName, clusterRoleName, rbName, roleName, saName := veleroName, veleroName, veleroName, veleroName, veleroName + if c.systemNamespace != veleroName { + vns := veleroName + "-" + c.systemNamespace + crbName, clusterRoleName, saName = vns+"-clusterrolebinding", vns+"-clusterrole", crbName+"-sa" + rbName, roleName = vns+"-rolebinding", vns+"-role" + } + + systemNS := new(corev1.Namespace) + if err := c.cl.Get(ctx, client.ObjectKey{Name: c.systemNamespace}, systemNS); apierrors.IsNotFound(err) { + systemNS.Name = c.systemNamespace + if err := c.cl.Create(ctx, systemNS); err != nil { + return "", fmt.Errorf("failed to create %s namespace for velero: %w", c.systemNamespace, err) + } + } + + sa := &corev1.ServiceAccount{ObjectMeta: metav1.ObjectMeta{Name: saName, Namespace: c.systemNamespace}} + if _, err := controllerutil.CreateOrUpdate(ctx, c.cl, sa, func() error { + sa.Labels = veleroinstall.Labels() + return nil + }); err != nil { + return "", fmt.Errorf("failed to create or update velero service account: %w", err) + } + + role := &rbacv1.Role{ObjectMeta: metav1.ObjectMeta{Name: roleName, Namespace: c.systemNamespace}} + if _, err := controllerutil.CreateOrUpdate(ctx, c.cl, role, func() error { + role.Labels = veleroinstall.Labels() + role.Rules = []rbacv1.PolicyRule{ + { + APIGroups: []string{velerov1api.SchemeGroupVersion.Group}, + Resources: []string{"*"}, + Verbs: []string{"*"}, + }, + { + APIGroups: []string{corev1.GroupName}, + Resources: []string{"secrets"}, + Verbs: []string{"create"}, + }, + } + return nil + }); err != nil { + return "", fmt.Errorf("failed to create or update velero role: %w", err) + } + + roleBinding := &rbacv1.RoleBinding{ObjectMeta: metav1.ObjectMeta{Name: rbName, Namespace: c.systemNamespace}} + if _, err := controllerutil.CreateOrUpdate(ctx, c.cl, roleBinding, func() error { + roleBinding.Labels = veleroinstall.Labels() + roleBinding.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "Role", + Name: roleName, + } + roleBinding.Subjects = []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: saName, + Namespace: c.systemNamespace, + }, + } + return nil + }); err != nil { + return "", fmt.Errorf("failed to create or update velero role binding: %w", err) + } + + cr := &rbacv1.ClusterRole{ObjectMeta: metav1.ObjectMeta{Name: clusterRoleName}} + if _, err := controllerutil.CreateOrUpdate(ctx, c.cl, cr, func() error { + cr.Labels = veleroinstall.Labels() + cr.Rules = []rbacv1.PolicyRule{ + { + APIGroups: []string{"*"}, + Resources: []string{"*"}, + Verbs: []string{"list", "get"}, + }, + { + APIGroups: []string{""}, + Resources: []string{"namespaces"}, + Verbs: []string{"list", "get"}, + }, + { + APIGroups: []string{apiextv1.GroupName}, + Resources: []string{"customresourcedefinitions"}, + Verbs: []string{"get"}, + }, + } + return nil + }); err != nil { + return "", fmt.Errorf("failed to create or update velero cluster role: %w", err) + } + + crb := &rbacv1.ClusterRoleBinding{ObjectMeta: metav1.ObjectMeta{Name: crbName}} + if _, err := controllerutil.CreateOrUpdate(ctx, c.cl, crb, func() error { + crb.Labels = veleroinstall.Labels() + crb.RoleRef = rbacv1.RoleRef{ + APIGroup: rbacv1.GroupName, + Kind: "ClusterRole", + Name: clusterRoleName, + } + crb.Subjects = []rbacv1.Subject{ + { + Kind: "ServiceAccount", + Name: saName, + Namespace: c.systemNamespace, + }, + } + return nil + }); err != nil { + return "", fmt.Errorf("failed to create or update velero cluster role binding: %w", err) + } + + return saName, nil +} + +type deployState struct { + needRequeue bool + needInstallation bool +} + +// checkVeleroDeployIsInstalled check whether the velero deploy is already installed: +// - the deployment is presented; +// - is in ready state; +// - the only container has the expected image and replicas. +// +// If image or replica count are not expected, the deploy will be patched regardingly. +// If the deploy has unexpected container name, the deploy will be deleted. +func (c *Config) checkVeleroDeployIsInstalled(ctx context.Context) (deployState, error) { + l := ctrl.LoggerFrom(ctx).WithName("velero-deploy-checker") + + l.Info("Checking if Velero deployment is already installed") + + veleroDeploy := new(appsv1.Deployment) + err := c.cl.Get(ctx, client.ObjectKey{Namespace: c.systemNamespace, Name: veleroName}, veleroDeploy) + if err != nil && !apierrors.IsNotFound(err) { + return deployState{}, fmt.Errorf("failed to get velero deploy: %w", err) + } + + if apierrors.IsNotFound(err) { + l.Info("Deployment is not found, considering the stack has not been (yet) installed") + return deployState{needInstallation: true}, nil + } + + if len(veleroDeploy.Spec.Template.Spec.Containers) == 0 || + veleroDeploy.Spec.Template.Spec.Containers[0].Name != veleroName { + l.Info("Deployment has unexpected container name, considering to reinstall the deployment again") + // the deploy is "corrupted", remove only it and then reinstall + if err := c.cl.Delete(ctx, veleroDeploy); err != nil { + return deployState{}, fmt.Errorf("failed to delete velero deploy: %w", err) + } + + removalCtx, cancel := context.WithCancel(ctx) + var checkErr error + checkFn := func(ctx context.Context) { + key := client.ObjectKeyFromObject(veleroDeploy) + ll := l.V(1).WithValues("velero_deploy", key.String()) + ll.Info("Checking if the deployment has been removed") + if checkErr = c.cl.Get(ctx, client.ObjectKeyFromObject(veleroDeploy), veleroDeploy); checkErr != nil { + if apierrors.IsNotFound(checkErr) { + ll.Info("Removed successfully") + checkErr = nil + } + cancel() + return + } + ll.Info("Not removed yet") + } + + wait.UntilWithContext(removalCtx, checkFn, time.Millisecond*500) + if checkErr != nil { + return deployState{}, fmt.Errorf("failed to wait for velero deploy removal: %w", checkErr) + } + + return deployState{needInstallation: true}, nil + } + + isPatchRequered := false + // process 2 invariants beforehand + cont := veleroDeploy.Spec.Template.Spec.Containers[0] + if cont.Image != c.image { + l.Info("Deployment container has unexpected image", "current_image", cont.Image, "expected_image", c.image) + cont.Image = c.image + veleroDeploy.Spec.Template.Spec.Containers[0] = cont + isPatchRequered = true + } + + if veleroDeploy.Spec.Replicas == nil || *veleroDeploy.Spec.Replicas == 0 { + l.Info("Deployment is scaled to 0, scaling up to 1") + *veleroDeploy.Spec.Replicas = 1 + isPatchRequered = true + } + + if isPatchRequered { + l.Info("Patching the deployment") + if err := c.cl.Patch(ctx, veleroDeploy, client.Merge); err != nil { + return deployState{}, fmt.Errorf("failed to patch velero deploy: %w", err) + } + + l.Info("Need to requeue after the successful patch") + return deployState{needRequeue: true}, nil + } + + r := isDeploymentReady(veleroDeploy) // if no invariants then just check the readiness + if !r { + l.Info("Deployment is not ready yet, will requeue") + return deployState{needRequeue: true}, nil + } + + l.Info("Deployment is in the expected state") + return deployState{}, nil +} + +// https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/kubectl/pkg/polymorphichelpers/rollout_status.go#L76-L89 + +// isDeploymentReady checks if the given Deployment instance is ready. +func isDeploymentReady(d *appsv1.Deployment) bool { + if d.Generation > d.Status.ObservedGeneration { + return false + } + + const timedOutReason = "ProgressDeadlineExceeded" // avoid dependency + var cond *appsv1.DeploymentCondition + for _, c := range d.Status.Conditions { + if c.Type == appsv1.DeploymentProgressing { + cond = &c + break + } + } + + if cond != nil && cond.Reason == timedOutReason { + return false + } + + if d.Spec.Replicas != nil && d.Status.UpdatedReplicas < *d.Spec.Replicas { + return false + } + + if d.Status.Replicas > d.Status.UpdatedReplicas { + return false + } + + if d.Status.AvailableReplicas < d.Status.UpdatedReplicas { + return false + } + + return true +} diff --git a/internal/controller/backup/oneshot.go b/internal/controller/backup/oneshot.go new file mode 100644 index 000000000..c90d9b0ee --- /dev/null +++ b/internal/controller/backup/oneshot.go @@ -0,0 +1,30 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package backup + +import ( + "context" + + hmcv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" +) + +func (*Config) ReconcileBackup(ctx context.Context, backup *hmcv1alpha1.Backup) error { + if backup == nil { + return nil + } + + _ = ctx + return nil +} diff --git a/internal/controller/backup/schedule.go b/internal/controller/backup/schedule.go new file mode 100644 index 000000000..f45679c00 --- /dev/null +++ b/internal/controller/backup/schedule.go @@ -0,0 +1,30 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package backup + +import ( + "context" + + hmcv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" +) + +func (*Config) ReconcileScheduledBackup(ctx context.Context, schedule *hmcv1alpha1.Backup) error { + if schedule == nil { + return nil + } + _ = ctx + + return nil +} diff --git a/internal/controller/backup/type.go b/internal/controller/backup/type.go new file mode 100644 index 000000000..0422c0be1 --- /dev/null +++ b/internal/controller/backup/type.go @@ -0,0 +1,62 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package backup + +import ( + "context" + "fmt" + + velerov1api "github.com/zerospiel/velero/pkg/apis/velero/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + + hmcv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" +) + +type Typ uint + +const ( + TypeNone Typ = iota + TypeSchedule + TypeBackup +) + +func (c *Config) GetBackupType(ctx context.Context, instance *hmcv1alpha1.Backup, reqName string) (Typ, error) { + if instance.Status.Reference != nil { + gv := velerov1api.SchemeGroupVersion + switch instance.Status.Reference.GroupVersionKind() { + case gv.WithKind("Schedule"): + return TypeSchedule, nil + case gv.WithKind("Backup"): + return TypeSchedule, nil + default: + return TypeNone, fmt.Errorf("unexpected kind %s in the backup reference", instance.Status.Reference.Kind) + } + } + + mgmts := new(hmcv1alpha1.ManagementList) + if err := c.cl.List(ctx, mgmts, client.Limit(1)); err != nil { + return TypeNone, fmt.Errorf("failed to list Management: %w", err) + } + + if len(mgmts.Items) == 0 { // nothing to do in such case for both scheduled/non-scheduled backups + return TypeNone, nil + } + + if reqName == mgmts.Items[0].Name { // mgmt name == scheduled-backup + return TypeSchedule, nil + } + + return TypeBackup, nil +} diff --git a/internal/controller/backup_controller.go b/internal/controller/backup_controller.go index 3572b673a..82c252992 100644 --- a/internal/controller/backup_controller.go +++ b/internal/controller/backup_controller.go @@ -16,45 +16,151 @@ package controller import ( "context" + "fmt" + "os" + "strings" + "time" - "k8s.io/apimachinery/pkg/runtime" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/builder" "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/event" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/predicate" - hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" + hmcv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/internal/controller/backup" ) // BackupReconciler reconciles a Backup object type BackupReconciler struct { client.Client - Scheme *runtime.Scheme + + kc *rest.Config + + image string + systemNamespace string + features []string + + requeueAfter time.Duration } -// +kubebuilder:rbac:groups=hmc.mirantis.com.hmc.mirantis.com,resources=Backups,verbs=get;list;watch;create;update;patch;delete -// +kubebuilder:rbac:groups=hmc.mirantis.com.hmc.mirantis.com,resources=Backups/status,verbs=get;update;patch -// +kubebuilder:rbac:groups=hmc.mirantis.com.hmc.mirantis.com,resources=Backups/finalizers,verbs=update +func (r *BackupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + l := ctrl.LoggerFrom(ctx) -// Reconcile is part of the main kubernetes reconciliation loop which aims to -// move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the Backup object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// -// For more details, check Reconcile and its Result here: -// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.19.0/pkg/reconcile -func (*BackupReconciler) Reconcile(ctx context.Context, _ ctrl.Request) (ctrl.Result, error) { - _ = log.FromContext(ctx) + backupInstance := new(hmcv1alpha1.Backup) + err := r.Client.Get(ctx, req.NamespacedName, backupInstance) + if ierr := client.IgnoreNotFound(err); ierr != nil { + l.Error(ierr, "unable to fetch Backup") + return ctrl.Result{}, ierr + } + + bcfg := backup.NewConfig(r.Client, r.kc, + backup.WithFeatures(r.features...), + backup.WithRequeueAfter(r.requeueAfter), + backup.WithVeleroImage(r.image), + backup.WithVeleroSystemNamespace(r.systemNamespace), + ) + + if apierrors.IsNotFound(err) { + // if non-scheduled backup is not found(deleted), then just skip the error + // if scheduled backup is not found, then it either does not exist yet + // and we should create it, or it has been removed; + // if the latter is the case, we either should re-create it once again + // or do nothing if mgmt backup is disabled + mgmt := new(hmcv1alpha1.Management) + if err := r.Client.Get(ctx, req.NamespacedName, mgmt); err != nil { + l.Error(err, "unable to fetch Management") + return ctrl.Result{}, client.IgnoreNotFound(err) + } + + if !mgmt.Spec.Backup.Enabled { + l.Info("Management backup is disabled, nothing to do") + return ctrl.Result{}, nil + } + + l.Info("Reconciling velero stack") + installRes, err := bcfg.ReconcileVeleroInstallation(ctx) + if err != nil { + l.Error(err, "velero installation") + return ctrl.Result{}, err + } + if installRes.Requeue || installRes.RequeueAfter > 0 { + return installRes, nil + } - // TODO(user): your logic here + // required during creation + backupInstance.Name = req.Name + backupInstance.Namespace = req.Namespace + } + + btype, err := bcfg.GetBackupType(ctx, backupInstance, req.Name) + if err != nil { + l.Error(err, "failed to determine backup type") + return ctrl.Result{}, err + } + + switch btype { + case backup.TypeNone: + l.Info("There are nothing to reconcile, management does not exists") + // TODO: do we need to reconcile/delete/pause schedules in this case? + return ctrl.Result{}, nil + case backup.TypeBackup: + return ctrl.Result{}, bcfg.ReconcileBackup(ctx, backupInstance) + case backup.TypeSchedule: + return ctrl.Result{}, bcfg.ReconcileScheduledBackup(ctx, backupInstance) + } return ctrl.Result{}, nil } // SetupWithManager sets up the controller with the Manager. func (r *BackupReconciler) SetupWithManager(mgr ctrl.Manager) error { + r.kc = mgr.GetConfig() + + const reqDuration = "BACKUP_CTRL_REQUEUE_DURATION" + r.features = strings.Split(strings.ReplaceAll(os.Getenv("BACKUP_FEATURES"), ", ", ","), ",") + r.systemNamespace = os.Getenv("BACKUP_SYSTEM_NAMESPACE") + r.image = os.Getenv("BACKUP_BASIC_IMAGE") + d, err := time.ParseDuration(os.Getenv(reqDuration)) + if err != nil { + return fmt.Errorf("failed to parse env %s duration: %w", reqDuration, err) + } + r.requeueAfter = d + return ctrl.NewControllerManagedBy(mgr). - For(&hmcmirantiscomv1alpha1.Backup{}). + For(&hmcv1alpha1.Backup{}). + Watches(&hmcv1alpha1.Management{}, handler.EnqueueRequestsFromMapFunc(func(_ context.Context, o client.Object) []ctrl.Request { + return []ctrl.Request{{NamespacedName: client.ObjectKeyFromObject(o)}} + }), builder.WithPredicates( // watch mgmt.spec.backup to manage the (only) scheduled Backup + predicate.Funcs{ + GenericFunc: func(event.TypedGenericEvent[client.Object]) bool { return false }, + DeleteFunc: func(event.TypedDeleteEvent[client.Object]) bool { return false }, + CreateFunc: func(tce event.TypedCreateEvent[client.Object]) bool { + mgmt, ok := tce.Object.(*hmcv1alpha1.Management) + if !ok { + return false + } + + return mgmt.Spec.Backup.Enabled + }, + UpdateFunc: func(tue event.TypedUpdateEvent[client.Object]) bool { + oldMgmt, ok := tue.ObjectOld.(*hmcv1alpha1.Management) + if !ok { + return false + } + + newMgmt, ok := tue.ObjectNew.(*hmcv1alpha1.Management) + if !ok { + return false + } + + return (newMgmt.Spec.Backup.Enabled != oldMgmt.Spec.Backup.Enabled || + newMgmt.Spec.Backup.Schedule != oldMgmt.Spec.Backup.Schedule) + }, + }, + )). Complete(r) } diff --git a/internal/controller/backup_controller_test.go b/internal/controller/backup_controller_test.go index 5fc61af7f..caada7982 100644 --- a/internal/controller/backup_controller_test.go +++ b/internal/controller/backup_controller_test.go @@ -22,7 +22,6 @@ import ( "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "sigs.k8s.io/controller-runtime/pkg/reconcile" hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" ) @@ -35,7 +34,7 @@ var _ = Describe("Backup Controller", func() { typeNamespacedName := types.NamespacedName{ Name: resourceName, - Namespace: "default", // TODO(user):Modify as needed + Namespace: metav1.NamespaceAll, } backup := &hmcmirantiscomv1alpha1.Backup{} @@ -46,16 +45,14 @@ var _ = Describe("Backup Controller", func() { resource := &hmcmirantiscomv1alpha1.Backup{ ObjectMeta: metav1.ObjectMeta{ Name: resourceName, - Namespace: "default", + Namespace: metav1.NamespaceAll, }, - // TODO(user): Specify other spec details if needed. } Expect(k8sClient.Create(ctx, resource)).To(Succeed()) } }) AfterEach(func() { - // TODO(user): Cleanup logic after each test, like removing the resource instance. resource := &hmcmirantiscomv1alpha1.Backup{} err := k8sClient.Get(ctx, typeNamespacedName, resource) Expect(err).NotTo(HaveOccurred()) @@ -63,19 +60,18 @@ var _ = Describe("Backup Controller", func() { By("Cleanup the specific resource instance Backup") Expect(k8sClient.Delete(ctx, resource)).To(Succeed()) }) + It("should successfully reconcile the resource", func() { By("Reconciling the created resource") controllerReconciler := &BackupReconciler{ Client: k8sClient, - Scheme: k8sClient.Scheme(), } + _ = controllerReconciler - _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ - NamespacedName: typeNamespacedName, - }) - Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. + // _, err := controllerReconciler.Reconcile(ctx, reconcile.Request{ + // NamespacedName: typeNamespacedName, + // }) + // Expect(err).NotTo(HaveOccurred()) }) }) }) diff --git a/internal/controller/credential_controller.go b/internal/controller/credential_controller.go index 661d021f5..850b8f73c 100644 --- a/internal/controller/credential_controller.go +++ b/internal/controller/credential_controller.go @@ -28,6 +28,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" hmc "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/internal/utils" ) const defaultSyncPeriod = 15 * time.Minute @@ -46,6 +47,11 @@ func (r *CredentialReconciler) Reconcile(ctx context.Context, req ctrl.Request) return ctrl.Result{}, client.IgnoreNotFound(err) } + if err := utils.AddHMCComponentLabel(ctx, r.Client, cred); err != nil { + l.Error(err, "adding component label") + return ctrl.Result{}, err + } + defer func() { err = errors.Join(err, r.updateStatus(ctx, cred)) }() diff --git a/internal/controller/managedcluster_controller.go b/internal/controller/managedcluster_controller.go index c89fdb1cc..c5e1f73d3 100644 --- a/internal/controller/managedcluster_controller.go +++ b/internal/controller/managedcluster_controller.go @@ -52,6 +52,7 @@ import ( "github.com/Mirantis/hmc/internal/helm" "github.com/Mirantis/hmc/internal/sveltos" "github.com/Mirantis/hmc/internal/telemetry" + "github.com/Mirantis/hmc/internal/utils" "github.com/Mirantis/hmc/internal/utils/status" ) @@ -146,6 +147,11 @@ func (r *ManagedClusterReconciler) reconcileUpdate(ctx context.Context, mc *hmc. return ctrl.Result{}, nil } + if err := utils.AddHMCComponentLabel(ctx, r.Client, mc); err != nil { + l.Error(err, "adding component label") + return ctrl.Result{}, err + } + if len(mc.Status.Conditions) == 0 { mc.InitConditions() } diff --git a/internal/controller/management_controller.go b/internal/controller/management_controller.go index 1331ddf94..e5b2d5cb9 100644 --- a/internal/controller/management_controller.go +++ b/internal/controller/management_controller.go @@ -91,6 +91,11 @@ func (r *ManagementReconciler) Update(ctx context.Context, management *hmc.Manag return ctrl.Result{}, nil } + if err := utils.AddHMCComponentLabel(ctx, r.Client, management); err != nil { + l.Error(err, "adding component label") + return ctrl.Result{}, err + } + if err := r.cleanupRemovedComponents(ctx, management); err != nil { l.Error(err, "failed to cleanup removed components") return ctrl.Result{}, err diff --git a/internal/controller/multiclusterservice_controller.go b/internal/controller/multiclusterservice_controller.go index d2bd10fd9..e5da86fcf 100644 --- a/internal/controller/multiclusterservice_controller.go +++ b/internal/controller/multiclusterservice_controller.go @@ -37,6 +37,7 @@ import ( hmc "github.com/Mirantis/hmc/api/v1alpha1" "github.com/Mirantis/hmc/internal/sveltos" + "github.com/Mirantis/hmc/internal/utils" ) // MultiClusterServiceReconciler reconciles a MultiClusterService object @@ -70,6 +71,13 @@ func (r *MultiClusterServiceReconciler) Reconcile(ctx context.Context, req ctrl. } func (r *MultiClusterServiceReconciler) reconcileUpdate(ctx context.Context, mcs *hmc.MultiClusterService) (_ ctrl.Result, err error) { + if utils.AddLabel(mcs, hmc.GenericComponentLabelName, hmc.GenericComponentLabelValueHMC) { + if err := r.Update(ctx, mcs); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update labels: %w", err) + } + return ctrl.Result{Requeue: true}, nil // generation has not changed, need explicit requeue + } + // servicesErr is handled separately from err because we do not want // to set the condition of SveltosClusterProfileReady type to "False" // if there is an error while retrieving status for the services. diff --git a/internal/controller/multiclusterservice_controller_test.go b/internal/controller/multiclusterservice_controller_test.go index f3f7f9b79..bffe0b71c 100644 --- a/internal/controller/multiclusterservice_controller_test.go +++ b/internal/controller/multiclusterservice_controller_test.go @@ -136,7 +136,8 @@ var _ = Describe("MultiClusterService Controller", func() { Name: serviceTemplate1Name, Namespace: testSystemNamespace, Labels: map[string]string{ - hmc.HMCManagedLabelKey: "true", + hmc.HMCManagedLabelKey: "true", + hmc.GenericComponentLabelName: hmc.GenericComponentLabelValueHMC, }, }, Spec: hmc.ServiceTemplateSpec{ @@ -159,6 +160,7 @@ var _ = Describe("MultiClusterService Controller", func() { ObjectMeta: metav1.ObjectMeta{ Name: serviceTemplate2Name, Namespace: testSystemNamespace, + Labels: map[string]string{hmc.GenericComponentLabelName: hmc.GenericComponentLabelValueHMC}, }, Spec: hmc.ServiceTemplateSpec{ Helm: hmc.HelmSpec{ @@ -206,7 +208,8 @@ var _ = Describe("MultiClusterService Controller", func() { if err != nil && apierrors.IsNotFound(err) { multiClusterService = &hmc.MultiClusterService{ ObjectMeta: metav1.ObjectMeta{ - Name: multiClusterServiceName, + Name: multiClusterServiceName, + Labels: map[string]string{hmc.GenericComponentLabelName: hmc.GenericComponentLabelValueHMC}, Finalizers: []string{ // Reconcile attempts to add this finalizer and returns immediately // if successful. So adding this finalizer here manually in order diff --git a/internal/controller/release_controller.go b/internal/controller/release_controller.go index be19aa876..b1db3fc17 100644 --- a/internal/controller/release_controller.go +++ b/internal/controller/release_controller.go @@ -77,6 +77,12 @@ func (r *ReleaseReconciler) Reconcile(ctx context.Context, req ctrl.Request) (re l.Error(err, "failed to get Release") return ctrl.Result{}, err } + + if err := utils.AddHMCComponentLabel(ctx, r.Client, release); err != nil { + l.Error(err, "adding component label") + return ctrl.Result{}, err + } + defer func() { release.Status.ObservedGeneration = release.Generation for _, condition := range release.Status.Conditions { diff --git a/internal/controller/template_controller.go b/internal/controller/template_controller.go index a4738b42d..012a9e2e6 100644 --- a/internal/controller/template_controller.go +++ b/internal/controller/template_controller.go @@ -80,6 +80,13 @@ func (r *ClusterTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Requ return ctrl.Result{}, err } + if utils.AddLabel(clusterTemplate, hmc.GenericComponentLabelName, hmc.GenericComponentLabelValueHMC) { + if err := r.Update(ctx, clusterTemplate); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update labels: %w", err) + } + return ctrl.Result{Requeue: true}, nil // generation has not changed, need explicit requeue + } + result, err := r.ReconcileTemplate(ctx, clusterTemplate) if err != nil { l.Error(err, "failed to reconcile template") @@ -113,6 +120,14 @@ func (r *ServiceTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Requ l.Error(err, "Failed to get ServiceTemplate") return ctrl.Result{}, err } + + if utils.AddLabel(serviceTemplate, hmc.GenericComponentLabelName, hmc.GenericComponentLabelValueHMC) { + if err := r.Update(ctx, serviceTemplate); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update labels: %w", err) + } + return ctrl.Result{Requeue: true}, nil // generation has not changed, need explicit requeue + } + return r.ReconcileTemplate(ctx, serviceTemplate) } @@ -130,6 +145,14 @@ func (r *ProviderTemplateReconciler) Reconcile(ctx context.Context, req ctrl.Req l.Error(err, "Failed to get ProviderTemplate") return ctrl.Result{}, err } + + if utils.AddLabel(providerTemplate, hmc.GenericComponentLabelName, hmc.GenericComponentLabelValueHMC) { + if err := r.Update(ctx, providerTemplate); err != nil { + return ctrl.Result{}, fmt.Errorf("failed to update labels: %w", err) + } + return ctrl.Result{Requeue: true}, nil // generation has not changed, need explicit requeue + } + changed, err := r.setReleaseOwnership(ctx, providerTemplate) if err != nil { l.Error(err, "Failed to set OwnerReferences") diff --git a/internal/controller/template_controller_test.go b/internal/controller/template_controller_test.go index c44f842de..288d0eaa2 100644 --- a/internal/controller/template_controller_test.go +++ b/internal/controller/template_controller_test.go @@ -218,6 +218,7 @@ var _ = Describe("Template Controller", func() { ObjectMeta: metav1.ObjectMeta{ Name: clusterTemplateName, Namespace: metav1.NamespaceDefault, + Labels: map[string]string{hmcmirantiscomv1alpha1.GenericComponentLabelName: hmcmirantiscomv1alpha1.GenericComponentLabelValueHMC}, }, Spec: hmcmirantiscomv1alpha1.ClusterTemplateSpec{ Helm: helmSpec, diff --git a/internal/controller/templatechain_controller.go b/internal/controller/templatechain_controller.go index 5d50207da..4a88aaedb 100644 --- a/internal/controller/templatechain_controller.go +++ b/internal/controller/templatechain_controller.go @@ -90,6 +90,11 @@ func (r *ServiceTemplateChainReconciler) Reconcile(ctx context.Context, req ctrl func (r *TemplateChainReconciler) ReconcileTemplateChain(ctx context.Context, templateChain templateChain) (ctrl.Result, error) { l := ctrl.LoggerFrom(ctx) + if err := utils.AddHMCComponentLabel(ctx, r.Client, templateChain); err != nil { + l.Error(err, "adding component label") + return ctrl.Result{}, err + } + if templateChain.GetNamespace() == r.SystemNamespace { return ctrl.Result{}, nil } diff --git a/internal/controller/templatechain_controller_test.go b/internal/controller/templatechain_controller_test.go index 33ab0ae2d..35f1db86d 100644 --- a/internal/controller/templatechain_controller_test.go +++ b/internal/controller/templatechain_controller_test.go @@ -170,6 +170,7 @@ var _ = Describe("Template Chain Controller", func() { ObjectMeta: metav1.ObjectMeta{ Name: chain.Name, Namespace: chain.Namespace, + Labels: map[string]string{hmcmirantiscomv1alpha1.GenericComponentLabelName: hmcmirantiscomv1alpha1.GenericComponentLabelValueHMC}, }, Spec: hmcmirantiscomv1alpha1.TemplateChainSpec{SupportedTemplates: supportedClusterTemplates[chain.Name]}, } @@ -185,6 +186,7 @@ var _ = Describe("Template Chain Controller", func() { ObjectMeta: metav1.ObjectMeta{ Name: chain.Name, Namespace: chain.Namespace, + Labels: map[string]string{hmcmirantiscomv1alpha1.GenericComponentLabelName: hmcmirantiscomv1alpha1.GenericComponentLabelValueHMC}, }, Spec: hmcmirantiscomv1alpha1.TemplateChainSpec{SupportedTemplates: supportedServiceTemplates[chain.Name]}, } @@ -196,6 +198,10 @@ var _ = Describe("Template Chain Controller", func() { ct := &hmcmirantiscomv1alpha1.ClusterTemplate{} err := k8sClient.Get(ctx, types.NamespacedName{Name: name, Namespace: utils.DefaultSystemNamespace}, ct) if err != nil && errors.IsNotFound(err) { + if template.Labels == nil { + template.Labels = make(map[string]string) + } + template.Labels[hmcmirantiscomv1alpha1.GenericComponentLabelName] = hmcmirantiscomv1alpha1.GenericComponentLabelValueHMC template.Spec.Providers = ctProviders Expect(k8sClient.Create(ctx, template)).To(Succeed()) } @@ -207,6 +213,10 @@ var _ = Describe("Template Chain Controller", func() { st := &hmcmirantiscomv1alpha1.ServiceTemplate{} err := k8sClient.Get(ctx, types.NamespacedName{Name: name, Namespace: utils.DefaultSystemNamespace}, st) if err != nil && errors.IsNotFound(err) { + if template.Labels == nil { + template.Labels = make(map[string]string) + } + template.Labels[hmcmirantiscomv1alpha1.GenericComponentLabelName] = hmcmirantiscomv1alpha1.GenericComponentLabelValueHMC template.Spec.Providers = stProviders Expect(k8sClient.Create(ctx, template)).To(Succeed()) } diff --git a/internal/utils/label.go b/internal/utils/label.go new file mode 100644 index 000000000..3e32a2724 --- /dev/null +++ b/internal/utils/label.go @@ -0,0 +1,53 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils + +import ( + "context" + "fmt" + + "sigs.k8s.io/controller-runtime/pkg/client" + + hmcv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" +) + +// AddLabel adds the provider label key and value to the object if not presented +// or if the existing label value does not equal the given one. +// Returns an indication of whether it updated the labels of the object. +func AddLabel(o client.Object, labelKey, labelValue string) (labelsUpdated bool) { + l := o.GetLabels() + v, ok := l[labelKey] + if ok && v == labelValue { + return false + } + if l == nil { + l = make(map[string]string) + } + l[labelKey] = labelValue + o.SetLabels(l) + return true +} + +// AddHMCComponentLabel adds the common HMC component label with the hmc value to the given object +// and updates if it is required. +func AddHMCComponentLabel(ctx context.Context, cl client.Client, o client.Object) error { + if !AddLabel(o, hmcv1alpha1.GenericComponentLabelName, hmcv1alpha1.GenericComponentLabelValueHMC) { + return nil + } + if err := cl.Update(ctx, o); err != nil { + return fmt.Errorf("failed to update %s %s labels: %w", o.GetObjectKind().GroupVersionKind().Kind, client.ObjectKeyFromObject(o), err) + } + return nil +} diff --git a/internal/utils/label_test.go b/internal/utils/label_test.go new file mode 100644 index 000000000..dc55470a4 --- /dev/null +++ b/internal/utils/label_test.go @@ -0,0 +1,92 @@ +// Copyright 2024 +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package utils_test + +import ( + "testing" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + hmcv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" + "github.com/Mirantis/hmc/internal/utils" +) + +func TestAddLabel(t *testing.T) { + obj := &hmcv1alpha1.Management{ObjectMeta: metav1.ObjectMeta{ + Labels: make(map[string]string), + }} + + withLabels := func(kv ...string) { + if len(kv) == 0 { + return + } + if len(kv)&1 != 0 { + panic("expected even number of args") + } + for k := range obj.Labels { + delete(obj.Labels, k) + } + for i := range len(kv) / 2 { + obj.Labels[kv[i*2]] = kv[i*2+1] + } + } + + type args struct { + mutate func() + labelKey string + labelValue string + } + tests := []struct { + name string + args args + wantLabelsUpdated bool + }{ + { + name: "no labels, expect updated map", + args: args{ + mutate: func() { withLabels() }, + labelKey: "foo", + labelValue: "bar", + }, + wantLabelsUpdated: true, + }, + { + name: "key exist diff value, expect updated map", + args: args{ + mutate: func() { withLabels("foo", "diff") }, + labelKey: "foo", + labelValue: "bar", + }, + wantLabelsUpdated: true, + }, + { + name: "key exist value is equal, expect no update required", + args: args{ + mutate: func() { withLabels("foo", "bar") }, + labelKey: "foo", + labelValue: "bar", + }, + }, + } + for _, tt := range tests { + _ = tt + t.Run(tt.name, func(t *testing.T) { + tt.args.mutate() + if gotLabelsUpdated := utils.AddLabel(obj, tt.args.labelKey, tt.args.labelValue); gotLabelsUpdated != tt.wantLabelsUpdated { + t.Errorf("AddLabel() = %v, want %v", gotLabelsUpdated, tt.wantLabelsUpdated) + } + }) + } +} diff --git a/internal/webhook/management_webhook.go b/internal/webhook/management_webhook.go index 516ebf93a..d97dbdcfe 100644 --- a/internal/webhook/management_webhook.go +++ b/internal/webhook/management_webhook.go @@ -265,6 +265,15 @@ func validateRelease(ctx context.Context, cl client.Client, releaseName string) } // Default implements webhook.Defaulter so a webhook will be registered for the type. -func (*ManagementValidator) Default(_ context.Context, _ runtime.Object) error { +func (*ManagementValidator) Default(_ context.Context, obj runtime.Object) error { + mgmt, ok := obj.(*hmcv1alpha1.Management) + if !ok { + return apierrors.NewBadRequest(fmt.Sprintf("expected Management but got a %T", obj)) + } + + if mgmt.Spec.Backup.Enabled && mgmt.Spec.Backup.Schedule == "" { + mgmt.Spec.Backup.Schedule = "0 */6 * * *" // every 6h + } + return nil } diff --git a/internal/webhook/management_webhook_test.go b/internal/webhook/management_webhook_test.go index d79c8d6a2..cea260559 100644 --- a/internal/webhook/management_webhook_test.go +++ b/internal/webhook/management_webhook_test.go @@ -22,6 +22,7 @@ import ( . "github.com/onsi/gomega" admissionv1 "k8s.io/api/admission/v1" "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client/fake" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -431,3 +432,56 @@ func TestManagementValidateDelete(t *testing.T) { }) } } + +func TestManagementDefault(t *testing.T) { + g := NewWithT(t) + + ctx := context.Background() + + tests := []struct { + name string + input client.Object + expected *v1alpha1.Management + err string + }{ + { + name: "should not set default backup schedule if already set", + input: management.NewManagement(management.WithBackup(v1alpha1.ManagementBackup{Enabled: true, Schedule: "0"})), + expected: management.NewManagement(management.WithBackup(v1alpha1.ManagementBackup{Enabled: true, Schedule: "0"})), + }, + { + name: "should set every six hours default backup schedule if backup is enabled but not set", + input: management.NewManagement(management.WithBackup(v1alpha1.ManagementBackup{Enabled: true})), + expected: management.NewManagement(management.WithBackup(v1alpha1.ManagementBackup{Enabled: true, Schedule: "0 */6 * * *"})), + }, + { + name: "should not set schedule if backup is disabled", + input: management.NewManagement(), + expected: management.NewManagement(), + }, + { + name: "should fail on non mgmt", + input: managedcluster.NewManagedCluster(), + err: "expected Management but got a *v1alpha1.ManagedCluster", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(_ *testing.T) { + c := fake.NewClientBuilder().WithScheme(scheme.Scheme).Build() + validator := &ManagementValidator{Client: c} + + err := validator.Default(ctx, tt.input) + if tt.err != "" { + g.Expect(err).To(HaveOccurred()) + g.Expect(err).To(MatchError(tt.err)) + } else { + g.Expect(err).To(Succeed()) + } + + if tt.expected != nil { + g.Expect(tt.input).To(BeEquivalentTo(tt.expected)) + } + }) + } +} diff --git a/templates/provider/hmc-templates/files/release.yaml b/templates/provider/hmc-templates/files/release.yaml index c882afc7d..c4df30041 100644 --- a/templates/provider/hmc-templates/files/release.yaml +++ b/templates/provider/hmc-templates/files/release.yaml @@ -4,6 +4,8 @@ metadata: name: hmc-0-0-5 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: version: 0.0.5 hmc: diff --git a/templates/provider/hmc-templates/files/templates/aws-eks-0-0-2.yaml b/templates/provider/hmc-templates/files/templates/aws-eks-0-0-2.yaml index fa3d75fd7..79843db74 100644 --- a/templates/provider/hmc-templates/files/templates/aws-eks-0-0-2.yaml +++ b/templates/provider/hmc-templates/files/templates/aws-eks-0-0-2.yaml @@ -4,6 +4,8 @@ metadata: name: aws-eks-0-0-2 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/aws-hosted-cp-0-0-3.yaml b/templates/provider/hmc-templates/files/templates/aws-hosted-cp-0-0-3.yaml index 30b3fd57a..8855640d4 100644 --- a/templates/provider/hmc-templates/files/templates/aws-hosted-cp-0-0-3.yaml +++ b/templates/provider/hmc-templates/files/templates/aws-hosted-cp-0-0-3.yaml @@ -4,6 +4,8 @@ metadata: name: aws-hosted-cp-0-0-3 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/aws-standalone-cp-0-0-4.yaml b/templates/provider/hmc-templates/files/templates/aws-standalone-cp-0-0-4.yaml index a72969fdf..5d53756f1 100644 --- a/templates/provider/hmc-templates/files/templates/aws-standalone-cp-0-0-4.yaml +++ b/templates/provider/hmc-templates/files/templates/aws-standalone-cp-0-0-4.yaml @@ -4,6 +4,8 @@ metadata: name: aws-standalone-cp-0-0-4 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/azure-hosted-cp-0-0-3.yaml b/templates/provider/hmc-templates/files/templates/azure-hosted-cp-0-0-3.yaml index aabd521bb..5a7151def 100644 --- a/templates/provider/hmc-templates/files/templates/azure-hosted-cp-0-0-3.yaml +++ b/templates/provider/hmc-templates/files/templates/azure-hosted-cp-0-0-3.yaml @@ -4,6 +4,8 @@ metadata: name: azure-hosted-cp-0-0-3 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/azure-standalone-cp-0-0-4.yaml b/templates/provider/hmc-templates/files/templates/azure-standalone-cp-0-0-4.yaml index b053db5e1..0d676e018 100644 --- a/templates/provider/hmc-templates/files/templates/azure-standalone-cp-0-0-4.yaml +++ b/templates/provider/hmc-templates/files/templates/azure-standalone-cp-0-0-4.yaml @@ -4,6 +4,8 @@ metadata: name: azure-standalone-cp-0-0-4 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/cert-manager-1-16-2.yaml b/templates/provider/hmc-templates/files/templates/cert-manager-1-16-2.yaml index 21f5131ec..59d26001f 100644 --- a/templates/provider/hmc-templates/files/templates/cert-manager-1-16-2.yaml +++ b/templates/provider/hmc-templates/files/templates/cert-manager-1-16-2.yaml @@ -4,6 +4,8 @@ metadata: name: cert-manager-1-16-2 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml b/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml index ddf83128a..7c8da0d40 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api-provider-aws.yaml @@ -4,6 +4,8 @@ metadata: name: cluster-api-provider-aws-0-0-4 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml b/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml index a92b7d9d8..4b0d97629 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api-provider-azure.yaml @@ -4,6 +4,8 @@ metadata: name: cluster-api-provider-azure-0-0-4 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml b/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml index 57a07e833..aa37affb3 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api-provider-vsphere.yaml @@ -4,6 +4,8 @@ metadata: name: cluster-api-provider-vsphere-0-0-4 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/cluster-api.yaml b/templates/provider/hmc-templates/files/templates/cluster-api.yaml index 4e6732d90..7618f2edd 100644 --- a/templates/provider/hmc-templates/files/templates/cluster-api.yaml +++ b/templates/provider/hmc-templates/files/templates/cluster-api.yaml @@ -4,6 +4,8 @@ metadata: name: cluster-api-0-0-5 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/dex-0-19-1.yaml b/templates/provider/hmc-templates/files/templates/dex-0-19-1.yaml index 28c2b5889..92b45589f 100644 --- a/templates/provider/hmc-templates/files/templates/dex-0-19-1.yaml +++ b/templates/provider/hmc-templates/files/templates/dex-0-19-1.yaml @@ -4,6 +4,8 @@ metadata: name: dex-0-19-1 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/external-secrets-0-11-0.yaml b/templates/provider/hmc-templates/files/templates/external-secrets-0-11-0.yaml index e95ee6158..4542376cf 100644 --- a/templates/provider/hmc-templates/files/templates/external-secrets-0-11-0.yaml +++ b/templates/provider/hmc-templates/files/templates/external-secrets-0-11-0.yaml @@ -4,6 +4,8 @@ metadata: name: external-secrets-0-11-0 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/hmc.yaml b/templates/provider/hmc-templates/files/templates/hmc.yaml index 8beb29aef..3e6e7a910 100644 --- a/templates/provider/hmc-templates/files/templates/hmc.yaml +++ b/templates/provider/hmc-templates/files/templates/hmc.yaml @@ -4,6 +4,8 @@ metadata: name: hmc-0-0-5 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-0.yaml b/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-0.yaml index c7092d713..a8cf1a7cf 100644 --- a/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-0.yaml +++ b/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-0.yaml @@ -4,6 +4,8 @@ metadata: name: ingress-nginx-4-11-0 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-3.yaml b/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-3.yaml index a55506fc6..e8a139632 100644 --- a/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-3.yaml +++ b/templates/provider/hmc-templates/files/templates/ingress-nginx-4-11-3.yaml @@ -4,6 +4,8 @@ metadata: name: ingress-nginx-4-11-3 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/k0smotron.yaml b/templates/provider/hmc-templates/files/templates/k0smotron.yaml index 3f2d16371..8a59a4377 100644 --- a/templates/provider/hmc-templates/files/templates/k0smotron.yaml +++ b/templates/provider/hmc-templates/files/templates/k0smotron.yaml @@ -4,6 +4,8 @@ metadata: name: k0smotron-0-0-4 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/kyverno-3-2-6.yaml b/templates/provider/hmc-templates/files/templates/kyverno-3-2-6.yaml index dc2ecb4da..e133c15f6 100644 --- a/templates/provider/hmc-templates/files/templates/kyverno-3-2-6.yaml +++ b/templates/provider/hmc-templates/files/templates/kyverno-3-2-6.yaml @@ -4,6 +4,8 @@ metadata: name: kyverno-3-2-6 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/projectsveltos.yaml b/templates/provider/hmc-templates/files/templates/projectsveltos.yaml index 95fb9312e..c88f0ef0c 100644 --- a/templates/provider/hmc-templates/files/templates/projectsveltos.yaml +++ b/templates/provider/hmc-templates/files/templates/projectsveltos.yaml @@ -4,6 +4,8 @@ metadata: name: projectsveltos-0-44-0 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/velero-8-1-0.yaml b/templates/provider/hmc-templates/files/templates/velero-8-1-0.yaml index fabb69123..03db61b6e 100644 --- a/templates/provider/hmc-templates/files/templates/velero-8-1-0.yaml +++ b/templates/provider/hmc-templates/files/templates/velero-8-1-0.yaml @@ -4,6 +4,8 @@ metadata: name: velero-8-1-0 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp-0-0-3.yaml b/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp-0-0-3.yaml index 4fcc0b118..4286b87e3 100644 --- a/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp-0-0-3.yaml +++ b/templates/provider/hmc-templates/files/templates/vsphere-hosted-cp-0-0-3.yaml @@ -4,6 +4,8 @@ metadata: name: vsphere-hosted-cp-0-0-3 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp-0-0-3.yaml b/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp-0-0-3.yaml index f628b7350..c60fb35a9 100644 --- a/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp-0-0-3.yaml +++ b/templates/provider/hmc-templates/files/templates/vsphere-standalone-cp-0-0-3.yaml @@ -4,6 +4,8 @@ metadata: name: vsphere-standalone-cp-0-0-3 annotations: helm.sh/resource-policy: keep + labels: + hmc.mirantis.com/component: hmc spec: helm: chartSpec: diff --git a/templates/provider/hmc/templates/_helpers.tpl b/templates/provider/hmc/templates/_helpers.tpl index 2d9e15365..780bffb09 100644 --- a/templates/provider/hmc/templates/_helpers.tpl +++ b/templates/provider/hmc/templates/_helpers.tpl @@ -40,6 +40,7 @@ helm.sh/chart: {{ include "hmc.chart" . }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} +hmc.mirantis.com/component: hmc {{- end }} {{/* @@ -108,3 +109,11 @@ hmc-webhook - list - watch {{- end -}} + +{{- define "backup.imageName" -}} +{{- if (.Values.controller.backup.image.fullName) -}} +{{- .Values.controller.backup.image.fullName -}} +{{- else -}} +{{- printf "%s/%s:%s" .Values.controller.backup.image.repository .Values.controller.backup.image.name .Values.controller.backup.image.tag -}} +{{- end -}} +{{- end -}} diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml index a6144d3ed..3652e601d 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml @@ -47,7 +47,6 @@ spec: into a cloud. properties: enabled: - default: false description: |- Flag to indicate whether the backup feature is enabled. If set to true, [Velero] platform will be installed. @@ -56,7 +55,6 @@ spec: [Velero]: https://velero.io type: boolean schedule: - default: 0 */6 * * * description: |- Schedule is a Cron expression defining when to run the scheduled Backup. Default value is to backup every 6 hours. diff --git a/templates/provider/hmc/templates/deployment.yaml b/templates/provider/hmc/templates/deployment.yaml index 31ae1004a..d2fcbe938 100644 --- a/templates/provider/hmc/templates/deployment.yaml +++ b/templates/provider/hmc/templates/deployment.yaml @@ -39,6 +39,14 @@ spec: env: - name: KUBERNETES_CLUSTER_DOMAIN value: {{ quote .Values.kubernetesClusterDomain }} + - name: BACKUP_BASIC_IMAGE + value: {{ template "backup.imageName" . }} + - name: BACKUP_FEATURES + value: {{ .Values.controller.backup.features }} + - name: BACKUP_SYSTEM_NAMESPACE + value: {{ .Values.controller.backup.namespace }} + - name: BACKUP_CTRL_REQUEUE_DURATION + value: {{ .Values.controller.backup.requeue }} image: {{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }} imagePullPolicy: {{ .Values.image.pullPolicy }} diff --git a/templates/provider/hmc/templates/rbac/controller/roles.yaml b/templates/provider/hmc/templates/rbac/controller/roles.yaml index 638b63e5c..4467ab592 100644 --- a/templates/provider/hmc/templates/rbac/controller/roles.yaml +++ b/templates/provider/hmc/templates/rbac/controller/roles.yaml @@ -214,7 +214,9 @@ rules: - "" resources: - secrets - verbs: {{ include "rbac.viewerVerbs" . | nindent 4 }} + verbs: {{ include "rbac.viewerVerbs" . | nindent 2 }} + - create +# backup-ctrl - apiGroups: - hmc.mirantis.com resources: @@ -234,6 +236,50 @@ rules: - get - patch - update +- apiGroups: + - "" + resources: + - serviceaccounts + - namespaces + verbs: {{ include "rbac.viewerVerbs" . | nindent 2 }} + - create +- apiGroups: + - apps + resources: + - deployments + verbs: {{ include "rbac.viewerVerbs" . | nindent 2 }} + - create + - delete + - patch +- apiGroups: + - rbac.authorization.k8s.io + resources: + - clusterrolebindings + - clusterroles + - rolebindings + - roles + verbs: {{ include "rbac.viewerVerbs" . | nindent 2 }} + - create +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: {{ include "rbac.viewerVerbs" . | nindent 2 }} + - create +- apiGroups: + - velero.io + resources: + - '*' + verbs: + - '*' +- apiGroups: + - '*' + resources: + - '*' + verbs: + - list + - get +# backup-ctrl --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role diff --git a/templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml b/templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml index 6fae7787e..1b977a065 100644 --- a/templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml +++ b/templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml @@ -12,3 +12,8 @@ rules: - backups - backups/status verbs: {{ include "rbac.editorVerbs" . | nindent 6 }} +- apiGroups: + - velero.io + resources: + - '*' + verbs: {{ include "rbac.editorVerbs" . | nindent 6 }} diff --git a/templates/provider/hmc/templates/rbac/user-facing/backup-viewer.yaml b/templates/provider/hmc/templates/rbac/user-facing/backup-viewer.yaml index 373511342..c6b57f6fe 100644 --- a/templates/provider/hmc/templates/rbac/user-facing/backup-viewer.yaml +++ b/templates/provider/hmc/templates/rbac/user-facing/backup-viewer.yaml @@ -12,3 +12,8 @@ rules: - backups - backups/status verbs: {{ include "rbac.viewerVerbs" . | nindent 6 }} +- apiGroups: + - velero.io + resources: + - '*' + verbs: {{ include "rbac.viewerVerbs" . | nindent 6 }} diff --git a/templates/provider/hmc/values.yaml b/templates/provider/hmc/values.yaml index 69fcd2e21..1b2e69a18 100644 --- a/templates/provider/hmc/values.yaml +++ b/templates/provider/hmc/values.yaml @@ -15,6 +15,14 @@ controller: createRelease: true createTemplates: true enableTelemetry: true + backup: + namespace: velero + features: "" + image: + repository: velero + name: velero + tag: v1.15.0 + requeue: 5s containerSecurityContext: allowPrivilegeEscalation: false diff --git a/test/objects/management/management.go b/test/objects/management/management.go index 67ad95970..6572a9f55 100644 --- a/test/objects/management/management.go +++ b/test/objects/management/management.go @@ -89,3 +89,9 @@ func WithRelease(v string) Opt { management.Spec.Release = v } } + +func WithBackup(v v1alpha1.ManagementBackup) Opt { + return func(management *v1alpha1.Management) { + management.Spec.Backup = v + } +}