diff --git a/.gitignore b/.gitignore index bcb9593..bd5cae9 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,6 @@ PROJECT # vendor/ bin/ config/* +!config/crd +config/crd/bases !config/samples diff --git a/api/v1alpha1/backupconfiguration_types.go b/api/v1alpha1/backupconfiguration_types.go index ba47f33..9d86a0d 100644 --- a/api/v1alpha1/backupconfiguration_types.go +++ b/api/v1alpha1/backupconfiguration_types.go @@ -29,10 +29,23 @@ type Repository struct { Name string `json:"name"` } +type Step struct { + Name string `json:"name"` + Env []corev1.EnvVar `json:"env"` +} + type Target struct { - ApiVersion string `json:"apiVersion"` - Name string `json:"name"` - Kind string `json:"kind"` + Kind string `json:"kind"` + // +optional + ApiVersion string `json:"apiVersion,omitempty"` + // +optional + Name string `json:"name,omitempty"` + // +optional + VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` + // +optional + Paths []string `json:"paths,omitempty"` + // +optional + Steps []Step `json:"steps,omitempty"` } type Keep struct { @@ -50,12 +63,8 @@ type BackupConfigurationSpec struct { // Foo is an example field of BackupConfiguration. Edit BackupConfiguration_types.go to remove/update Repository `json:"repository"` - Task string `json:"task,omitempty"` - Schedule string `json:"schedule"` - Target `json:"target"` - // +optional - VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` - Paths []string `json:"paths"` + Schedule string `json:"schedule"` + Targets []Target `json:"targets"` // +optional Keep `json:"keep,omitempty"` } diff --git a/api/v1alpha1/task_types.go b/api/v1alpha1/task_types.go deleted file mode 100644 index 7b41f9a..0000000 --- a/api/v1alpha1/task_types.go +++ /dev/null @@ -1,73 +0,0 @@ -/* - - -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 ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! -// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. - -type Param struct { - Name string `json:"name"` - Value string `json:"value"` -} - -type Step struct { - Name string `json:"name"` - Params []Param `json:"params,omitempty"` -} - -// TaskSpec defines the desired state of Task -type TaskSpec struct { - // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster - // Important: Run "make" to regenerate code after modifying this file - - // Foo is an example field of Task. Edit Task_types.go to remove/update - Steps []Step `json:"steps"` -} - -// TaskStatus defines the observed state of Task -type TaskStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file -} - -// +kubebuilder:object:root=true - -// Task is the Schema for the tasks API -type Task struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec TaskSpec `json:"spec,omitempty"` - Status TaskStatus `json:"status,omitempty"` -} - -// +kubebuilder:object:root=true - -// TaskList contains a list of Task -type TaskList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - Items []Task `json:"items"` -} - -func init() { - SchemeBuilder.Register(&Task{}, &TaskList{}) -} diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 314fa00..0d97fe3 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -105,19 +105,13 @@ func (in *BackupConfigurationList) DeepCopyObject() runtime.Object { func (in *BackupConfigurationSpec) DeepCopyInto(out *BackupConfigurationSpec) { *out = *in out.Repository = in.Repository - out.Target = in.Target - if in.VolumeMounts != nil { - in, out := &in.VolumeMounts, &out.VolumeMounts - *out = make([]v1.VolumeMount, len(*in)) + if in.Targets != nil { + in, out := &in.Targets, &out.Targets + *out = make([]Target, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.Paths != nil { - in, out := &in.Paths, &out.Paths - *out = make([]string, len(*in)) - copy(*out, *in) - } out.Keep = in.Keep } @@ -322,21 +316,6 @@ func (in *Keep) DeepCopy() *Keep { return out } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Param) DeepCopyInto(out *Param) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Param. -func (in *Param) DeepCopy() *Param { - if in == nil { - return nil - } - out := new(Param) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Ref) DeepCopyInto(out *Ref) { *out = *in @@ -475,10 +454,12 @@ func (in *S3) DeepCopy() *S3 { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Step) DeepCopyInto(out *Step) { *out = *in - if in.Params != nil { - in, out := &in.Params, &out.Params - *out = make([]Param, len(*in)) - copy(*out, *in) + if in.Env != nil { + in, out := &in.Env, &out.Env + *out = make([]v1.EnvVar, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } } } @@ -495,6 +476,25 @@ func (in *Step) DeepCopy() *Step { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Target) DeepCopyInto(out *Target) { *out = *in + if in.VolumeMounts != nil { + in, out := &in.VolumeMounts, &out.VolumeMounts + *out = make([]v1.VolumeMount, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Paths != nil { + in, out := &in.Paths, &out.Paths + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Steps != nil { + in, out := &in.Steps, &out.Steps + *out = make([]Step, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Target. @@ -506,99 +506,3 @@ func (in *Target) DeepCopy() *Target { in.DeepCopyInto(out) return out } - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Task) DeepCopyInto(out *Task) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Task. -func (in *Task) DeepCopy() *Task { - if in == nil { - return nil - } - out := new(Task) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Task) 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 *TaskList) DeepCopyInto(out *TaskList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Task, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskList. -func (in *TaskList) DeepCopy() *TaskList { - if in == nil { - return nil - } - out := new(TaskList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *TaskList) 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 *TaskSpec) DeepCopyInto(out *TaskSpec) { - *out = *in - if in.Steps != nil { - in, out := &in.Steps, &out.Steps - *out = make([]Step, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskSpec. -func (in *TaskSpec) DeepCopy() *TaskSpec { - if in == nil { - return nil - } - out := new(TaskSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *TaskStatus) DeepCopyInto(out *TaskStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TaskStatus. -func (in *TaskStatus) DeepCopy() *TaskStatus { - if in == nil { - return nil - } - out := new(TaskStatus) - in.DeepCopyInto(out) - return out -} diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml new file mode 100644 index 0000000..0b4701d --- /dev/null +++ b/config/crd/kustomization.yaml @@ -0,0 +1,31 @@ +# This kustomization.yaml is not intended to be run by itself, +# since it depends on service name and namespace that are out of this kustomize package. +# It should be run by config/default +resources: +- bases/formol.desmojim.fr_functions.yaml +- bases/formol.desmojim.fr_backupconfigurations.yaml +- bases/formol.desmojim.fr_backupsessions.yaml +- bases/formol.desmojim.fr_repoes.yaml +# +kubebuilder:scaffold:crdkustomizeresource + +patchesStrategicMerge: +# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix. +# patches here are for enabling the conversion webhook for each CRD +#- patches/webhook_in_tasks.yaml +#- patches/webhook_in_functions.yaml +#- patches/webhook_in_backupconfigurations.yaml +- patches/webhook_in_backupsessions.yaml +#- patches/webhook_in_repoes.yaml +# +kubebuilder:scaffold:crdkustomizewebhookpatch + +# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. +# patches here are for enabling the CA injection for each CRD +- patches/cainjection_in_functions.yaml +- patches/cainjection_in_backupconfigurations.yaml +- patches/cainjection_in_backupsessions.yaml +#- patches/cainjection_in_repoes.yaml +# +kubebuilder:scaffold:crdkustomizecainjectionpatch + +# the following config is for teaching kustomize how to do kustomization for CRDs. +configurations: +- kustomizeconfig.yaml diff --git a/config/crd/patches/cainjection_in_tasks.yaml b/config/crd/patches/cainjection_in_tasks.yaml deleted file mode 100644 index 647543b..0000000 --- a/config/crd/patches/cainjection_in_tasks.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# The following patch adds a directive for certmanager to inject CA into the CRD -# CRD conversion requires k8s 1.13 or later. -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - annotations: - cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME) - name: tasks.formol.desmojim.fr diff --git a/config/crd/patches/webhook_in_tasks.yaml b/config/crd/patches/webhook_in_tasks.yaml deleted file mode 100644 index 0922597..0000000 --- a/config/crd/patches/webhook_in_tasks.yaml +++ /dev/null @@ -1,17 +0,0 @@ -# The following patch enables conversion webhook for CRD -# CRD conversion requires k8s 1.13 or later. -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: tasks.formol.desmojim.fr -spec: - conversion: - strategy: Webhook - webhookClientConfig: - # this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, - # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager) - caBundle: Cg== - service: - namespace: system - name: webhook-service - path: /convert diff --git a/config/rbac/task_editor_role.yaml b/config/rbac/task_editor_role.yaml deleted file mode 100644 index 29aefe1..0000000 --- a/config/rbac/task_editor_role.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# permissions for end users to edit tasks. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: task-editor-role -rules: -- apiGroups: - - formol.desmojim.fr - resources: - - tasks - verbs: - - create - - delete - - get - - list - - patch - - update - - watch -- apiGroups: - - formol.desmojim.fr - resources: - - tasks/status - verbs: - - get diff --git a/config/rbac/task_viewer_role.yaml b/config/rbac/task_viewer_role.yaml deleted file mode 100644 index 96fa07e..0000000 --- a/config/rbac/task_viewer_role.yaml +++ /dev/null @@ -1,20 +0,0 @@ -# permissions for end users to view tasks. -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: task-viewer-role -rules: -- apiGroups: - - formol.desmojim.fr - resources: - - tasks - verbs: - - get - - list - - watch -- apiGroups: - - formol.desmojim.fr - resources: - - tasks/status - verbs: - - get diff --git a/config/samples/formol_v1alpha1_task.yaml b/config/samples/formol_v1alpha1_task.yaml deleted file mode 100644 index 72eaf24..0000000 --- a/config/samples/formol_v1alpha1_task.yaml +++ /dev/null @@ -1,9 +0,0 @@ -apiVersion: formol.desmojim.fr/v1alpha1 -kind: Task -metadata: - name: task-backup-pvc - namespace: backup -spec: - steps: - - name: function-backup-pvc - diff --git a/controllers/backupconfiguration_controller.go b/controllers/backupconfiguration_controller.go index 6f7a5e4..38651c5 100644 --- a/controllers/backupconfiguration_controller.go +++ b/controllers/backupconfiguration_controller.go @@ -67,8 +67,8 @@ func (r *BackupConfigurationReconciler) getDeployment(namespace string, name str // +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=batch,resources=cronjobs/status,verbs=get -func (r *BackupConfigurationReconciler) deleteSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration) error { - deployment, err := r.getDeployment(backupConf.Namespace, backupConf.Spec.Target.Name) +func (r *BackupConfigurationReconciler) deleteSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration, target formolv1alpha1.Target) error { + deployment, err := r.getDeployment(backupConf.Namespace, target.Name) if err != nil { return err } @@ -118,9 +118,9 @@ func (r *BackupConfigurationReconciler) deleteSidecarContainer(backupConf *formo return nil } -func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration) error { +func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration, target formolv1alpha1.Target) error { log := r.Log.WithValues("backupconf", backupConf.Name) - deployment, err := r.getDeployment(backupConf.Namespace, backupConf.Spec.Target.Name) + deployment, err := r.getDeployment(backupConf.Namespace, target.Name) if err != nil { log.Error(err, "unable to get Deployment") return err @@ -174,7 +174,7 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1 } // S3 backing storage if (formolv1alpha1.S3{}) != repo.Spec.Backend.S3 { - url := "s3:http://" + repo.Spec.Backend.S3.Server + "/" + repo.Spec.Backend.S3.Bucket + "/" + backupConf.Spec.Target.Name + url := "s3:http://" + repo.Spec.Backend.S3.Server + "/" + repo.Spec.Backend.S3.Bucket + "/" + target.Name sidecar.Env = append(sidecar.Env, corev1.EnvVar{ Name: "RESTIC_REPOSITORY", Value: url, @@ -198,7 +198,7 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1 } } - for _, volumemount := range backupConf.Spec.VolumeMounts { + for _, volumemount := range target.VolumeMounts { log.V(1).Info("mounts", "volumemount", volumemount) volumemount.ReadOnly = true sidecar.VolumeMounts = append(sidecar.VolumeMounts, *volumemount.DeepCopy()) @@ -366,15 +366,17 @@ func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result } backupConf.Status.ActiveCronJob = true - switch backupConf.Spec.Target.Kind { - case "Deployment": - if err := r.addSidecarContainer(backupConf); err != nil { + for _, target := range backupConf.Spec.Targets { + switch target.Kind { + case "Deployment": + if err := r.addSidecarContainer(backupConf, target); err != nil { + return ctrl.Result{}, nil + } + backupConf.Status.ActiveSidecar = true + case "PersistentVolumeClaim": + log.V(0).Info("TODO backup PVC") return ctrl.Result{}, nil } - backupConf.Status.ActiveSidecar = true - case "PersistentVolumeClaim": - log.V(0).Info("TODO backup PVC") - return ctrl.Result{}, nil } backupConf.Status.Suspended = false @@ -388,18 +390,23 @@ func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result } func (r *BackupConfigurationReconciler) deleteExternalResources(backupConf *formolv1alpha1.BackupConfiguration) error { - deployment, err := r.getDeployment(backupConf.Namespace, backupConf.Spec.Target.Name) - if err != nil { - return err - } - if err := formolrbac.DeleteBackupSessionListenerRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil { - return err - } - if err := formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil { - return err - } - if err := r.deleteSidecarContainer(backupConf); err != nil { - return err + for _, target := range backupConf.Spec.Targets { + switch target.Kind { + case "Deployment": + deployment, err := r.getDeployment(backupConf.Namespace, target.Name) + if err != nil { + return err + } + if err := formolrbac.DeleteBackupSessionListenerRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil { + return err + } + if err := formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil { + return err + } + if err := r.deleteSidecarContainer(backupConf, target); err != nil { + return err + } + } } return nil } diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 0f819b9..af337f2 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -61,12 +61,13 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro log.V(1).Info("Found BackupConfiguration", "BackupConfiguration", backupConf) // Found the BackupConfiguration. - switch backupConf.Spec.Target.Kind { - case "PersistentVolumeClaim": - return r.CreateJob() - default: - return ctrl.Result{}, nil + for _, target := range backupConf.Spec.Targets { + switch target.Kind { + case "PersistentVolumeClaim": + return r.CreateJob() + } } + return ctrl.Result{}, nil } func (r *BackupSessionReconciler) CreateJob() (ctrl.Result, error) {