From b5270a1dd9249833bd32a80f20ad34018fa81ad1 Mon Sep 17 00:00:00 2001 From: zerospiel Date: Mon, 2 Dec 2024 17:36:06 +0100 Subject: [PATCH] Introduce Backup API * Backup API for disaster recovery (kubebuilder project, ctrl and tests stubs, main ctrl add) * edit/reader roles for the Backup API Closes #604 --- PROJECT | 9 + api/v1alpha1/backup_types.go | 79 +++++ api/v1alpha1/management_types.go | 17 + api/v1alpha1/zz_generated.deepcopy.go | 121 +++++++ cmd/main.go | 7 + go.mod | 3 +- go.sum | 4 +- internal/controller/backup_controller.go | 60 ++++ internal/controller/backup_controller_test.go | 81 +++++ .../crds/hmc.mirantis.com_backups.yaml | 294 ++++++++++++++++++ .../crds/hmc.mirantis.com_managements.yaml | 20 ++ .../rbac/user-facing/backup-editor.yaml | 14 + .../rbac/user-facing/backup-viewer.yaml | 14 + 13 files changed, 721 insertions(+), 2 deletions(-) create mode 100644 api/v1alpha1/backup_types.go create mode 100644 internal/controller/backup_controller.go create mode 100644 internal/controller/backup_controller_test.go create mode 100644 templates/provider/hmc/templates/crds/hmc.mirantis.com_backups.yaml create mode 100644 templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml create mode 100644 templates/provider/hmc/templates/rbac/user-facing/backup-viewer.yaml diff --git a/PROJECT b/PROJECT index 6db141328..dfaec2288 100644 --- a/PROJECT +++ b/PROJECT @@ -104,4 +104,13 @@ resources: kind: MultiClusterService path: github.com/Mirantis/hmc/api/v1alpha1 version: v1alpha1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: hmc.mirantis.com + group: hmc.mirantis.com + kind: Backup + path: github.com/Mirantis/hmc/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/v1alpha1/backup_types.go b/api/v1alpha1/backup_types.go new file mode 100644 index 000000000..c10ac476d --- /dev/null +++ b/api/v1alpha1/backup_types.go @@ -0,0 +1,79 @@ +// 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 v1alpha1 + +import ( + velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// BackupSpec defines the desired state of Backup +type BackupSpec struct { + // +kubebuilder:default="0 */6 * * *" + + // Schedule is a Cron expression defining when to run the Backup. + // A shortcut instead of filling the .customSchedule field up. + // Default value is to backup every 6 hours. + // If both this field and the .customSchedule field + // are given, the schedule from the latter will be utilized. + Schedule string `json:"schedule"` + + // Oneshot indicates whether the Backup should not be scheduled + // and rather created immediately and only once. + // If set to true, the .schedule field is ignored. + // If set to true and the .customSchedule field is given, + // the .spec.template from the latter will be utilized, + // the HMC-required options still might override or precede the options + // from the field. + Oneshot bool `json:"oneshot,omitempty"` +} + +// BackupStatus defines the observed state of Backup +type BackupStatus struct { + // Reference to the underlying Velero object being managed. + // Might be either Velero Backup or Schedule. + Reference *corev1.ObjectReference `json:"reference,omitempty"` + // Status of the Velero Schedule if .spec.oneshot is set to false. + Schedule *velerov1.ScheduleStatus `json:"schedule,omitempty"` + // Last Velero Backup that has been created. + LastBackup *velerov1.BackupStatus `json:"lastBackup,omitempty"` +} + +// +kubebuilder:object:root=true +// +kubebuilder:subresource:status +// +kubebuilder:resource:scope=Cluster + +// Backup is the Schema for the backups API +type Backup struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec BackupSpec `json:"spec,omitempty"` + Status BackupStatus `json:"status,omitempty"` +} + +// +kubebuilder:object:root=true + +// BackupList contains a list of Backup +type BackupList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []Backup `json:"items"` +} + +func init() { + SchemeBuilder.Register(&Backup{}, &BackupList{}) +} diff --git a/api/v1alpha1/management_types.go b/api/v1alpha1/management_types.go index c09f9d8d9..3d3605d3d 100644 --- a/api/v1alpha1/management_types.go +++ b/api/v1alpha1/management_types.go @@ -34,6 +34,7 @@ const ( type ManagementSpec struct { // +kubebuilder:validation:MinLength=1 // +kubebuilder:validation:MaxLength=253 + // Release references the Release object. Release string `json:"release"` // Core holds the core Management components that are mandatory. @@ -42,6 +43,8 @@ type ManagementSpec struct { // Providers is the list of supported CAPI providers. Providers []Provider `json:"providers,omitempty"` + + Backup ManagementBackup `json:"backup,omitempty"` } // Core represents a structure describing core Management components. @@ -52,6 +55,18 @@ type Core struct { CAPI Component `json:"capi,omitempty"` } +// ManagementBackup enables a feature to backup HMC objects into a cloud. +type ManagementBackup struct { + // +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. + // + // [Velero]: https://velero.io + Enabled bool `json:"enabled"` +} + // Component represents HMC management component type Component struct { // Config allows to provide parameters for management component customization. @@ -116,6 +131,8 @@ type ManagementStatus struct { CAPIContracts map[string]CompatibilityContracts `json:"capiContracts,omitempty"` // Components indicates the status of installed HMC components and CAPI providers. Components map[string]ComponentStatus `json:"components,omitempty"` + // BackupName is a name of the management cluster scheduled backup. + BackupName string `json:"backupName,omitempty"` // Release indicates the current Release object. Release string `json:"release,omitempty"` // AvailableProviders holds all available CAPI providers. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 48fbe8536..965efe2fe 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -20,6 +20,7 @@ package v1alpha1 import ( "github.com/fluxcd/helm-controller/api/v2" + velerov1 "github.com/vmware-tanzu/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" @@ -175,6 +176,110 @@ func (in *AvailableUpgrade) DeepCopy() *AvailableUpgrade { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Backup) DeepCopyInto(out *Backup) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + out.Spec = in.Spec + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Backup. +func (in *Backup) DeepCopy() *Backup { + if in == nil { + return nil + } + out := new(Backup) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Backup) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupList) DeepCopyInto(out *BackupList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Backup, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupList. +func (in *BackupList) DeepCopy() *BackupList { + if in == nil { + return nil + } + out := new(BackupList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *BackupList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupSpec) DeepCopyInto(out *BackupSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSpec. +func (in *BackupSpec) DeepCopy() *BackupSpec { + if in == nil { + return nil + } + out := new(BackupSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackupStatus) DeepCopyInto(out *BackupStatus) { + *out = *in + if in.Reference != nil { + in, out := &in.Reference, &out.Reference + *out = new(corev1.ObjectReference) + **out = **in + } + if in.Schedule != nil { + in, out := &in.Schedule, &out.Schedule + *out = new(velerov1.ScheduleStatus) + (*in).DeepCopyInto(*out) + } + if in.LastBackup != nil { + in, out := &in.LastBackup, &out.LastBackup + *out = new(velerov1.BackupStatus) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupStatus. +func (in *BackupStatus) DeepCopy() *BackupStatus { + if in == nil { + return nil + } + out := new(BackupStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ClusterTemplate) DeepCopyInto(out *ClusterTemplate) { *out = *in @@ -702,6 +807,21 @@ func (in *Management) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ManagementBackup) DeepCopyInto(out *ManagementBackup) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagementBackup. +func (in *ManagementBackup) DeepCopy() *ManagementBackup { + if in == nil { + return nil + } + out := new(ManagementBackup) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ManagementList) DeepCopyInto(out *ManagementList) { *out = *in @@ -749,6 +869,7 @@ func (in *ManagementSpec) DeepCopyInto(out *ManagementSpec) { (*in)[i].DeepCopyInto(&(*out)[i]) } } + out.Backup = in.Backup } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ManagementSpec. diff --git a/cmd/main.go b/cmd/main.go index a6e1aa80c..a341a5e04 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -302,6 +302,13 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "MultiClusterService") os.Exit(1) } + 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) + } // +kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/go.mod b/go.mod index f226e630d..263357346 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/Mirantis/hmc -go 1.22.7 +go 1.22.10 require ( github.com/Masterminds/semver/v3 v3.3.1 @@ -19,6 +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 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.16.3 k8s.io/api v0.31.3 diff --git a/go.sum b/go.sum index 840260219..60f955598 100644 --- a/go.sum +++ b/go.sum @@ -457,6 +457,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO 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= @@ -618,7 +620,7 @@ 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-20231211222908-989df2bf70f3 h1:1hfbdAfFbkmpg41000wDVqr7jUpK/Yo+LPnIxxGzmkg= +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= diff --git a/internal/controller/backup_controller.go b/internal/controller/backup_controller.go new file mode 100644 index 000000000..3572b673a --- /dev/null +++ b/internal/controller/backup_controller.go @@ -0,0 +1,60 @@ +// 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 controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + hmcmirantiscomv1alpha1 "github.com/Mirantis/hmc/api/v1alpha1" +) + +// BackupReconciler reconciles a Backup object +type BackupReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +// +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 + +// 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) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BackupReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&hmcmirantiscomv1alpha1.Backup{}). + Complete(r) +} diff --git a/internal/controller/backup_controller_test.go b/internal/controller/backup_controller_test.go new file mode 100644 index 000000000..5fc61af7f --- /dev/null +++ b/internal/controller/backup_controller_test.go @@ -0,0 +1,81 @@ +// 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 controller + +import ( + "context" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "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" +) + +var _ = Describe("Backup Controller", func() { + Context("When reconciling a resource", func() { + const resourceName = "test-resource" + + ctx := context.Background() + + typeNamespacedName := types.NamespacedName{ + Name: resourceName, + Namespace: "default", // TODO(user):Modify as needed + } + backup := &hmcmirantiscomv1alpha1.Backup{} + + BeforeEach(func() { + By("creating the custom resource for the Kind Backup") + err := k8sClient.Get(ctx, typeNamespacedName, backup) + if err != nil && errors.IsNotFound(err) { + resource := &hmcmirantiscomv1alpha1.Backup{ + ObjectMeta: metav1.ObjectMeta{ + Name: resourceName, + Namespace: "default", + }, + // 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()) + + 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(), + } + + _, 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. + }) + }) +}) diff --git a/templates/provider/hmc/templates/crds/hmc.mirantis.com_backups.yaml b/templates/provider/hmc/templates/crds/hmc.mirantis.com_backups.yaml new file mode 100644 index 000000000..8de58b19c --- /dev/null +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_backups.yaml @@ -0,0 +1,294 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.3 + name: backups.hmc.mirantis.com +spec: + group: hmc.mirantis.com + names: + kind: Backup + listKind: BackupList + plural: backups + singular: backup + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: Backup is the Schema for the backups API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: BackupSpec defines the desired state of Backup + properties: + oneshot: + description: |- + Oneshot indicates whether the Backup should not be scheduled + and rather created immediately and only once. + If set to true, the .schedule field is ignored. + If set to true and the .customSchedule field is given, + the .spec.template from the latter will be utilized, + the HMC-required options still might override or precede the options + from the field. + type: boolean + schedule: + default: 0 */6 * * * + description: |- + Schedule is a Cron expression defining when to run the Backup. + A shortcut instead of filling the .customSchedule field up. + Default value is to backup every 6 hours. + If both this field and the .customSchedule field + are given, the schedule from the latter will be utilized. + type: string + required: + - schedule + type: object + status: + description: BackupStatus defines the observed state of Backup + properties: + lastBackup: + description: Last Velero Backup that has been created. + properties: + backupItemOperationsAttempted: + description: |- + BackupItemOperationsAttempted is the total number of attempted + async BackupItemAction operations for this backup. + type: integer + backupItemOperationsCompleted: + description: |- + BackupItemOperationsCompleted is the total number of successfully completed + async BackupItemAction operations for this backup. + type: integer + backupItemOperationsFailed: + description: |- + BackupItemOperationsFailed is the total number of async + BackupItemAction operations for this backup which ended with an error. + type: integer + completionTimestamp: + description: |- + CompletionTimestamp records the time a backup was completed. + Completion time is recorded even on failed backups. + Completion time is recorded before uploading the backup object. + The server's time is used for CompletionTimestamps + format: date-time + nullable: true + type: string + csiVolumeSnapshotsAttempted: + description: |- + CSIVolumeSnapshotsAttempted is the total number of attempted + CSI VolumeSnapshots for this backup. + type: integer + csiVolumeSnapshotsCompleted: + description: |- + CSIVolumeSnapshotsCompleted is the total number of successfully + completed CSI VolumeSnapshots for this backup. + type: integer + errors: + description: |- + Errors is a count of all error messages that were generated during + execution of the backup. The actual errors are in the backup's log + file in object storage. + type: integer + expiration: + description: Expiration is when this Backup is eligible for garbage-collection. + format: date-time + nullable: true + type: string + failureReason: + description: FailureReason is an error that caused the entire + backup to fail. + type: string + formatVersion: + description: FormatVersion is the backup format version, including + major, minor, and patch version. + type: string + hookStatus: + description: HookStatus contains information about the status + of the hooks. + nullable: true + properties: + hooksAttempted: + description: |- + HooksAttempted is the total number of attempted hooks + Specifically, HooksAttempted represents the number of hooks that failed to execute + and the number of hooks that executed successfully. + type: integer + hooksFailed: + description: HooksFailed is the total number of hooks which + ended with an error + type: integer + type: object + phase: + description: Phase is the current state of the Backup. + enum: + - New + - FailedValidation + - InProgress + - WaitingForPluginOperations + - WaitingForPluginOperationsPartiallyFailed + - Finalizing + - FinalizingPartiallyFailed + - Completed + - PartiallyFailed + - Failed + - Deleting + type: string + progress: + description: |- + Progress contains information about the backup's execution progress. Note + that this information is best-effort only -- if Velero fails to update it + during a backup for any reason, it may be inaccurate/stale. + nullable: true + properties: + itemsBackedUp: + description: |- + ItemsBackedUp is the number of items that have actually been written to the + backup tarball so far. + type: integer + totalItems: + description: |- + TotalItems is the total number of items to be backed up. This number may change + throughout the execution of the backup due to plugins that return additional related + items to back up, the velero.io/exclude-from-backup label, and various other + filters that happen as items are processed. + type: integer + type: object + startTimestamp: + description: |- + StartTimestamp records the time a backup was started. + Separate from CreationTimestamp, since that value changes + on restores. + The server's time is used for StartTimestamps + format: date-time + nullable: true + type: string + validationErrors: + description: |- + ValidationErrors is a slice of all validation errors (if + applicable). + items: + type: string + nullable: true + type: array + version: + description: |- + Version is the backup format major version. + Deprecated: Please see FormatVersion + type: integer + volumeSnapshotsAttempted: + description: |- + VolumeSnapshotsAttempted is the total number of attempted + volume snapshots for this backup. + type: integer + volumeSnapshotsCompleted: + description: |- + VolumeSnapshotsCompleted is the total number of successfully + completed volume snapshots for this backup. + type: integer + warnings: + description: |- + Warnings is a count of all warning messages that were generated during + execution of the backup. The actual warnings are in the backup's log + file in object storage. + type: integer + type: object + reference: + description: |- + Reference to the underlying Velero object being managed. + Might be either Velero Backup or Schedule. + properties: + apiVersion: + description: API version of the referent. + type: string + fieldPath: + description: |- + If referring to a piece of an object instead of an entire object, this string + should contain a valid JSON/Go field access statement, such as desiredState.manifest.containers[2]. + For example, if the object reference is to a container within a pod, this would take on a value like: + "spec.containers{name}" (where "name" refers to the name of the container that triggered + the event) or if no container name is specified "spec.containers[2]" (container with + index 2 in this pod). This syntax is chosen only to have some well-defined way of + referencing a part of an object. + type: string + kind: + description: |- + Kind of the referent. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + name: + description: |- + Name of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + namespace: + description: |- + Namespace of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/namespaces/ + type: string + resourceVersion: + description: |- + Specific resourceVersion to which this reference is made, if any. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency + type: string + uid: + description: |- + UID of the referent. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#uids + type: string + type: object + x-kubernetes-map-type: atomic + schedule: + description: Status of the Velero Schedule if .spec.oneshot is set + to false. + properties: + lastBackup: + description: |- + LastBackup is the last time a Backup was run for this + Schedule schedule + format: date-time + nullable: true + type: string + lastSkipped: + description: LastSkipped is the last time a Schedule was skipped + format: date-time + nullable: true + type: string + phase: + description: Phase is the current phase of the Schedule + enum: + - New + - Enabled + - FailedValidation + type: string + validationErrors: + description: |- + ValidationErrors is a slice of all validation errors (if + applicable) + items: + type: string + type: array + type: object + type: object + type: object + served: true + storage: true + subresources: + status: {} 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 7ff679588..5f9c5e6e2 100644 --- a/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml +++ b/templates/provider/hmc/templates/crds/hmc.mirantis.com_managements.yaml @@ -42,6 +42,22 @@ spec: spec: description: ManagementSpec defines the desired state of Management properties: + backup: + description: ManagementBackup enables a feature to backup HMC objects + 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. + If set to false, creation or modification of Backups/Restores will be blocked. + + [Velero]: https://velero.io + type: boolean + required: + - enabled + type: object core: description: |- Core holds the core Management components that are mandatory. @@ -118,6 +134,10 @@ spec: items: type: string type: array + backupName: + description: BackupName is a name of the management cluster scheduled + backup. + type: string capiContracts: additionalProperties: additionalProperties: diff --git a/templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml b/templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml new file mode 100644 index 000000000..6fae7787e --- /dev/null +++ b/templates/provider/hmc/templates/rbac/user-facing/backup-editor.yaml @@ -0,0 +1,14 @@ +# permissions for end users to edit backups. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + hmc.mirantis.com/aggregate-to-global-admin: "true" + name: {{ include "hmc.fullname" . }}-backup-editor-role +rules: +- apiGroups: + - hmc.mirantis.com + resources: + - backups + - backups/status + 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 new file mode 100644 index 000000000..373511342 --- /dev/null +++ b/templates/provider/hmc/templates/rbac/user-facing/backup-viewer.yaml @@ -0,0 +1,14 @@ +# permissions for end users to view backups. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + hmc.mirantis.com/aggregate-to-global-viewer: "true" + name: {{ include "hmc.fullname" . }}-backup-viewer-role +rules: +- apiGroups: + - hmc.mirantis.com + resources: + - backups + - backups/status + verbs: {{ include "rbac.viewerVerbs" . | nindent 6 }}