diff --git a/api/v1alpha1/function_types.go b/api/v1alpha1/function_types.go index 10b6550..55adfae 100644 --- a/api/v1alpha1/function_types.go +++ b/api/v1alpha1/function_types.go @@ -17,27 +17,13 @@ limitations under the License. package v1alpha1 import ( + corev1 "k8s.io/api/core/v1" 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. -// FunctionSpec defines the desired state of Function -type FunctionSpec 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 Function. Edit Function_types.go to remove/update - Foo string `json:"foo,omitempty"` -} - -// FunctionStatus defines the observed state of Function -type FunctionStatus struct { - // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster - // Important: Run "make" to regenerate code after modifying this file -} - // +kubebuilder:object:root=true // Function is the Schema for the functions API @@ -45,8 +31,7 @@ type Function struct { metav1.TypeMeta `json:",inline"` metav1.ObjectMeta `json:"metadata,omitempty"` - Spec FunctionSpec `json:"spec,omitempty"` - Status FunctionStatus `json:"status,omitempty"` + Spec corev1.Container `json:"spec,omitempty"` } // +kubebuilder:object:root=true diff --git a/api/v1alpha1/task_types.go b/api/v1alpha1/task_types.go index 2f21690..7b41f9a 100644 --- a/api/v1alpha1/task_types.go +++ b/api/v1alpha1/task_types.go @@ -23,13 +23,23 @@ import ( // 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 - Foo string `json:"foo,omitempty"` + Steps []Step `json:"steps"` } // TaskStatus defines the observed state of Task diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go deleted file mode 100644 index 5ca9a96..0000000 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ /dev/null @@ -1,427 +0,0 @@ -// +build !ignore_autogenerated - -/* - - -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. -*/ - -// Code generated by controller-gen. DO NOT EDIT. - -package v1alpha1 - -import ( - runtime "k8s.io/apimachinery/pkg/runtime" -) - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BackupConfiguration) DeepCopyInto(out *BackupConfiguration) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - in.Spec.DeepCopyInto(&out.Spec) - in.Status.DeepCopyInto(&out.Status) -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupConfiguration. -func (in *BackupConfiguration) DeepCopy() *BackupConfiguration { - if in == nil { - return nil - } - out := new(BackupConfiguration) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BackupConfiguration) 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 *BackupConfigurationList) DeepCopyInto(out *BackupConfigurationList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]BackupConfiguration, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupConfigurationList. -func (in *BackupConfigurationList) DeepCopy() *BackupConfigurationList { - if in == nil { - return nil - } - out := new(BackupConfigurationList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BackupConfigurationList) 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 *BackupConfigurationSpec) DeepCopyInto(out *BackupConfigurationSpec) { - *out = *in - out.Repository = in.Repository - if in.Suspend != nil { - in, out := &in.Suspend, &out.Suspend - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupConfigurationSpec. -func (in *BackupConfigurationSpec) DeepCopy() *BackupConfigurationSpec { - if in == nil { - return nil - } - out := new(BackupConfigurationSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BackupConfigurationStatus) DeepCopyInto(out *BackupConfigurationStatus) { - *out = *in - if in.LastBackupTime != nil { - in, out := &in.LastBackupTime, &out.LastBackupTime - *out = (*in).DeepCopy() - } - if in.Suspended != nil { - in, out := &in.Suspended, &out.Suspended - *out = new(bool) - **out = **in - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupConfigurationStatus. -func (in *BackupConfigurationStatus) DeepCopy() *BackupConfigurationStatus { - if in == nil { - return nil - } - out := new(BackupConfigurationStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BackupSession) DeepCopyInto(out *BackupSession) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSession. -func (in *BackupSession) DeepCopy() *BackupSession { - if in == nil { - return nil - } - out := new(BackupSession) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BackupSession) 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 *BackupSessionList) DeepCopyInto(out *BackupSessionList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]BackupSession, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSessionList. -func (in *BackupSessionList) DeepCopy() *BackupSessionList { - if in == nil { - return nil - } - out := new(BackupSessionList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *BackupSessionList) 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 *BackupSessionSpec) DeepCopyInto(out *BackupSessionSpec) { - *out = *in - out.Ref = in.Ref -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSessionSpec. -func (in *BackupSessionSpec) DeepCopy() *BackupSessionSpec { - if in == nil { - return nil - } - out := new(BackupSessionSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *BackupSessionStatus) DeepCopyInto(out *BackupSessionStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSessionStatus. -func (in *BackupSessionStatus) DeepCopy() *BackupSessionStatus { - if in == nil { - return nil - } - out := new(BackupSessionStatus) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Function) DeepCopyInto(out *Function) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec - out.Status = in.Status -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Function. -func (in *Function) DeepCopy() *Function { - if in == nil { - return nil - } - out := new(Function) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *Function) 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 *FunctionList) DeepCopyInto(out *FunctionList) { - *out = *in - out.TypeMeta = in.TypeMeta - in.ListMeta.DeepCopyInto(&out.ListMeta) - if in.Items != nil { - in, out := &in.Items, &out.Items - *out = make([]Function, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionList. -func (in *FunctionList) DeepCopy() *FunctionList { - if in == nil { - return nil - } - out := new(FunctionList) - in.DeepCopyInto(out) - return out -} - -// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *FunctionList) 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 *FunctionSpec) DeepCopyInto(out *FunctionSpec) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionSpec. -func (in *FunctionSpec) DeepCopy() *FunctionSpec { - if in == nil { - return nil - } - out := new(FunctionSpec) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *FunctionStatus) DeepCopyInto(out *FunctionStatus) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new FunctionStatus. -func (in *FunctionStatus) DeepCopy() *FunctionStatus { - if in == nil { - return nil - } - out := new(FunctionStatus) - 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 -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Ref. -func (in *Ref) DeepCopy() *Ref { - if in == nil { - return nil - } - out := new(Ref) - in.DeepCopyInto(out) - return out -} - -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Repository) DeepCopyInto(out *Repository) { - *out = *in -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Repository. -func (in *Repository) DeepCopy() *Repository { - if in == nil { - return nil - } - out := new(Repository) - 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) - out.Spec = in.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 -} - -// 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/bin/manager b/bin/manager deleted file mode 100755 index a799d7a..0000000 Binary files a/bin/manager and /dev/null differ diff --git a/config/samples/formol_v1alpha1_backupconfiguration.yaml b/config/samples/formol_v1alpha1_backupconfiguration.yaml index d1f3370..4b282ed 100644 --- a/config/samples/formol_v1alpha1_backupconfiguration.yaml +++ b/config/samples/formol_v1alpha1_backupconfiguration.yaml @@ -6,5 +6,5 @@ metadata: spec: repository: name: local-backups - task: backup-pvc + task: task-backup-pv schedule: "*/5 * * * *" diff --git a/config/samples/formol_v1alpha1_function.yaml b/config/samples/formol_v1alpha1_function.yaml index 094b0ad..502fe85 100644 --- a/config/samples/formol_v1alpha1_function.yaml +++ b/config/samples/formol_v1alpha1_function.yaml @@ -1,7 +1,11 @@ apiVersion: formol.desmojim.fr/v1alpha1 kind: Function metadata: - name: function-sample + name: function-backup-pv + namespace: backup spec: - # Add fields here - foo: bar + name: function-backup-pv + image: desmo999r/formolcli + args: + - backup + - pvc diff --git a/config/samples/formol_v1alpha1_task.yaml b/config/samples/formol_v1alpha1_task.yaml index c69a6b0..2cf0585 100644 --- a/config/samples/formol_v1alpha1_task.yaml +++ b/config/samples/formol_v1alpha1_task.yaml @@ -1,7 +1,9 @@ apiVersion: formol.desmojim.fr/v1alpha1 kind: Task metadata: - name: task-sample + name: task-backup-pv + namespace: backup spec: - # Add fields here - foo: bar + steps: + - name: function-backup-pv + diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index d4ddf1f..7459810 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -18,6 +18,8 @@ package controllers import ( "context" + "fmt" + "time" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/runtime" @@ -25,6 +27,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // BackupSessionReconciler reconciles a BackupSession object @@ -57,12 +62,121 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro } log.V(1).Info("Found BackupConfiguration", "BackupConfiguration", backupConf) + var childJobs batchv1.JobList + if err := r.List(ctx, &childJobs, client.InNamespace(req.Namespace), client.MatchingFields{jobOwnerKey: backupSession.Spec.Ref.Name}); err != nil { + log.Error(err, "unable to list child jobs") + return ctrl.Result{}, err + } + + var activeJobs []*batchv1.Job + var successfulJobs []*batchv1.Job + var failedJobs []*batchv1.Job + + isJobFinished := func(job *batchv1.Job) (bool, batchv1.JobConditionType) { + for _, c := range job.Status.Conditions { + if (c.Type == batchv1.JobComplete || c.Type == batchv1.JobFailed) && c.Status == corev1.ConditionTrue { + return true, c.Type + } + } + return false, "" + } + + for i, job := range childJobs.Items { + _, finishedType := isJobFinished(&job) + switch finishedType { + case "": // running + activeJobs = append(activeJobs, &childJobs.Items[i]) + case batchv1.JobFailed: + failedJobs = append(failedJobs, &childJobs.Items[i]) + case batchv1.JobComplete: + successfulJobs = append(successfulJobs, &childJobs.Items[i]) + } + } + + if len(activeJobs) > 0 { + log.V(0).Info("A backup job is already running. Skipping") + return ctrl.Result{}, nil + } + + constructJobForBackupConfiguration := func(backupConf formolv1alpha1.BackupConfiguration) (*batchv1.Job, error) { + name := fmt.Sprintf("%s-%d", backupConf.Name, time.Now().Unix()) + log.V(1).Info("constructing a new Job", "name", name) + task := &formolv1alpha1.Task{} + if err := r.Get(ctx, client.ObjectKey{ + Namespace: "backup", + Name: backupConf.Spec.Task, + }, task); err != nil { + log.Error(err, "unable to get Task from BackupConfiguration") + return nil, err + } + log.V(1).Info("found task", "task", task.Name) + containers := []corev1.Container{} + for _, step := range task.Spec.Steps { + function := &formolv1alpha1.Function{} + if err := r.Get(ctx, client.ObjectKey{ + Namespace: "backup", + Name: step.Name, + }, function); err != nil { + log.Error(err, "unable to get Function") + return nil, err + } + log.V(1).Info("found function", "function", function.Name) + containers = append(containers, *function.Spec.DeepCopy()) + } + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Labels: make(map[string]string), + Annotations: make(map[string]string), + Name: name, + Namespace: backupConf.Namespace, + }, + Spec: batchv1.JobSpec{ + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: containers, + RestartPolicy: corev1.RestartPolicyOnFailure, + }, + }, + }, + } + return job, nil + + } + job, err := constructJobForBackupConfiguration(backupConf) + if err != nil { + log.Error(err, "unable to construct job") + return ctrl.Result{}, nil + } + + if err := r.Create(ctx, job); err != nil { + log.Error(err, "unable to create job") + return ctrl.Result{}, err + } return ctrl.Result{}, nil } +var ( + jobOwnerKey = ".metadata.controller" + apiGVStr = formolv1alpha1.GroupVersion.String() +) + func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { + if err := mgr.GetFieldIndexer().IndexField(&batchv1.Job{}, jobOwnerKey, func(rawObj runtime.Object) []string { + job := rawObj.(*batchv1.Job) + owner := metav1.GetControllerOf(job) + if owner == nil { + return nil + } + if owner.APIVersion != apiGVStr || owner.Kind != "BackupSession" { + return nil + } + return []string{owner.Name} + }); err != nil { + return err + } return ctrl.NewControllerManagedBy(mgr). For(&formolv1alpha1.BackupSession{}). + Owns(&batchv1.Job{}). Complete(r) }