Compare commits

..

No commits in common. "477022347d6e35b44aa71cdbf5cd4f7e48441391" and "393465300a25a19ce3e8ae366fe63160926559ce" have entirely different histories.

31 changed files with 1126 additions and 1610 deletions

1
.gitignore vendored
View File

@ -20,7 +20,6 @@ PROJECT
bin/ bin/
config/* config/*
!config/crd !config/crd
!config/default
config/crd/bases config/crd/bases
!config/samples !config/samples
test/restic test/restic

View File

@ -2,8 +2,7 @@
# Image URL to use all building/pushing image targets # Image URL to use all building/pushing image targets
IMG ?= desmo999r/formolcontroller:latest IMG ?= desmo999r/formolcontroller:latest
# Produce CRDs that work back to Kubernetes 1.11 (no version conversion) # Produce CRDs that work back to Kubernetes 1.11 (no version conversion)
#CRD_OPTIONS ?= "crd:trivialVersions=true" CRD_OPTIONS ?= "crd:trivialVersions=true"
CRD_OPTIONS ?= "crd:trivialVersions=true,crdVersions=v1"
# Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set)
ifeq (,$(shell go env GOBIN)) ifeq (,$(shell go env GOBIN))

View File

@ -21,18 +21,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
const (
SidecarKind string = "Sidecar"
JobKind string = "Job"
BackupVolumes string = "Volumes"
)
type Step struct { type Step struct {
Name string `json:"name"` Name string `json:"name"`
// +optional Namespace string `json:"namespace"`
Env []corev1.EnvVar `json:"env,omitempty"` Env []corev1.EnvVar `json:"env"`
// +optional
Finalize *bool `json:"finalize,omitempty"`
} }
type Hook struct { type Hook struct {
@ -42,10 +34,14 @@ type Hook struct {
} }
type Target struct { type Target struct {
// +kubebuilder:validation:Enum=Sidecar;Job // +kubebuilder:validation:Enum=Deployment;Task
Kind string `json:"kind"` Kind string `json:"kind"`
Name string `json:"name"` Name string `json:"name"`
// +optional // +optional
BeforeBackup []Hook `json:"beforeBackup,omitempty"`
// +optional
AfterBackup []Hook `json:"afterBackup,omitempty"`
// +optional
ApiVersion string `json:"apiVersion,omitempty"` ApiVersion string `json:"apiVersion,omitempty"`
// +optional // +optional
VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"` VolumeMounts []corev1.VolumeMount `json:"volumeMounts,omitempty"`
@ -54,8 +50,6 @@ type Target struct {
// +optional // +optional
// +kubebuilder:validation:MinItems=1 // +kubebuilder:validation:MinItems=1
Steps []Step `json:"steps,omitempty"` Steps []Step `json:"steps,omitempty"`
// +kubebuilder:default:=2
Retry int `json:"retry,omitempty"`
} }
type Keep struct { type Keep struct {

View File

@ -17,16 +17,23 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
) )
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! // 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. // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
type Ref struct {
Name string `json:"name"`
}
// BackupSessionSpec defines the desired state of BackupSession // BackupSessionSpec defines the desired state of BackupSession
type BackupSessionSpec struct { type BackupSessionSpec struct {
Ref corev1.ObjectReference `json:"ref"` // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of BackupSession. Edit BackupSession_types.go to remove/update
Ref `json:"ref"`
} }
// BackupSessionStatus defines the observed state of BackupSession // BackupSessionStatus defines the observed state of BackupSession
@ -34,6 +41,8 @@ type BackupSessionStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file // Important: Run "make" to regenerate code after modifying this file
// +optional // +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
SessionState `json:"state,omitempty"` SessionState `json:"state,omitempty"`
// +optional // +optional
StartTime *metav1.Time `json:"startTime,omitempty"` StartTime *metav1.Time `json:"startTime,omitempty"`

View File

@ -8,25 +8,13 @@ type SessionState string
const ( const (
New SessionState = "New" New SessionState = "New"
Init SessionState = "Initializing"
Running SessionState = "Running" Running SessionState = "Running"
Waiting SessionState = "Waiting"
Finalize SessionState = "Finalizing"
Success SessionState = "Success" Success SessionState = "Success"
Failure SessionState = "Failure" Failure SessionState = "Failure"
Deleted SessionState = "Deleted" Deleted SessionState = "Deleted"
// Environment variables used by the sidecar container
RESTORE_ANNOTATION = "restore"
// the name of the sidecar container
SIDECARCONTAINER_NAME string = "formol"
// Used by both the backupsession and restoresession controllers to identified the target deployment
TARGET_NAME string = "TARGET_NAME" TARGET_NAME string = "TARGET_NAME"
// Used by restoresession controller
RESTORESESSION_NAMESPACE string = "RESTORESESSION_NAMESPACE" RESTORESESSION_NAMESPACE string = "RESTORESESSION_NAMESPACE"
RESTORESESSION_NAME string = "RESTORESESSION_NAME" RESTORESESSION_NAME string = "RESTORESESSION_NAME"
// Used by the backupsession controller
POD_NAME string = "POD_NAME"
POD_NAMESPACE string = "POD_NAMESPACE"
) )
type TargetStatus struct { type TargetStatus struct {
@ -40,6 +28,4 @@ type TargetStatus struct {
StartTime *metav1.Time `json:"startTime,omitempty"` StartTime *metav1.Time `json:"startTime,omitempty"`
// +optional // +optional
Duration *metav1.Duration `json:"duration,omitempty"` Duration *metav1.Duration `json:"duration,omitempty"`
// +optional
Try int `json:"try,omitemmpty"`
} }

View File

@ -1,76 +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 (
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/webhook"
)
// log is for logging in this package.
var functionlog = logf.Log.WithName("function-resource")
func (r *Function) SetupWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).
For(r).
Complete()
}
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// +kubebuilder:webhook:path=/mutate-formol-desmojim-fr-v1alpha1-function,mutating=true,failurePolicy=fail,groups=formol.desmojim.fr,resources=functions,verbs=create;update,versions=v1alpha1,name=mfunction.kb.io
var _ webhook.Defaulter = &Function{}
// Default implements webhook.Defaulter so a webhook will be registered for the type
func (r *Function) Default() {
functionlog.Info("default", "name", r.Name)
// TODO(user): fill in your defaulting logic.
r.Spec.Name = r.Name
}
// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation.
// +kubebuilder:webhook:verbs=create;update,path=/validate-formol-desmojim-fr-v1alpha1-function,mutating=false,failurePolicy=fail,groups=formol.desmojim.fr,resources=functions,versions=v1alpha1,name=vfunction.kb.io
var _ webhook.Validator = &Function{}
// ValidateCreate implements webhook.Validator so a webhook will be registered for the type
func (r *Function) ValidateCreate() error {
functionlog.Info("validate create", "name", r.Name)
// TODO(user): fill in your validation logic upon object creation.
return nil
}
// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type
func (r *Function) ValidateUpdate(old runtime.Object) error {
functionlog.Info("validate update", "name", r.Name)
// TODO(user): fill in your validation logic upon object update.
return nil
}
// ValidateDelete implements webhook.Validator so a webhook will be registered for the type
func (r *Function) ValidateDelete() error {
functionlog.Info("validate delete", "name", r.Name)
// TODO(user): fill in your validation logic upon object deletion.
return nil
}

View File

@ -17,30 +17,25 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
//"k8s.io/apimachinery/pkg/types"
) )
type BackupSessionRef struct { // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// +optional // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
Ref corev1.ObjectReference `json:"ref,omitempty"`
// +optional
Spec BackupSessionSpec `json:"spec,omitempty"`
// +optional
Status BackupSessionStatus `json:"status,omitempty"`
}
// RestoreSessionSpec defines the desired state of RestoreSession // RestoreSessionSpec defines the desired state of RestoreSession
type RestoreSessionSpec struct { type RestoreSessionSpec struct {
BackupSessionRef `json:"backupSession"` // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
//Ref string `json:"backupSessionRef"` // Important: Run "make" to regenerate code after modifying this file
// +optional
//Targets []TargetStatus `json:"target,omitempty"` BackupSessionRef metav1.ObjectMeta `json:"backupSessionRef"`
} }
// RestoreSessionStatus defines the observed state of RestoreSession // RestoreSessionStatus defines the observed state of RestoreSession
type RestoreSessionStatus struct { type RestoreSessionStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional // +optional
SessionState `json:"state,omitempty"` SessionState `json:"state,omitempty"`
// +optional // +optional

View File

@ -207,24 +207,6 @@ func (in *BackupSessionList) DeepCopyObject() runtime.Object {
return nil return nil
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackupSessionRef) DeepCopyInto(out *BackupSessionRef) {
*out = *in
out.Ref = in.Ref
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackupSessionRef.
func (in *BackupSessionRef) DeepCopy() *BackupSessionRef {
if in == nil {
return nil
}
out := new(BackupSessionRef)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BackupSessionSpec) DeepCopyInto(out *BackupSessionSpec) { func (in *BackupSessionSpec) DeepCopyInto(out *BackupSessionSpec) {
*out = *in *out = *in
@ -360,6 +342,21 @@ func (in *Keep) DeepCopy() *Keep {
return 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. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Repo) DeepCopyInto(out *Repo) { func (in *Repo) DeepCopyInto(out *Repo) {
*out = *in *out = *in
@ -576,11 +573,6 @@ func (in *Step) DeepCopyInto(out *Step) {
(*in)[i].DeepCopyInto(&(*out)[i]) (*in)[i].DeepCopyInto(&(*out)[i])
} }
} }
if in.Finalize != nil {
in, out := &in.Finalize, &out.Finalize
*out = new(bool)
**out = **in
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Step. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Step.
@ -596,6 +588,20 @@ func (in *Step) DeepCopy() *Step {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Target) DeepCopyInto(out *Target) { func (in *Target) DeepCopyInto(out *Target) {
*out = *in *out = *in
if in.BeforeBackup != nil {
in, out := &in.BeforeBackup, &out.BeforeBackup
*out = make([]Hook, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.AfterBackup != nil {
in, out := &in.AfterBackup, &out.AfterBackup
*out = make([]Hook, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.VolumeMounts != nil { if in.VolumeMounts != nil {
in, out := &in.VolumeMounts, &out.VolumeMounts in, out := &in.VolumeMounts, &out.VolumeMounts
*out = make([]v1.VolumeMount, len(*in)) *out = make([]v1.VolumeMount, len(*in))

View File

@ -15,16 +15,16 @@ patchesStrategicMerge:
#- patches/webhook_in_tasks.yaml #- patches/webhook_in_tasks.yaml
#- patches/webhook_in_functions.yaml #- patches/webhook_in_functions.yaml
#- patches/webhook_in_backupconfigurations.yaml #- patches/webhook_in_backupconfigurations.yaml
#- patches/webhook_in_backupsessions.yaml - patches/webhook_in_backupsessions.yaml
#- patches/webhook_in_repoes.yaml #- patches/webhook_in_repoes.yaml
#- patches/webhook_in_restoresessions.yaml #- patches/webhook_in_restoresessions.yaml
# +kubebuilder:scaffold:crdkustomizewebhookpatch # +kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix. # [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
# patches here are for enabling the CA injection for each CRD # patches here are for enabling the CA injection for each CRD
#- patches/cainjection_in_functions.yaml - patches/cainjection_in_functions.yaml
#- patches/cainjection_in_backupconfigurations.yaml - patches/cainjection_in_backupconfigurations.yaml
#- patches/cainjection_in_backupsessions.yaml - patches/cainjection_in_backupsessions.yaml
#- patches/cainjection_in_repoes.yaml #- patches/cainjection_in_repoes.yaml
#- patches/cainjection_in_restoresessions.yaml #- patches/cainjection_in_restoresessions.yaml
# +kubebuilder:scaffold:crdkustomizecainjectionpatch # +kubebuilder:scaffold:crdkustomizecainjectionpatch

View File

@ -1,6 +1,6 @@
# The following patch adds a directive for certmanager to inject CA into the CRD # The following patch adds a directive for certmanager to inject CA into the CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:

View File

@ -1,6 +1,6 @@
# The following patch adds a directive for certmanager to inject CA into the CRD # The following patch adds a directive for certmanager to inject CA into the CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:

View File

@ -1,6 +1,6 @@
# The following patch adds a directive for certmanager to inject CA into the CRD # The following patch adds a directive for certmanager to inject CA into the CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:

View File

@ -1,6 +1,6 @@
# The following patch adds a directive for certmanager to inject CA into the CRD # The following patch adds a directive for certmanager to inject CA into the CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
annotations: annotations:

View File

@ -1,16 +1,13 @@
# The following patch enables conversion webhook for CRD # The following patch enables conversion webhook for CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
name: backupconfigurations.formol.desmojim.fr name: backupconfigurations.formol.desmojim.fr
spec: spec:
preserveUnknownFields: false
conversion: conversion:
strategy: Webhook strategy: Webhook
webhook: webhookClientConfig:
conversionReviewVersions: ["v1", "v1beta1", "v1alpha1"]
clientConfig:
# this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank, # 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) # but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
caBundle: Cg== caBundle: Cg==

View File

@ -1,6 +1,6 @@
# The following patch enables conversion webhook for CRD # The following patch enables conversion webhook for CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
name: backupsessions.formol.desmojim.fr name: backupsessions.formol.desmojim.fr

View File

@ -1,6 +1,6 @@
# The following patch enables conversion webhook for CRD # The following patch enables conversion webhook for CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
name: functions.formol.desmojim.fr name: functions.formol.desmojim.fr

View File

@ -1,6 +1,6 @@
# The following patch enables conversion webhook for CRD # The following patch enables conversion webhook for CRD
# CRD conversion requires k8s 1.13 or later. # CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1 apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition kind: CustomResourceDefinition
metadata: metadata:
name: restoresessions.formol.desmojim.fr.desmojim.fr name: restoresessions.formol.desmojim.fr.desmojim.fr

View File

@ -18,9 +18,9 @@ bases:
- ../manager - ../manager
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml # crd/kustomization.yaml
#- ../webhook - ../webhook
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required. # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. 'WEBHOOK' components are required.
#- ../certmanager - ../certmanager
# [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'. # [PROMETHEUS] To enable prometheus monitor, uncomment all sections with 'PROMETHEUS'.
#- ../prometheus #- ../prometheus
@ -32,39 +32,39 @@ patchesStrategicMerge:
# [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in # [WEBHOOK] To enable webhook, uncomment all the sections with [WEBHOOK] prefix including the one in
# crd/kustomization.yaml # crd/kustomization.yaml
#- manager_webhook_patch.yaml - manager_webhook_patch.yaml
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'. # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER'.
# Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks. # Uncomment 'CERTMANAGER' sections in crd/kustomization.yaml to enable the CA injection in the admission webhooks.
# 'CERTMANAGER' needs to be enabled to use ca injection # 'CERTMANAGER' needs to be enabled to use ca injection
#- webhookcainjection_patch.yaml - webhookcainjection_patch.yaml
# the following config is for teaching kustomize how to do var substitution # the following config is for teaching kustomize how to do var substitution
vars: vars:
# [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix. # [CERTMANAGER] To enable cert-manager, uncomment all sections with 'CERTMANAGER' prefix.
#- name: CERTIFICATE_NAMESPACE # namespace of the certificate CR - name: CERTIFICATE_NAMESPACE # namespace of the certificate CR
# objref: objref:
# kind: Certificate kind: Certificate
# group: cert-manager.io group: cert-manager.io
# version: v1alpha2 version: v1alpha2
# name: serving-cert # this name should match the one in certificate.yaml name: serving-cert # this name should match the one in certificate.yaml
# fieldref: fieldref:
# fieldpath: metadata.namespace fieldpath: metadata.namespace
#- name: CERTIFICATE_NAME - name: CERTIFICATE_NAME
# objref: objref:
# kind: Certificate kind: Certificate
# group: cert-manager.io group: cert-manager.io
# version: v1alpha2 version: v1alpha2
# name: serving-cert # this name should match the one in certificate.yaml name: serving-cert # this name should match the one in certificate.yaml
#- name: SERVICE_NAMESPACE # namespace of the service - name: SERVICE_NAMESPACE # namespace of the service
# objref: objref:
# kind: Service kind: Service
# version: v1 version: v1
# name: webhook-service name: webhook-service
# fieldref: fieldref:
# fieldpath: metadata.namespace fieldpath: metadata.namespace
#- name: SERVICE_NAME - name: SERVICE_NAME
# objref: objref:
# kind: Service kind: Service
# version: v1 version: v1
# name: webhook-service name: webhook-service

View File

@ -18,7 +18,7 @@ package controllers
import ( import (
"context" "context"
//"time" "time"
formolrbac "github.com/desmo999r/formol/pkg/rbac" formolrbac "github.com/desmo999r/formol/pkg/rbac"
formolutils "github.com/desmo999r/formol/pkg/utils" formolutils "github.com/desmo999r/formol/pkg/utils"
@ -33,7 +33,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller" "sigs.k8s.io/controller-runtime/pkg/controller"
//"sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/predicate"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
) )
@ -45,6 +45,16 @@ type BackupConfigurationReconciler struct {
Scheme *runtime.Scheme Scheme *runtime.Scheme
} }
func (r *BackupConfigurationReconciler) getDeployment(namespace string, name string) (*appsv1.Deployment, error) {
deployment := &appsv1.Deployment{}
err := r.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: name,
}, deployment)
return deployment, err
}
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=*,verbs=* // +kubebuilder:rbac:groups=formol.desmojim.fr,resources=*,verbs=*
// +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=deployments,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=apps,resources=replicasets,verbs=get;list;watch;create;update;patch;delete
@ -57,29 +67,183 @@ type BackupConfigurationReconciler struct {
// +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=batch,resources=cronjobs,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=batch,resources=cronjobs/status,verbs=get // +kubebuilder:rbac:groups=batch,resources=cronjobs/status,verbs=get
func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { func (r *BackupConfigurationReconciler) deleteSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration, target formolv1alpha1.Target) error {
var changed bool deployment, err := r.getDeployment(backupConf.Namespace, target.Name)
ctx := context.Background() if err != nil {
log := r.Log.WithValues("backupconfiguration", req.NamespacedName) return err
//time.Sleep(300 * time.Millisecond) }
restorecontainers := []corev1.Container{}
log.V(1).Info("Enter Reconcile with req", "req", req, "reconciler", r) for _, container := range deployment.Spec.Template.Spec.Containers {
if container.Name == "backup" {
backupConf := &formolv1alpha1.BackupConfiguration{} continue
if err := r.Get(ctx, req.NamespacedName, backupConf); err != nil { }
return ctrl.Result{}, client.IgnoreNotFound(err) restorecontainers = append(restorecontainers, container)
}
deployment.Spec.Template.Spec.Containers = restorecontainers
if err := r.Update(context.Background(), deployment); err != nil {
return err
}
if err := formolrbac.DeleteFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
return err
}
selector, err := metav1.LabelSelectorAsMap(deployment.Spec.Selector)
if err != nil {
return nil
}
pods := &corev1.PodList{}
err = r.List(context.Background(), pods, client.MatchingLabels(selector))
if err != nil {
return nil
}
replicasToDelete := []appsv1.ReplicaSet{}
for _, pod := range pods.Items {
for _, podRef := range pod.OwnerReferences {
rs := &appsv1.ReplicaSet{}
if err := r.Get(context.Background(), client.ObjectKey{
Name: podRef.Name,
Namespace: pod.Namespace,
}, rs); err != nil {
return nil
}
for _, rsRef := range rs.OwnerReferences {
if rsRef.Kind == deployment.Kind && rsRef.Name == deployment.Name {
replicasToDelete = append(replicasToDelete, *rs)
}
}
}
} }
getDeployment := func(namespace string, name string) (*appsv1.Deployment, error) { for _, replica := range replicasToDelete {
deployment := &appsv1.Deployment{} if err := r.Delete(context.TODO(), &replica); err != nil {
err := r.Get(context.Background(), client.ObjectKey{ return nil
Namespace: namespace, }
Name: name, }
}, deployment) return nil
return deployment, err }
func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1alpha1.BackupConfiguration, target formolv1alpha1.Target) error {
log := r.Log.WithValues("backupconf", backupConf.Name)
deployment, err := r.getDeployment(backupConf.Namespace, target.Name)
if err != nil {
log.Error(err, "unable to get Deployment")
return err
}
log.V(1).Info("got deployment", "Deployment", deployment)
for _, container := range deployment.Spec.Template.Spec.Containers {
if container.Name == "backup" {
log.V(0).Info("There is already a backup sidecar container. Skipping", "container", container)
return nil
}
}
sidecar := corev1.Container{
Name: "backup",
Image: "desmo999r/formolcli:latest",
Args: []string{"backupsession", "server"},
//Image: "busybox",
//Command: []string{
// "sh",
// "-c",
// "sleep 3600; echo done",
//},
Env: []corev1.EnvVar{
corev1.EnvVar{
Name: "POD_NAME",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
},
corev1.EnvVar{
Name: "POD_NAMESPACE",
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
corev1.EnvVar{
Name: "POD_DEPLOYMENT",
Value: target.Name,
},
},
VolumeMounts: []corev1.VolumeMount{},
} }
deleteCronJob := func() error { // Gather information from the repo
repo := &formolv1alpha1.Repo{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: backupConf.Namespace,
Name: backupConf.Spec.Repository,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
sidecar.Env = append(sidecar.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
for _, volumemount := range target.VolumeMounts {
log.V(1).Info("mounts", "volumemount", volumemount)
volumemount.ReadOnly = true
sidecar.VolumeMounts = append(sidecar.VolumeMounts, *volumemount.DeepCopy())
}
selector, err := metav1.LabelSelectorAsMap(deployment.Spec.Selector)
if err != nil {
log.Error(err, "unable to get LableSelector for deployment", "label", deployment.Spec.Selector)
return nil
}
log.V(1).Info("getting pods matching label", "label", selector)
pods := &corev1.PodList{}
err = r.List(context.Background(), pods, client.InNamespace(backupConf.Namespace), client.MatchingLabels(selector))
if err != nil {
log.Error(err, "unable to get deployment pods")
return nil
}
replicasToDelete := []appsv1.ReplicaSet{}
log.V(1).Info("got that list of pods", "pods", len(pods.Items))
for _, pod := range pods.Items {
log.V(1).Info("checking pod", "pod", pod)
for _, podRef := range pod.OwnerReferences {
rs := &appsv1.ReplicaSet{}
if err := r.Get(context.Background(), client.ObjectKey{
Name: podRef.Name,
Namespace: pod.Namespace,
}, rs); err != nil {
log.Error(err, "unable to get replicaset", "replicaset", podRef.Name)
return nil
}
log.V(1).Info("got a replicaset", "rs", rs.Name)
for _, rsRef := range rs.OwnerReferences {
if rsRef.Kind == deployment.Kind && rsRef.Name == deployment.Name {
log.V(0).Info("Adding pod to the list of pods to be restarted", "pod", pod.Name)
replicasToDelete = append(replicasToDelete, *rs)
}
}
}
}
deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar)
deployment.Spec.Template.Spec.ShareProcessNamespace = func() *bool { b := true; return &b }()
if err := formolrbac.CreateFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
log.Error(err, "unable to create backupsessionlistener RBAC")
return nil
}
log.V(0).Info("Adding a sicar container")
if err := r.Update(context.Background(), deployment); err != nil {
log.Error(err, "unable to update the Deployment")
return err
}
for _, replica := range replicasToDelete {
if err := r.Delete(context.TODO(), &replica); err != nil {
log.Error(err, "unable to delete replica", "replica", replica.Name)
return nil
}
}
return nil
}
func (r *BackupConfigurationReconciler) deleteCronJob(backupConf *formolv1alpha1.BackupConfiguration) error {
log := r.Log.WithValues("deleteCronJob", backupConf.Name)
_ = formolrbac.DeleteFormolRBAC(r.Client, "default", backupConf.Namespace) _ = formolrbac.DeleteFormolRBAC(r.Client, "default", backupConf.Namespace)
_ = formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace) _ = formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace)
cronjob := &kbatch_beta1.CronJob{} cronjob := &kbatch_beta1.CronJob{}
@ -92,9 +256,10 @@ func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result
} else { } else {
return err return err
} }
} }
func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.BackupConfiguration) error {
log := r.Log.WithValues("addCronJob", backupConf.Name)
addCronJob := func() error {
if err := formolrbac.CreateFormolRBAC(r.Client, "default", backupConf.Namespace); err != nil { if err := formolrbac.CreateFormolRBAC(r.Client, "default", backupConf.Namespace); err != nil {
log.Error(err, "unable to create backupsessionlistener RBAC") log.Error(err, "unable to create backupsessionlistener RBAC")
return nil return nil
@ -111,12 +276,13 @@ func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result
Name: "backup-" + backupConf.Name, Name: "backup-" + backupConf.Name,
}, cronjob); err == nil { }, cronjob); err == nil {
log.V(0).Info("there is already a cronjob") log.V(0).Info("there is already a cronjob")
var changed bool
if backupConf.Spec.Schedule != cronjob.Spec.Schedule { if backupConf.Spec.Schedule != cronjob.Spec.Schedule {
log.V(0).Info("cronjob schedule has changed", "old schedule", cronjob.Spec.Schedule, "new schedule", backupConf.Spec.Schedule) log.V(0).Info("cronjob schedule has changed", "old schedule", cronjob.Spec.Schedule, "new schedule", backupConf.Spec.Schedule)
cronjob.Spec.Schedule = backupConf.Spec.Schedule cronjob.Spec.Schedule = backupConf.Spec.Schedule
changed = true changed = true
} }
if backupConf.Spec.Suspend != nil && backupConf.Spec.Suspend != cronjob.Spec.Suspend { if backupConf.Spec.Suspend != cronjob.Spec.Suspend {
log.V(0).Info("cronjob suspend has changed", "before", cronjob.Spec.Suspend, "new", backupConf.Spec.Suspend) log.V(0).Info("cronjob suspend has changed", "before", cronjob.Spec.Suspend, "new", backupConf.Spec.Suspend)
cronjob.Spec.Suspend = backupConf.Spec.Suspend cronjob.Spec.Suspend = backupConf.Spec.Suspend
changed = true changed = true
@ -175,190 +341,92 @@ func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result
if err := r.Create(context.Background(), cronjob); err != nil { if err := r.Create(context.Background(), cronjob); err != nil {
log.Error(err, "unable to create the cronjob", "cronjob", cronjob) log.Error(err, "unable to create the cronjob", "cronjob", cronjob)
return err return err
} else {
changed = true
return nil
}
}
deleteSidecarContainer := func(target formolv1alpha1.Target) error {
deployment, err := getDeployment(backupConf.Namespace, target.Name)
if err != nil {
return err
}
restorecontainers := []corev1.Container{}
for _, container := range deployment.Spec.Template.Spec.Containers {
if container.Name == formolv1alpha1.SIDECARCONTAINER_NAME {
continue
}
restorecontainers = append(restorecontainers, container)
}
deployment.Spec.Template.Spec.Containers = restorecontainers
if err := r.Update(context.Background(), deployment); err != nil {
return err
}
if err := formolrbac.DeleteFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
return err
} }
return nil return nil
} }
addSidecarContainer := func(target formolv1alpha1.Target) error { func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
deployment, err := getDeployment(backupConf.Namespace, target.Name) ctx := context.Background()
if err != nil { log := r.Log.WithValues("backupconfiguration", req.NamespacedName)
log.Error(err, "unable to get Deployment") time.Sleep(300 * time.Millisecond)
return err
}
log.V(1).Info("got deployment", "Deployment", deployment)
for _, container := range deployment.Spec.Template.Spec.Containers {
if container.Name == formolv1alpha1.SIDECARCONTAINER_NAME {
log.V(0).Info("There is already a backup sidecar container. Skipping", "container", container)
return nil
}
}
sidecar := corev1.Container{
Name: formolv1alpha1.SIDECARCONTAINER_NAME,
// TODO: Put the image in the BackupConfiguration YAML file
Image: "desmo999r/formolcli:latest",
Args: []string{"backupsession", "server"},
//Image: "busybox",
//Command: []string{
// "sh",
// "-c",
// "sleep 3600; echo done",
//},
Env: []corev1.EnvVar{
corev1.EnvVar{
Name: formolv1alpha1.POD_NAME,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
},
corev1.EnvVar{
Name: formolv1alpha1.POD_NAMESPACE,
ValueFrom: &corev1.EnvVarSource{
FieldRef: &corev1.ObjectFieldSelector{
FieldPath: "metadata.namespace",
},
},
},
corev1.EnvVar{
Name: formolv1alpha1.TARGET_NAME,
Value: target.Name,
},
},
VolumeMounts: []corev1.VolumeMount{},
}
// Gather information from the repo log.V(1).Info("Enter Reconcile with req", "req", req)
repo := &formolv1alpha1.Repo{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: backupConf.Namespace,
Name: backupConf.Spec.Repository,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
sidecar.Env = append(sidecar.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
for _, volumemount := range target.VolumeMounts { backupConf := &formolv1alpha1.BackupConfiguration{}
log.V(1).Info("mounts", "volumemount", volumemount) if err := r.Get(ctx, req.NamespacedName, backupConf); err != nil {
volumemount.ReadOnly = true return ctrl.Result{}, client.IgnoreNotFound(err)
sidecar.VolumeMounts = append(sidecar.VolumeMounts, *volumemount.DeepCopy())
}
deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar)
deployment.Spec.Template.Spec.ShareProcessNamespace = func() *bool { b := true; return &b }()
if err := formolrbac.CreateFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
log.Error(err, "unable to create backupsessionlistener RBAC")
return nil
}
log.V(0).Info("Adding a sicar container")
if err := r.Update(context.Background(), deployment); err != nil {
log.Error(err, "unable to update the Deployment")
return err
} else {
changed = true
return nil
}
}
deleteExternalResources := func() error {
for _, target := range backupConf.Spec.Targets {
switch target.Kind {
case formolv1alpha1.SidecarKind:
_ = deleteSidecarContainer(target)
}
}
// TODO: remove the hardcoded "default"
_ = deleteCronJob()
return nil
} }
finalizerName := "finalizer.backupconfiguration.formol.desmojim.fr" finalizerName := "finalizer.backupconfiguration.formol.desmojim.fr"
if !backupConf.ObjectMeta.DeletionTimestamp.IsZero() { if backupConf.ObjectMeta.DeletionTimestamp.IsZero() {
if !formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) {
backupConf.ObjectMeta.Finalizers = append(backupConf.ObjectMeta.Finalizers, finalizerName)
if err := r.Update(context.Background(), backupConf); err != nil {
log.Error(err, "unable to append finalizer")
return ctrl.Result{}, err
}
}
} else {
log.V(0).Info("backupconf being deleted", "backupconf", backupConf.Name) log.V(0).Info("backupconf being deleted", "backupconf", backupConf.Name)
if formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) { if formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) {
_ = deleteExternalResources() _ = r.deleteExternalResources(backupConf)
}
backupConf.ObjectMeta.Finalizers = formolutils.RemoveString(backupConf.ObjectMeta.Finalizers, finalizerName) backupConf.ObjectMeta.Finalizers = formolutils.RemoveString(backupConf.ObjectMeta.Finalizers, finalizerName)
if err := r.Update(context.Background(), backupConf); err != nil { if err := r.Update(context.Background(), backupConf); err != nil {
log.Error(err, "unable to remove finalizer") log.Error(err, "unable to remove finalizer")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
}
// We have been deleted. Return here // We have been deleted. Return here
log.V(0).Info("backupconf deleted", "backupconf", backupConf.Name) log.V(0).Info("backupconf deleted", "backupconf", backupConf.Name)
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
// Add finalizer if err := r.addCronJob(backupConf); err != nil {
if !formolutils.ContainsString(backupConf.ObjectMeta.Finalizers, finalizerName) {
backupConf.ObjectMeta.Finalizers = append(backupConf.ObjectMeta.Finalizers, finalizerName)
err := r.Update(context.Background(), backupConf)
if err != nil {
log.Error(err, "unable to append finalizer")
}
return ctrl.Result{}, err
}
if err := addCronJob(); err != nil {
return ctrl.Result{}, nil return ctrl.Result{}, nil
} else {
backupConf.Status.ActiveCronJob = true
} }
backupConf.Status.ActiveCronJob = true
for _, target := range backupConf.Spec.Targets { for _, target := range backupConf.Spec.Targets {
switch target.Kind { switch target.Kind {
case formolv1alpha1.SidecarKind: case "Deployment":
if err := addSidecarContainer(target); err != nil { if err := r.addSidecarContainer(backupConf, target); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err) return ctrl.Result{}, client.IgnoreNotFound(err)
} else {
backupConf.Status.ActiveSidecar = true
} }
backupConf.Status.ActiveSidecar = true
case "PersistentVolumeClaim":
log.V(0).Info("TODO backup PVC")
return ctrl.Result{}, nil
} }
} }
//backupConf.Status.Suspended = false backupConf.Status.Suspended = false
if changed == true {
log.V(1).Info("updating backupconf") log.V(1).Info("updating backupconf")
if err := r.Status().Update(ctx, backupConf); err != nil { if err := r.Status().Update(ctx, backupConf); err != nil {
log.Error(err, "unable to update backupconf", "backupconf", backupConf) log.Error(err, "unable to update backupconf", "backupconf", backupConf)
return ctrl.Result{}, err return ctrl.Result{}, err
} }
}
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
func (r *BackupConfigurationReconciler) deleteExternalResources(backupConf *formolv1alpha1.BackupConfiguration) error {
for _, target := range backupConf.Spec.Targets {
switch target.Kind {
case "Deployment":
_ = r.deleteSidecarContainer(backupConf, target)
}
}
// TODO: remove the hardcoded "default"
_ = r.deleteCronJob(backupConf)
return nil
}
func (r *BackupConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *BackupConfigurationReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr). return ctrl.NewControllerManagedBy(mgr).
For(&formolv1alpha1.BackupConfiguration{}). For(&formolv1alpha1.BackupConfiguration{}).
WithOptions(controller.Options{MaxConcurrentReconciles: 3}). WithOptions(controller.Options{MaxConcurrentReconciles: 3}).
//WithEventFilter(predicate.GenerationChangedPredicate{}). // Don't reconcile when status gets updated WithEventFilter(predicate.GenerationChangedPredicate{}). // Don't reconcile when status gets updated
//Owns(&formolv1alpha1.BackupSession{}). //Owns(&formolv1alpha1.BackupSession{}).
Owns(&kbatch_beta1.CronJob{}). Owns(&kbatch_beta1.CronJob{}).
Complete(r) Complete(r)

View File

@ -5,7 +5,7 @@ import (
//"k8s.io/apimachinery/pkg/types" //"k8s.io/apimachinery/pkg/types"
//"reflect" //"reflect"
//"fmt" //"fmt"
"time" //"time"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -21,11 +21,11 @@ import (
var _ = Describe("Testing BackupConf controller", func() { var _ = Describe("Testing BackupConf controller", func() {
const ( const (
BCBackupConfName = "test-backupconf-controller" BackupConfName = "test-backupconf"
) )
var ( var (
key = types.NamespacedName{ key = types.NamespacedName{
Name: BCBackupConfName, Name: BackupConfName,
Namespace: TestNamespace, Namespace: TestNamespace,
} }
ctx = context.Background() ctx = context.Background()
@ -35,32 +35,24 @@ var _ = Describe("Testing BackupConf controller", func() {
BeforeEach(func() { BeforeEach(func() {
backupConf = &formolv1alpha1.BackupConfiguration{ backupConf = &formolv1alpha1.BackupConfiguration{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: BCBackupConfName, Name: BackupConfName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, },
Spec: formolv1alpha1.BackupConfigurationSpec{ Spec: formolv1alpha1.BackupConfigurationSpec{
Repository: TestRepoName, Repository: RepoName,
Schedule: "1 * * * *", Schedule: "1 * * * *",
Targets: []formolv1alpha1.Target{ Targets: []formolv1alpha1.Target{
formolv1alpha1.Target{ formolv1alpha1.Target{
Kind: formolv1alpha1.SidecarKind, Kind: "Deployment",
Name: TestDeploymentName, Name: DeploymentName,
VolumeMounts: []corev1.VolumeMount{
corev1.VolumeMount{
Name: TestDataVolume,
MountPath: TestDataMountPath,
},
},
Paths: []string{
TestDataMountPath,
},
}, },
formolv1alpha1.Target{ formolv1alpha1.Target{
Kind: formolv1alpha1.JobKind, Kind: "Task",
Name: TestBackupFuncName, Name: BackupFuncName,
Steps: []formolv1alpha1.Step{ Steps: []formolv1alpha1.Step{
formolv1alpha1.Step{ formolv1alpha1.Step{
Name: TestBackupFuncName, Name: BackupFuncName,
Namespace: TestNamespace,
Env: []corev1.EnvVar{ Env: []corev1.EnvVar{
corev1.EnvVar{ corev1.EnvVar{
Name: "foo", Name: "foo",
@ -74,7 +66,7 @@ var _ = Describe("Testing BackupConf controller", func() {
}, },
} }
}) })
Context("Creating a backupconf", func() { Context("There is a backupconf", func() {
JustBeforeEach(func() { JustBeforeEach(func() {
Eventually(func() error { Eventually(func() error {
return k8sClient.Create(ctx, backupConf) return k8sClient.Create(ctx, backupConf)
@ -93,16 +85,18 @@ var _ = Describe("Testing BackupConf controller", func() {
return true return true
}, timeout, interval).Should(BeTrue()) }, timeout, interval).Should(BeTrue())
Expect(realBackupConf.Spec.Schedule).Should(Equal("1 * * * *")) Expect(realBackupConf.Spec.Schedule).Should(Equal("1 * * * *"))
Expect(realBackupConf.Spec.Targets[0].Retry).Should(Equal(2))
}) })
It("Should also create a CronJob", func() { It("Should also create a CronJob", func() {
cronJob := &batchv1beta1.CronJob{} cronJob := &batchv1beta1.CronJob{}
Eventually(func() bool { Eventually(func() bool {
err := k8sClient.Get(ctx, types.NamespacedName{ err := k8sClient.Get(ctx, types.NamespacedName{
Name: "backup-" + BCBackupConfName, Name: "backup-" + BackupConfName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, cronJob) }, cronJob)
return err == nil if err != nil {
return false
}
return true
}, timeout, interval).Should(BeTrue()) }, timeout, interval).Should(BeTrue())
Expect(cronJob.Spec.Schedule).Should(Equal("1 * * * *")) Expect(cronJob.Spec.Schedule).Should(Equal("1 * * * *"))
}) })
@ -110,7 +104,7 @@ var _ = Describe("Testing BackupConf controller", func() {
realDeployment := &appsv1.Deployment{} realDeployment := &appsv1.Deployment{}
Eventually(func() (int, error) { Eventually(func() (int, error) {
err := k8sClient.Get(ctx, types.NamespacedName{ err := k8sClient.Get(ctx, types.NamespacedName{
Name: TestDeploymentName, Name: DeploymentName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, realDeployment) }, realDeployment)
if err != nil { if err != nil {
@ -121,7 +115,6 @@ var _ = Describe("Testing BackupConf controller", func() {
}) })
It("Should also update the CronJob", func() { It("Should also update the CronJob", func() {
realBackupConf := &formolv1alpha1.BackupConfiguration{} realBackupConf := &formolv1alpha1.BackupConfiguration{}
time.Sleep(300 * time.Millisecond)
Eventually(func() bool { Eventually(func() bool {
err := k8sClient.Get(ctx, key, realBackupConf) err := k8sClient.Get(ctx, key, realBackupConf)
if err != nil { if err != nil {
@ -136,7 +129,7 @@ var _ = Describe("Testing BackupConf controller", func() {
cronJob := &batchv1beta1.CronJob{} cronJob := &batchv1beta1.CronJob{}
Eventually(func() (string, error) { Eventually(func() (string, error) {
err := k8sClient.Get(ctx, types.NamespacedName{ err := k8sClient.Get(ctx, types.NamespacedName{
Name: "backup-" + BCBackupConfName, Name: "backup-" + BackupConfName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, cronJob) }, cronJob)
if err != nil { if err != nil {
@ -146,7 +139,7 @@ var _ = Describe("Testing BackupConf controller", func() {
}, timeout, interval).Should(Equal("1 0 * * *")) }, timeout, interval).Should(Equal("1 0 * * *"))
Eventually(func() (bool, error) { Eventually(func() (bool, error) {
err := k8sClient.Get(ctx, types.NamespacedName{ err := k8sClient.Get(ctx, types.NamespacedName{
Name: "backup-" + BCBackupConfName, Name: "backup-" + BackupConfName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, cronJob) }, cronJob)
if err != nil { if err != nil {
@ -167,7 +160,7 @@ var _ = Describe("Testing BackupConf controller", func() {
realDeployment := &appsv1.Deployment{} realDeployment := &appsv1.Deployment{}
Eventually(func() (int, error) { Eventually(func() (int, error) {
err := k8sClient.Get(ctx, types.NamespacedName{ err := k8sClient.Get(ctx, types.NamespacedName{
Name: TestDeploymentName, Name: DeploymentName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, realDeployment) }, realDeployment)
if err != nil { if err != nil {

View File

@ -48,199 +48,36 @@ type BackupSessionReconciler struct {
client.Client client.Client
Log logr.Logger Log logr.Logger
Scheme *runtime.Scheme Scheme *runtime.Scheme
BackupSession *formolv1alpha1.BackupSession
BackupConf *formolv1alpha1.BackupConfiguration
} }
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions,verbs=get;list;watch;create;update;patch;delete func (r *BackupSessionReconciler) StatusUpdate() error {
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions/status,verbs=get;update;patch;create;delete log := r.Log.WithValues("backupsession-statusupdate", r.BackupSession)
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=functions,verbs=get;list;watch
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete;watch
func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("backupsession", req.NamespacedName)
ctx := context.Background() ctx := context.Background()
r.BackupConf = &formolv1alpha1.BackupConfiguration{}
backupSession := &formolv1alpha1.BackupSession{}
if err := r.Get(ctx, req.NamespacedName, backupSession); err != nil {
log.Error(err, "unable to get backupsession")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
backupConf := &formolv1alpha1.BackupConfiguration{}
if err := r.Get(ctx, client.ObjectKey{ if err := r.Get(ctx, client.ObjectKey{
Namespace: backupSession.Namespace, Namespace: r.BackupSession.Namespace,
Name: backupSession.Spec.Ref.Name, Name: r.BackupSession.Spec.Ref.Name}, r.BackupConf); err != nil {
}, backupConf); err != nil {
log.Error(err, "unable to get backupConfiguration") log.Error(err, "unable to get backupConfiguration")
return ctrl.Result{}, client.IgnoreNotFound(err) return client.IgnoreNotFound(err)
}
// helper functions
// is there a backup operation ongoing
isBackupOngoing := func() bool {
backupSessionList := &formolv1alpha1.BackupSessionList{}
if err := r.List(ctx, backupSessionList, client.InNamespace(backupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: "Running"})}); err != nil {
log.Error(err, "unable to get backupsessionlist")
return true
}
return len(backupSessionList.Items) > 0
}
// delete session specific backup resources
deleteExternalResources := func() error {
log := r.Log.WithValues("deleteExternalResources", backupSession.Name)
// Gather information from the repo
repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: backupConf.Namespace,
Name: backupConf.Spec.Repository,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
env := formolutils.ConfigureResticEnvVar(backupConf, repo)
// container that will delete the restic snapshot(s) matching the backupsession
deleteSnapshots := []corev1.Container{}
for _, target := range backupSession.Status.Targets {
if target.SessionState == formolv1alpha1.Success {
deleteSnapshots = append(deleteSnapshots, corev1.Container{
Name: target.Name,
Image: "desmo999r/formolcli:latest",
Args: []string{"snapshot", "delete", "--snapshot-id", target.SnapshotId},
Env: env,
})
}
}
// create a job to delete the restic snapshot(s) with the backupsession name tag
if len(deleteSnapshots) > 0 {
jobTtl := JOBTTL
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("delete-%s-", backupSession.Name),
Namespace: backupSession.Namespace,
},
Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &jobTtl,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{},
Containers: deleteSnapshots,
RestartPolicy: corev1.RestartPolicyOnFailure,
},
},
},
}
log.V(0).Info("creating a job to delete restic snapshots")
if err := r.Create(ctx, job); err != nil {
log.Error(err, "unable to delete job", "job", job)
return err
}
}
return nil
}
// create a backup job
createBackupJob := func(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createbackupjob", target.Name)
ctx := context.Background()
backupSessionEnv := []corev1.EnvVar{
corev1.EnvVar{
Name: "TARGET_NAME",
Value: target.Name,
},
corev1.EnvVar{
Name: "BACKUPSESSION_NAME",
Value: backupSession.Name,
},
corev1.EnvVar{
Name: "BACKUPSESSION_NAMESPACE",
Value: backupSession.Namespace,
},
}
output := corev1.VolumeMount{
Name: "output",
MountPath: "/output",
}
restic := corev1.Container{
Name: "restic",
Image: "desmo999r/formolcli:latest",
Args: []string{"volume", "backup", "--tag", backupSession.Name, "--path", "/output"},
VolumeMounts: []corev1.VolumeMount{output},
Env: backupSessionEnv,
}
log.V(1).Info("creating a tagged backup job", "container", restic)
// Gather information from the repo
repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: backupConf.Namespace,
Name: backupConf.Spec.Repository,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
// S3 backing storage
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...)
jobTtl := JOBTTL
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-%s-", backupSession.Name, target.Name),
Namespace: backupConf.Namespace,
},
Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &jobTtl,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{},
Containers: []corev1.Container{restic},
Volumes: []corev1.Volume{
corev1.Volume{Name: "output"},
},
RestartPolicy: corev1.RestartPolicyOnFailure,
},
},
},
}
for _, step := range target.Steps {
function := &formolv1alpha1.Function{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: backupConf.Namespace,
Name: step.Name,
}, function); err != nil {
log.Error(err, "unable to get function", "Function", step)
return err
}
function.Spec.Name = function.Name
function.Spec.Env = append(step.Env, backupSessionEnv...)
function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output)
job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec)
}
if err := ctrl.SetControllerReference(backupConf, job, r.Scheme); err != nil {
log.Error(err, "unable to set controller on job", "job", job, "backupconf", backupConf)
return err
}
log.V(0).Info("creating a backup job", "target", target)
if err := r.Create(ctx, job); err != nil {
log.Error(err, "unable to create job", "job", job)
return err
}
return nil
} }
// start the next task // start the next task
startNextTask := func() (*formolv1alpha1.TargetStatus, error) { startNextTask := func() (*formolv1alpha1.TargetStatus, error) {
nextTarget := len(backupSession.Status.Targets) nextTarget := len(r.BackupSession.Status.Targets)
if nextTarget < len(backupConf.Spec.Targets) { if nextTarget < len(r.BackupConf.Spec.Targets) {
target := backupConf.Spec.Targets[nextTarget] target := r.BackupConf.Spec.Targets[nextTarget]
targetStatus := formolv1alpha1.TargetStatus{ targetStatus := formolv1alpha1.TargetStatus{
Name: target.Name, Name: target.Name,
Kind: target.Kind, Kind: target.Kind,
SessionState: formolv1alpha1.New, SessionState: formolv1alpha1.New,
StartTime: &metav1.Time{Time: time.Now()}, StartTime: &metav1.Time{Time: time.Now()},
Try: 1,
} }
backupSession.Status.Targets = append(backupSession.Status.Targets, targetStatus) r.BackupSession.Status.Targets = append(r.BackupSession.Status.Targets, targetStatus)
switch target.Kind { switch target.Kind {
case formolv1alpha1.JobKind: case "Task":
if err := createBackupJob(target); err != nil { if err := r.CreateBackupJob(target); err != nil {
log.V(0).Info("unable to create task", "task", target) log.V(0).Info("unable to create task", "task", target)
targetStatus.SessionState = formolv1alpha1.Failure targetStatus.SessionState = formolv1alpha1.Failure
return nil, err return nil, err
@ -251,18 +88,56 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
return nil, nil return nil, nil
} }
} }
// Test the backupsession backupstate to decide what to do
// cleanup existing backupsessions switch r.BackupSession.Status.SessionState {
cleanupSessions := func() { case formolv1alpha1.New:
// Brand new backupsession; start the first task
r.BackupSession.Status.SessionState = formolv1alpha1.Running
targetStatus, err := startNextTask()
if err != nil {
return err
}
log.V(0).Info("New backup. Start the first task", "task", targetStatus)
if err := r.Status().Update(ctx, r.BackupSession); err != nil {
log.Error(err, "unable to update BackupSession status")
return err
}
case formolv1alpha1.Running:
// Backup ongoing. Check the status of the last task to decide what to do
currentTargetStatus := r.BackupSession.Status.Targets[len(r.BackupSession.Status.Targets)-1]
switch currentTargetStatus.SessionState {
case formolv1alpha1.Failure:
// The last task failed. We mark the backupsession as failed and we stop here.
log.V(0).Info("last backup task failed. Stop here", "targetStatus", currentTargetStatus)
r.BackupSession.Status.SessionState = formolv1alpha1.Failure
log.V(1).Info("New BackupSession status", "status", r.BackupSession.Status.SessionState)
if err := r.Status().Update(ctx, r.BackupSession); err != nil {
log.Error(err, "unable to update BackupSession status")
return err
}
case formolv1alpha1.Running:
// The current task is still running. Nothing to do
log.V(0).Info("task is still running", "targetStatus", currentTargetStatus)
case formolv1alpha1.Success:
// The last task successed. Let's try to start the next one
log.V(0).Info("last task was a success. start a new one", "currentTargetStatus", currentTargetStatus, "targetStatus", currentTargetStatus)
targetStatus, err := startNextTask()
if err != nil {
return err
}
if targetStatus == nil {
// No more task to start. The backup is a success
r.BackupSession.Status.SessionState = formolv1alpha1.Success
log.V(0).Info("Backup is successful. Let's try to do some cleanup")
backupSessionList := &formolv1alpha1.BackupSessionList{} backupSessionList := &formolv1alpha1.BackupSessionList{}
if err := r.List(ctx, backupSessionList, client.InNamespace(backupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: string(formolv1alpha1.Success)})}); err != nil { if err := r.List(ctx, backupSessionList, client.InNamespace(r.BackupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: "Success"})}); err != nil {
log.Error(err, "unable to get backupsessionlist") log.Error(err, "unable to get backupsessionlist")
return return nil
} }
if len(backupSessionList.Items) < 2 { if len(backupSessionList.Items) < 2 {
// Not enough backupSession to proceed // Not enough backupSession to proceed
log.V(1).Info("Not enough successful backup jobs") log.V(1).Info("Not enough successful backup jobs")
return break
} }
sort.Slice(backupSessionList.Items, func(i, j int) bool { sort.Slice(backupSessionList.Items, func(i, j int) bool {
@ -275,13 +150,13 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
} }
var lastBackups, dailyBackups, weeklyBackups, monthlyBackups, yearlyBackups KeepBackup var lastBackups, dailyBackups, weeklyBackups, monthlyBackups, yearlyBackups KeepBackup
lastBackups.Counter = backupConf.Spec.Keep.Last lastBackups.Counter = r.BackupConf.Spec.Keep.Last
dailyBackups.Counter = backupConf.Spec.Keep.Daily dailyBackups.Counter = r.BackupConf.Spec.Keep.Daily
weeklyBackups.Counter = backupConf.Spec.Keep.Weekly weeklyBackups.Counter = r.BackupConf.Spec.Keep.Weekly
monthlyBackups.Counter = backupConf.Spec.Keep.Monthly monthlyBackups.Counter = r.BackupConf.Spec.Keep.Monthly
yearlyBackups.Counter = backupConf.Spec.Keep.Yearly yearlyBackups.Counter = r.BackupConf.Spec.Keep.Yearly
for _, session := range backupSessionList.Items { for _, session := range backupSessionList.Items {
if session.Spec.Ref.Name != backupConf.Name { if session.Spec.Ref.Name != r.BackupConf.Name {
continue continue
} }
deleteSession := true deleteSession := true
@ -342,111 +217,103 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
} }
} }
} }
// end helper functions log.V(1).Info("New BackupSession status", "status", r.BackupSession.Status.SessionState)
if err := r.Status().Update(ctx, r.BackupSession); err != nil {
log.Error(err, "unable to update BackupSession status")
return err
}
}
case formolv1alpha1.Deleted:
for _, target := range r.BackupSession.Status.Targets {
if target.SessionState != formolv1alpha1.Deleted {
log.V(1).Info("snaphot has not been deleted. won't delete the backupsession", "target", target)
return nil
}
}
log.V(1).Info("all the snapshots have been deleted. deleting the backupsession")
controllerutil.RemoveFinalizer(r.BackupSession, finalizerName)
if err := r.Update(ctx, r.BackupSession); err != nil {
log.Error(err, "unable to remove finalizer")
return err
}
}
return nil
}
log.V(0).Info("backupSession", "backupSession.ObjectMeta", backupSession.ObjectMeta, "backupSession.Status", backupSession.Status) func (r *BackupSessionReconciler) IsBackupOngoing() bool {
if backupSession.ObjectMeta.DeletionTimestamp.IsZero() { log := r.Log.WithName("IsBackupOngoing")
switch backupSession.Status.SessionState { ctx := context.Background()
case formolv1alpha1.New:
backupSessionList := &formolv1alpha1.BackupSessionList{}
if err := r.List(ctx, backupSessionList, client.InNamespace(r.BackupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: "Running"})}); err != nil {
log.Error(err, "unable to get backupsessionlist")
return true
}
if len(backupSessionList.Items) > 0 {
return true
} else {
return false
}
}
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions/status,verbs=get;update;patch;create;delete
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=functions,verbs=get;list;watch
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete;watch
func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
log := r.Log.WithValues("backupsession", req.NamespacedName)
ctx := context.Background()
// your logic here
//time.Sleep(300 * time.Millisecond)
r.BackupSession = &formolv1alpha1.BackupSession{}
if err := r.Get(ctx, req.NamespacedName, r.BackupSession); err != nil {
log.Error(err, "unable to get backupsession")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.V(0).Info("backupSession", "backupSession.ObjectMeta", r.BackupSession.ObjectMeta, "backupSession.Status", r.BackupSession.Status)
if r.BackupSession.Status.ObservedGeneration == r.BackupSession.ObjectMeta.Generation {
// status update
log.V(0).Info("status update")
return ctrl.Result{}, r.StatusUpdate()
}
if r.IsBackupOngoing() {
// There is already a backup ongoing. We don't do anything and we reschedule
log.V(0).Info("there is an ongoing backup. let's reschedule this operation")
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil
} else if r.BackupSession.ObjectMeta.DeletionTimestamp.IsZero() {
// Check if the finalizer has been registered // Check if the finalizer has been registered
if !controllerutil.ContainsFinalizer(backupSession, finalizerName) { if !controllerutil.ContainsFinalizer(r.BackupSession, finalizerName) {
controllerutil.AddFinalizer(backupSession, finalizerName) controllerutil.AddFinalizer(r.BackupSession, finalizerName)
// We update the BackupSession to add the finalizer // We update the BackupSession to add the finalizer
// Reconcile will be called again // Reconcile will be called again
// return now // return now
err := r.Update(ctx, backupSession) err := r.Update(ctx, r.BackupSession)
if err != nil { if err != nil {
log.Error(err, "unable to add finalizer") log.Error(err, "unable to add finalizer")
} }
return ctrl.Result{}, err return ctrl.Result{}, err
} }
// Brand new backupsession // All signals are green
if isBackupOngoing() { // We start the backup process
log.V(0).Info("There is an ongoing backup. Let's reschedule this operation") r.BackupSession.Status.ObservedGeneration = r.BackupSession.ObjectMeta.Generation
return ctrl.Result{RequeueAfter: 30 * time.Second}, nil r.BackupSession.Status.SessionState = formolv1alpha1.New
} r.BackupSession.Status.StartTime = &metav1.Time{Time: time.Now()}
// start the first task if err := r.Status().Update(ctx, r.BackupSession); err != nil {
backupSession.Status.SessionState = formolv1alpha1.Running
targetStatus, err := startNextTask()
if err != nil {
return ctrl.Result{}, err
}
log.V(0).Info("New backup. Start the first task", "task", targetStatus)
if err := r.Status().Update(ctx, backupSession); err != nil {
log.Error(err, "unable to update BackupSession status")
return ctrl.Result{}, err
}
case formolv1alpha1.Running:
// Backup ongoing. Check the status of the last task to decide what to do
currentTargetStatus := &backupSession.Status.Targets[len(backupSession.Status.Targets)-1]
switch currentTargetStatus.SessionState {
case formolv1alpha1.Running:
// The current task is still running. Nothing to do
log.V(0).Info("task is still running", "targetStatus", currentTargetStatus)
case formolv1alpha1.Success:
// The last task succeed. Let's try to start the next one
targetStatus, err := startNextTask()
log.V(0).Info("last task was a success. start a new one", "currentTargetStatus", currentTargetStatus, "targetStatus", targetStatus)
if err != nil {
return ctrl.Result{}, err
}
if targetStatus == nil {
// No more task to start. The backup is a success
backupSession.Status.SessionState = formolv1alpha1.Success
log.V(0).Info("Backup is successful. Let's try to do some cleanup")
cleanupSessions()
}
if err := r.Status().Update(ctx, backupSession); err != nil {
log.Error(err, "unable to update BackupSession status")
return ctrl.Result{}, err
}
case formolv1alpha1.Failure:
// last task failed. Try to run it again
currentTarget := backupConf.Spec.Targets[len(backupSession.Status.Targets)-1]
if currentTargetStatus.Try < currentTarget.Retry {
log.V(0).Info("last task was a failure. try again", "currentTargetStatus", currentTargetStatus)
currentTargetStatus.Try++
currentTargetStatus.SessionState = formolv1alpha1.New
currentTargetStatus.StartTime = &metav1.Time{Time: time.Now()}
switch currentTarget.Kind {
case formolv1alpha1.JobKind:
if err := createBackupJob(currentTarget); err != nil {
log.V(0).Info("unable to create task", "task", currentTarget)
currentTargetStatus.SessionState = formolv1alpha1.Failure
return ctrl.Result{}, err
}
}
} else {
log.V(0).Info("task failed again and for the last time", "currentTargetStatus", currentTargetStatus)
backupSession.Status.SessionState = formolv1alpha1.Failure
}
if err := r.Status().Update(ctx, backupSession); err != nil {
log.Error(err, "unable to update BackupSession status")
return ctrl.Result{}, err
}
}
case formolv1alpha1.Success:
// Should never go there
case formolv1alpha1.Failure:
// The backup failed
case "":
// BackupSession has just been created
backupSession.Status.SessionState = formolv1alpha1.New
backupSession.Status.StartTime = &metav1.Time{Time: time.Now()}
if err := r.Status().Update(ctx, backupSession); err != nil {
log.Error(err, "unable to update backupSession") log.Error(err, "unable to update backupSession")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
}
} else { } else {
log.V(0).Info("backupsession being deleted", "backupsession", backupSession.Name) log.V(0).Info("backupsession being deleted", "backupsession", r.BackupSession.Name)
if controllerutil.ContainsFinalizer(backupSession, finalizerName) { if controllerutil.ContainsFinalizer(r.BackupSession, finalizerName) {
if err := deleteExternalResources(); err != nil { if err := r.deleteExternalResources(); err != nil {
return ctrl.Result{}, err return ctrl.Result{}, err
} }
} }
controllerutil.RemoveFinalizer(backupSession, finalizerName) controllerutil.RemoveFinalizer(r.BackupSession, finalizerName)
if err := r.Update(ctx, backupSession); err != nil { if err := r.Update(ctx, r.BackupSession); err != nil {
log.Error(err, "unable to remove finalizer") log.Error(err, "unable to remove finalizer")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
@ -456,6 +323,144 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
return ctrl.Result{}, nil return ctrl.Result{}, nil
} }
func (r *BackupSessionReconciler) CreateBackupJob(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createbackupjob", target.Name)
ctx := context.Background()
backupSessionEnv := []corev1.EnvVar{
corev1.EnvVar{
Name: "TARGET_NAME",
Value: target.Name,
},
corev1.EnvVar{
Name: "BACKUPSESSION_NAME",
Value: r.BackupSession.Name,
},
corev1.EnvVar{
Name: "BACKUPSESSION_NAMESPACE",
Value: r.BackupSession.Namespace,
},
}
output := corev1.VolumeMount{
Name: "output",
MountPath: "/output",
}
restic := corev1.Container{
Name: "restic",
Image: "desmo999r/formolcli:latest",
Args: []string{"volume", "backup", "--tag", r.BackupSession.Name, "--path", "/output"},
VolumeMounts: []corev1.VolumeMount{output},
Env: backupSessionEnv,
}
log.V(1).Info("creating a tagged backup job", "container", restic)
// Gather information from the repo
repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: r.BackupConf.Namespace,
Name: r.BackupConf.Spec.Repository,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
// S3 backing storage
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
jobTtl := JOBTTL
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-%s-", r.BackupSession.Name, target.Name),
Namespace: r.BackupConf.Namespace,
},
Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &jobTtl,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{},
Containers: []corev1.Container{restic},
Volumes: []corev1.Volume{
corev1.Volume{Name: "output"},
},
RestartPolicy: corev1.RestartPolicyOnFailure,
},
},
},
}
for _, step := range target.Steps {
function := &formolv1alpha1.Function{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: step.Namespace,
Name: step.Name}, function); err != nil {
log.Error(err, "unable to get function", "Function", step)
return err
}
function.Spec.Env = append(step.Env, backupSessionEnv...)
function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output)
job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec)
}
if err := ctrl.SetControllerReference(r.BackupConf, job, r.Scheme); err != nil {
log.Error(err, "unable to set controller on job", "job", job, "backupconf", r.BackupConf)
return err
}
log.V(0).Info("creating a backup job", "target", target)
if err := r.Create(ctx, job); err != nil {
log.Error(err, "unable to create job", "job", job)
return err
}
return nil
}
func (r *BackupSessionReconciler) deleteExternalResources() error {
ctx := context.Background()
log := r.Log.WithValues("deleteExternalResources", r.BackupSession.Name)
// Gather information from the repo
repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: r.BackupConf.Namespace,
Name: r.BackupConf.Spec.Repository,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
env := formolutils.ConfigureResticEnvVar(r.BackupConf, repo)
// container that will delete the restic snapshot(s) matching the backupsession
deleteSnapshots := []corev1.Container{}
for _, target := range r.BackupSession.Status.Targets {
if target.SessionState == formolv1alpha1.Success {
deleteSnapshots = append(deleteSnapshots, corev1.Container{
Name: target.Name,
Image: "desmo999r/formolcli:latest",
Args: []string{"snapshot", "delete", "--snapshot-id", target.SnapshotId},
Env: env,
})
}
}
// create a job to delete the restic snapshot(s) with the backupsession name tag
if len(deleteSnapshots) > 0 {
jobTtl := JOBTTL
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("delete-%s-", r.BackupSession.Name),
Namespace: r.BackupSession.Namespace,
},
Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &jobTtl,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{},
Containers: deleteSnapshots,
RestartPolicy: corev1.RestartPolicyOnFailure,
},
},
},
}
log.V(0).Info("creating a job to delete restic snapshots")
if err := r.Create(ctx, job); err != nil {
log.Error(err, "unable to delete job", "job", job)
return err
}
}
return nil
}
func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error {
if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &formolv1alpha1.BackupSession{}, sessionState, func(rawObj runtime.Object) []string { if err := mgr.GetFieldIndexer().IndexField(context.TODO(), &formolv1alpha1.BackupSession{}, sessionState, func(rawObj runtime.Object) []string {
session := rawObj.(*formolv1alpha1.BackupSession) session := rawObj.(*formolv1alpha1.BackupSession)

View File

@ -1,147 +0,0 @@
package controllers
import (
"context"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
//corev1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
var _ = Describe("Testing BackupSession controller", func() {
const (
BSBackupSessionName = "test-backupsession-controller"
)
var (
ctx = context.Background()
key = types.NamespacedName{
Name: BSBackupSessionName,
Namespace: TestNamespace,
}
backupSession = &formolv1alpha1.BackupSession{}
)
BeforeEach(func() {
backupSession = &formolv1alpha1.BackupSession{
ObjectMeta: metav1.ObjectMeta{
Name: BSBackupSessionName,
Namespace: TestNamespace,
},
Spec: formolv1alpha1.BackupSessionSpec{
Ref: corev1.ObjectReference{
Name: TestBackupConfName,
},
},
}
})
Context("Creating a backupsession", func() {
JustBeforeEach(func() {
Eventually(func() error {
return k8sClient.Create(ctx, backupSession)
}, timeout, interval).Should(Succeed())
realBackupSession := &formolv1alpha1.BackupSession{}
Eventually(func() error {
err := k8sClient.Get(ctx, key, realBackupSession)
return err
}, timeout, interval).Should(Succeed())
Eventually(func() formolv1alpha1.SessionState {
if err := k8sClient.Get(ctx, key, realBackupSession); err != nil {
return ""
} else {
return realBackupSession.Status.SessionState
}
}, timeout, interval).Should(Equal(formolv1alpha1.Running))
})
AfterEach(func() {
Expect(k8sClient.Delete(ctx, backupSession)).Should(Succeed())
})
It("Should have a new task", func() {
realBackupSession := &formolv1alpha1.BackupSession{}
_ = k8sClient.Get(ctx, key, realBackupSession)
Expect(realBackupSession.Status.Targets[0].Name).Should(Equal(TestDeploymentName))
Expect(realBackupSession.Status.Targets[0].SessionState).Should(Equal(formolv1alpha1.New))
Expect(realBackupSession.Status.Targets[0].Kind).Should(Equal(formolv1alpha1.SidecarKind))
Expect(realBackupSession.Status.Targets[0].Try).Should(Equal(1))
})
It("Should move to the next task when the first one is a success", func() {
realBackupSession := &formolv1alpha1.BackupSession{}
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
realBackupSession.Status.Targets[0].SessionState = formolv1alpha1.Success
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
Eventually(func() int {
_ = k8sClient.Get(ctx, key, realBackupSession)
return len(realBackupSession.Status.Targets)
}, timeout, interval).Should(Equal(2))
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
Expect(realBackupSession.Status.Targets[1].Name).Should(Equal(TestBackupFuncName))
Expect(realBackupSession.Status.Targets[1].SessionState).Should(Equal(formolv1alpha1.New))
Expect(realBackupSession.Status.Targets[1].Kind).Should(Equal(formolv1alpha1.JobKind))
})
It("Should be a success when the last task is a success", func() {
realBackupSession := &formolv1alpha1.BackupSession{}
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
realBackupSession.Status.Targets[0].SessionState = formolv1alpha1.Success
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
Eventually(func() int {
_ = k8sClient.Get(ctx, key, realBackupSession)
return len(realBackupSession.Status.Targets)
}, timeout, interval).Should(Equal(2))
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
realBackupSession.Status.Targets[1].SessionState = formolv1alpha1.Success
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
Eventually(func() formolv1alpha1.SessionState {
_ = k8sClient.Get(ctx, key, realBackupSession)
return realBackupSession.Status.SessionState
}, timeout, interval).Should(Equal(formolv1alpha1.Success))
})
It("Should retry when the task is a failure", func() {
realBackupSession := &formolv1alpha1.BackupSession{}
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
realBackupSession.Status.Targets[0].SessionState = formolv1alpha1.Success
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
Eventually(func() int {
_ = k8sClient.Get(ctx, key, realBackupSession)
return len(realBackupSession.Status.Targets)
}, timeout, interval).Should(Equal(2))
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
realBackupSession.Status.Targets[1].SessionState = formolv1alpha1.Failure
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
Eventually(func() int {
_ = k8sClient.Get(ctx, key, realBackupSession)
return realBackupSession.Status.Targets[1].Try
}, timeout, interval).Should(Equal(2))
Expect(k8sClient.Get(ctx, key, realBackupSession)).Should(Succeed())
Expect(realBackupSession.Status.Targets[1].SessionState).Should(Equal(formolv1alpha1.New))
realBackupSession.Status.Targets[1].SessionState = formolv1alpha1.Failure
Expect(k8sClient.Status().Update(ctx, realBackupSession)).Should(Succeed())
Eventually(func() formolv1alpha1.SessionState {
_ = k8sClient.Get(ctx, key, realBackupSession)
return realBackupSession.Status.SessionState
}, timeout, interval).Should(Equal(formolv1alpha1.Failure))
})
It("should create a backup job", func() {
})
})
Context("When other BackupSession exist", func() {
const (
bs1Name = "test-backupsession-controller1"
bs2Name = "test-backupsession-controller2"
bs3Name = "test-backupsession-controller3"
)
var ()
BeforeEach(func() {
})
JustBeforeEach(func() {
})
It("Should clean up old sessions", func() {
})
})
})

View File

@ -26,7 +26,6 @@ import (
appsv1 "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1" batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
@ -46,51 +45,14 @@ type RestoreSessionReconciler struct {
client.Client client.Client
Log logr.Logger Log logr.Logger
Scheme *runtime.Scheme Scheme *runtime.Scheme
RestoreSession *formolv1alpha1.RestoreSession
BackupSession *formolv1alpha1.BackupSession
BackupConf *formolv1alpha1.BackupConfiguration
} }
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions,verbs=get;list;watch;create;update;patch;delete func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target) error {
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions/status,verbs=get;update;patch log := r.Log.WithValues("createrestorejob", target.Name)
func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background() ctx := context.Background()
log := r.Log.WithValues("restoresession", req.NamespacedName)
// Get the RestoreSession
restoreSession := &formolv1alpha1.RestoreSession{}
if err := r.Get(ctx, req.NamespacedName, restoreSession); err != nil {
log.Error(err, "unable to get restoresession")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
log.V(1).Info("got restoresession", "restoreSession", restoreSession)
// Get the BackupSession the RestoreSession references
backupSession := &formolv1alpha1.BackupSession{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: restoreSession.Namespace,
Name: restoreSession.Spec.BackupSessionRef.Ref.Name,
}, backupSession); err != nil {
if errors.IsNotFound(err) {
backupSession = &formolv1alpha1.BackupSession{
Spec: restoreSession.Spec.BackupSessionRef.Spec,
Status: restoreSession.Spec.BackupSessionRef.Status,
}
log.V(1).Info("generated backupsession", "backupsession", backupSession)
} else {
log.Error(err, "unable to get backupsession", "restoresession", restoreSession.Spec)
return ctrl.Result{}, client.IgnoreNotFound(err)
}
}
// Get the BackupConfiguration linked to the BackupSession
backupConf := &formolv1alpha1.BackupConfiguration{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: backupSession.Spec.Ref.Namespace,
Name: backupSession.Spec.Ref.Name,
}, backupConf); err != nil {
log.Error(err, "unable to get backupConfiguration", "name", backupSession.Spec.Ref, "namespace", backupSession.Namespace)
return ctrl.Result{}, client.IgnoreNotFound(err)
}
// Helper functions
createRestoreJob := func(target formolv1alpha1.Target) error {
restoreSessionEnv := []corev1.EnvVar{ restoreSessionEnv := []corev1.EnvVar{
corev1.EnvVar{ corev1.EnvVar{
Name: "TARGET_NAME", Name: "TARGET_NAME",
@ -98,11 +60,11 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
}, },
corev1.EnvVar{ corev1.EnvVar{
Name: "RESTORESESSION_NAME", Name: "RESTORESESSION_NAME",
Value: restoreSession.Name, Value: r.RestoreSession.Name,
}, },
corev1.EnvVar{ corev1.EnvVar{
Name: "RESTORESESSION_NAMESPACE", Name: "RESTORESESSION_NAMESPACE",
Value: restoreSession.Namespace, Value: r.RestoreSession.Namespace,
}, },
} }
@ -110,7 +72,7 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
Name: "output", Name: "output",
MountPath: "/output", MountPath: "/output",
} }
for _, targetStatus := range backupSession.Status.Targets { for _, targetStatus := range r.BackupSession.Status.Targets {
if targetStatus.Name == target.Name { if targetStatus.Name == target.Name {
snapshotId := targetStatus.SnapshotId snapshotId := targetStatus.SnapshotId
restic := corev1.Container{ restic := corev1.Container{
@ -129,19 +91,19 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
} }
repo := &formolv1alpha1.Repo{} repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{ if err := r.Get(ctx, client.ObjectKey{
Namespace: backupConf.Namespace, Namespace: r.BackupConf.Namespace,
Name: backupConf.Spec.Repository, Name: r.BackupConf.Spec.Repository,
}, repo); err != nil { }, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration") log.Error(err, "unable to get Repo from BackupConfiguration")
return err return err
} }
// S3 backing storage // S3 backing storage
var ttl int32 = 300 var ttl int32 = 300
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...) restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
job := &batchv1.Job{ job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-%s-", restoreSession.Name, target.Name), GenerateName: fmt.Sprintf("%s-%s-", r.RestoreSession.Name, target.Name),
Namespace: restoreSession.Namespace, Namespace: r.RestoreSession.Namespace,
}, },
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &ttl, TTLSecondsAfterFinished: &ttl,
@ -159,34 +121,18 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
} }
for _, step := range target.Steps { for _, step := range target.Steps {
function := &formolv1alpha1.Function{} function := &formolv1alpha1.Function{}
// get the backup function
if err := r.Get(ctx, client.ObjectKey{ if err := r.Get(ctx, client.ObjectKey{
Namespace: restoreSession.Namespace, Namespace: r.RestoreSession.Namespace,
Name: step.Name, Name: strings.Replace(step.Name, "backup", "restore", 1)}, function); err != nil {
}, function); err != nil {
log.Error(err, "unable to get backup function", "name", step.Name)
return err
}
var restoreName string
if function.Annotations["restoreFunction"] != "" {
restoreName = function.Annotations["restoreFunction"]
} else {
restoreName = strings.Replace(step.Name, "backup", "restore", 1)
}
if err := r.Get(ctx, client.ObjectKey{
Namespace: restoreSession.Namespace,
Name: restoreName,
}, function); err != nil {
log.Error(err, "unable to get function", "function", step) log.Error(err, "unable to get function", "function", step)
return err return err
} }
function.Spec.Name = function.Name
function.Spec.Env = append(step.Env, restoreSessionEnv...) function.Spec.Env = append(step.Env, restoreSessionEnv...)
function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output) function.Spec.VolumeMounts = append(function.Spec.VolumeMounts, output)
job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec) job.Spec.Template.Spec.InitContainers = append(job.Spec.Template.Spec.InitContainers, function.Spec)
} }
if err := ctrl.SetControllerReference(restoreSession, job, r.Scheme); err != nil { if err := ctrl.SetControllerReference(r.RestoreSession, job, r.Scheme); err != nil {
log.Error(err, "unable to set controller on job", "job", job, "restoresession", restoreSession) log.Error(err, "unable to set controller on job", "job", job, "restoresession", r.RestoreSession)
return err return err
} }
log.V(0).Info("creating a restore job", "target", target.Name) log.V(0).Info("creating a restore job", "target", target.Name)
@ -197,12 +143,14 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
} }
} }
return nil return nil
} }
deleteRestoreInitContainer := func(target formolv1alpha1.Target) error { func (r *RestoreSessionReconciler) DeleteRestoreInitContainer(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createrestoreinitcontainer", target.Name)
ctx := context.Background()
deployment := &appsv1.Deployment{} deployment := &appsv1.Deployment{}
if err := r.Get(context.Background(), client.ObjectKey{ if err := r.Get(context.Background(), client.ObjectKey{
Namespace: backupConf.Namespace, Namespace: r.BackupConf.Namespace,
Name: target.Name, Name: target.Name,
}, deployment); err != nil { }, deployment); err != nil {
log.Error(err, "unable to get deployment") log.Error(err, "unable to get deployment")
@ -223,12 +171,14 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
return err return err
} }
return nil return nil
} }
createRestoreInitContainer := func(target formolv1alpha1.Target) error { func (r *RestoreSessionReconciler) CreateRestoreInitContainer(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createrestoreinitcontainer", target.Name)
ctx := context.Background()
deployment := &appsv1.Deployment{} deployment := &appsv1.Deployment{}
if err := r.Get(context.Background(), client.ObjectKey{ if err := r.Get(context.Background(), client.ObjectKey{
Namespace: restoreSession.Namespace, Namespace: r.RestoreSession.Namespace,
Name: target.Name, Name: target.Name,
}, deployment); err != nil { }, deployment); err != nil {
log.Error(err, "unable to get deployment") log.Error(err, "unable to get deployment")
@ -241,9 +191,12 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
return nil return nil
} }
} }
for _, targetStatus := range backupSession.Status.Targets { var snapshotId string
for _, targetStatus := range r.BackupSession.Status.Targets {
if targetStatus.Name == target.Name && targetStatus.Kind == target.Kind { if targetStatus.Name == target.Name && targetStatus.Kind == target.Kind {
snapshotId := targetStatus.SnapshotId snapshotId = targetStatus.SnapshotId
}
}
restoreSessionEnv := []corev1.EnvVar{ restoreSessionEnv := []corev1.EnvVar{
corev1.EnvVar{ corev1.EnvVar{
Name: formolv1alpha1.TARGET_NAME, Name: formolv1alpha1.TARGET_NAME,
@ -251,11 +204,11 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
}, },
corev1.EnvVar{ corev1.EnvVar{
Name: formolv1alpha1.RESTORESESSION_NAME, Name: formolv1alpha1.RESTORESESSION_NAME,
Value: restoreSession.Name, Value: r.RestoreSession.Name,
}, },
corev1.EnvVar{ corev1.EnvVar{
Name: formolv1alpha1.RESTORESESSION_NAMESPACE, Name: formolv1alpha1.RESTORESESSION_NAMESPACE,
Value: restoreSession.Namespace, Value: r.RestoreSession.Namespace,
}, },
} }
initContainer := corev1.Container{ initContainer := corev1.Container{
@ -267,14 +220,14 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
} }
repo := &formolv1alpha1.Repo{} repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{ if err := r.Get(ctx, client.ObjectKey{
Namespace: backupConf.Namespace, Namespace: r.BackupConf.Namespace,
Name: backupConf.Spec.Repository, Name: r.BackupConf.Spec.Repository,
}, repo); err != nil { }, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration") log.Error(err, "unable to get Repo from BackupConfiguration")
return err return err
} }
// S3 backing storage // S3 backing storage
initContainer.Env = append(initContainer.Env, formolutils.ConfigureResticEnvVar(backupConf, repo)...) initContainer.Env = append(initContainer.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
deployment.Spec.Template.Spec.InitContainers = append([]corev1.Container{initContainer}, deployment.Spec.Template.Spec.InitContainers = append([]corev1.Container{initContainer},
deployment.Spec.Template.Spec.InitContainers...) deployment.Spec.Template.Spec.InitContainers...)
if err := r.Update(ctx, deployment); err != nil { if err := r.Update(ctx, deployment); err != nil {
@ -283,31 +236,31 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
} }
return nil return nil
} }
}
return nil
}
func (r *RestoreSessionReconciler) StatusUpdate() error {
log := r.Log.WithValues("statusupdate", r.RestoreSession.Name)
ctx := context.Background()
startNextTask := func() (*formolv1alpha1.TargetStatus, error) { startNextTask := func() (*formolv1alpha1.TargetStatus, error) {
nextTarget := len(restoreSession.Status.Targets) nextTarget := len(r.RestoreSession.Status.Targets)
if nextTarget < len(backupConf.Spec.Targets) { if nextTarget < len(r.BackupConf.Spec.Targets) {
target := backupConf.Spec.Targets[nextTarget] target := r.BackupConf.Spec.Targets[nextTarget]
targetStatus := formolv1alpha1.TargetStatus{ targetStatus := formolv1alpha1.TargetStatus{
Name: target.Name, Name: target.Name,
Kind: target.Kind, Kind: target.Kind,
SessionState: formolv1alpha1.New, SessionState: formolv1alpha1.New,
StartTime: &metav1.Time{Time: time.Now()}, StartTime: &metav1.Time{Time: time.Now()},
} }
restoreSession.Status.Targets = append(restoreSession.Status.Targets, targetStatus) r.RestoreSession.Status.Targets = append(r.RestoreSession.Status.Targets, targetStatus)
switch target.Kind { switch target.Kind {
case formolv1alpha1.SidecarKind: case "Deployment":
if err := createRestoreInitContainer(target); err != nil { if err := r.CreateRestoreInitContainer(target); err != nil {
log.V(0).Info("unable to create restore init container", "task", target) log.V(0).Info("unable to create restore init container", "task", target)
targetStatus.SessionState = formolv1alpha1.Failure targetStatus.SessionState = formolv1alpha1.Failure
return nil, err return nil, err
} }
case formolv1alpha1.JobKind: case "Task":
if err := createRestoreJob(target); err != nil { if err := r.CreateRestoreJob(target); err != nil {
log.V(0).Info("unable to create restore job", "task", target) log.V(0).Info("unable to create restore job", "task", target)
targetStatus.SessionState = formolv1alpha1.Failure targetStatus.SessionState = formolv1alpha1.Failure
return nil, err return nil, err
@ -318,97 +271,110 @@ func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, err
return nil, nil return nil, nil
} }
} }
endTask := func() error { endTask := func() error {
target := backupConf.Spec.Targets[len(restoreSession.Status.Targets)-1] target := r.BackupConf.Spec.Targets[len(r.RestoreSession.Status.Targets)-1]
switch target.Kind { switch target.Kind {
case formolv1alpha1.SidecarKind: case "Deployment":
if err := deleteRestoreInitContainer(target); err != nil { if err := r.DeleteRestoreInitContainer(target); err != nil {
log.Error(err, "unable to delete restore init container") log.Error(err, "unable to delete restore init container")
return err return err
} }
} }
return nil return nil
} }
switch r.RestoreSession.Status.SessionState {
switch restoreSession.Status.SessionState {
case formolv1alpha1.New: case formolv1alpha1.New:
restoreSession.Status.SessionState = formolv1alpha1.Running r.RestoreSession.Status.SessionState = formolv1alpha1.Running
if targetStatus, err := startNextTask(); err != nil { targetStatus, err := startNextTask()
log.Error(err, "unable to start next restore task") if err != nil {
return ctrl.Result{}, err return err
} else {
log.V(0).Info("New restore. Start the first task", "task", targetStatus.Name)
if err := r.Status().Update(ctx, restoreSession); err != nil {
log.Error(err, "unable to update restoresession")
return ctrl.Result{}, err
} }
log.V(0).Info("New restore. Start the first task", "task", targetStatus.Name)
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
log.Error(err, "unable to update restoresession")
return err
} }
case formolv1alpha1.Running: case formolv1alpha1.Running:
currentTargetStatus := &restoreSession.Status.Targets[len(restoreSession.Status.Targets)-1] currentTargetStatus := r.RestoreSession.Status.Targets[len(r.RestoreSession.Status.Targets)-1]
switch currentTargetStatus.SessionState { switch currentTargetStatus.SessionState {
case formolv1alpha1.Failure: case formolv1alpha1.Failure:
log.V(0).Info("last restore task failed. Stop here", "target", currentTargetStatus.Name) log.V(0).Info("last restore task failed. Stop here", "target", currentTargetStatus.Name)
restoreSession.Status.SessionState = formolv1alpha1.Failure r.RestoreSession.Status.SessionState = formolv1alpha1.Failure
if err := r.Status().Update(ctx, restoreSession); err != nil { if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
log.Error(err, "unable to update restoresession") log.Error(err, "unable to update restoresession")
return ctrl.Result{}, err return err
} }
case formolv1alpha1.Running: case formolv1alpha1.Running:
log.V(0).Info("task is still running", "target", currentTargetStatus.Name) log.V(0).Info("task is still running", "target", currentTargetStatus.Name)
return ctrl.Result{}, nil return nil
case formolv1alpha1.Waiting:
target := backupConf.Spec.Targets[len(restoreSession.Status.Targets)-1]
if target.Kind == formolv1alpha1.SidecarKind {
deployment := &appsv1.Deployment{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: restoreSession.Namespace,
Name: target.Name,
}, deployment); err != nil {
log.Error(err, "unable to get deployment")
return ctrl.Result{}, err
}
if deployment.Status.ReadyReplicas == *deployment.Spec.Replicas {
log.V(0).Info("The deployment is ready. We can resume the backup")
currentTargetStatus.SessionState = formolv1alpha1.Finalize
if err := r.Status().Update(ctx, restoreSession); err != nil {
log.Error(err, "unable to update restoresession")
return ctrl.Result{}, err
}
} else {
log.V(0).Info("Waiting for the sidecar to come back")
return ctrl.Result{RequeueAfter: 10 * time.Second}, nil
}
} else {
log.V(0).Info("not a SidecarKind. Ignoring Waiting")
}
case formolv1alpha1.Success: case formolv1alpha1.Success:
_ = endTask() _ = endTask()
log.V(0).Info("last task was a success. start a new one", "target", currentTargetStatus) log.V(0).Info("last task was a success. start a new one", "target", currentTargetStatus)
targetStatus, err := startNextTask() targetStatus, err := startNextTask()
if err != nil { if err != nil {
return ctrl.Result{}, err return err
} }
if targetStatus == nil { if targetStatus == nil {
// No more task to start. The restore is over // No more task to start. The restore is over
restoreSession.Status.SessionState = formolv1alpha1.Success r.RestoreSession.Status.SessionState = formolv1alpha1.Success
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
log.Error(err, "unable to update restoresession")
return err
} }
if err := r.Status().Update(ctx, restoreSession); err != nil { }
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
log.Error(err, "unable to update restoresession")
return err
}
}
}
return nil
}
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=restoresessions/status,verbs=get;update;patch
func (r *RestoreSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
time.Sleep(500 * time.Millisecond)
ctx := context.Background()
log := r.Log.WithValues("restoresession", req.NamespacedName)
r.RestoreSession = &formolv1alpha1.RestoreSession{}
if err := r.Get(ctx, req.NamespacedName, r.RestoreSession); err != nil {
log.Error(err, "unable to get restoresession")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
r.BackupSession = &formolv1alpha1.BackupSession{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: r.RestoreSession.Spec.BackupSessionRef.Namespace,
Name: r.RestoreSession.Spec.BackupSessionRef.Name}, r.BackupSession); err != nil {
log.Error(err, "unable to get backupsession")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
r.BackupConf = &formolv1alpha1.BackupConfiguration{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: r.BackupSession.Namespace,
Name: r.BackupSession.Spec.Ref.Name}, r.BackupConf); err != nil {
log.Error(err, "unable to get backupConfiguration")
return ctrl.Result{}, client.IgnoreNotFound(err)
}
if r.RestoreSession.Status.ObservedGeneration == r.RestoreSession.ObjectMeta.Generation {
// status update
log.V(0).Info("status update")
return ctrl.Result{}, r.StatusUpdate()
}
r.RestoreSession.Status.ObservedGeneration = r.RestoreSession.ObjectMeta.Generation
r.RestoreSession.Status.SessionState = formolv1alpha1.New
r.RestoreSession.Status.StartTime = &metav1.Time{Time: time.Now()}
reschedule := ctrl.Result{RequeueAfter: 5 * time.Second}
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
log.Error(err, "unable to update restoresession") log.Error(err, "unable to update restoresession")
return ctrl.Result{}, err return ctrl.Result{}, err
} }
}
case "": return reschedule, nil
// Restore session has just been created
restoreSession.Status.SessionState = formolv1alpha1.New
restoreSession.Status.StartTime = &metav1.Time{Time: time.Now()}
if err := r.Status().Update(ctx, restoreSession); err != nil {
log.Error(err, "unable to update restoreSession")
return ctrl.Result{}, err
}
}
return ctrl.Result{}, nil
} }
func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error {

View File

@ -1,95 +0,0 @@
package controllers
import (
"context"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
var _ = Describe("Testing RestoreSession controller", func() {
const (
RSRestoreSessionName = "test-restoresession-controller"
)
var (
ctx = context.Background()
key = types.NamespacedName{
Name: RSRestoreSessionName,
Namespace: TestNamespace,
}
restoreSession = &formolv1alpha1.RestoreSession{}
)
BeforeEach(func() {
restoreSession = &formolv1alpha1.RestoreSession{
ObjectMeta: metav1.ObjectMeta{
Name: RSRestoreSessionName,
Namespace: TestNamespace,
},
Spec: formolv1alpha1.RestoreSessionSpec{
BackupSessionRef: formolv1alpha1.BackupSessionRef{
Ref: corev1.ObjectReference{
Name: TestBackupSessionName,
},
},
},
}
})
Context("Creating a RestoreSession", func() {
JustBeforeEach(func() {
Eventually(func() error {
return k8sClient.Create(ctx, restoreSession)
}, timeout, interval).Should(Succeed())
realRestoreSession := &formolv1alpha1.RestoreSession{}
Eventually(func() error {
return k8sClient.Get(ctx, key, realRestoreSession)
}, timeout, interval).Should(Succeed())
Eventually(func() formolv1alpha1.SessionState {
_ = k8sClient.Get(ctx, key, realRestoreSession)
return realRestoreSession.Status.SessionState
}, timeout, interval).Should(Equal(formolv1alpha1.Running))
})
AfterEach(func() {
Expect(k8sClient.Delete(ctx, restoreSession)).Should(Succeed())
})
It("Should have a new task and should fail if the task fails", func() {
restoreSession := &formolv1alpha1.RestoreSession{}
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
Expect(len(restoreSession.Status.Targets)).Should(Equal(1))
Expect(restoreSession.Status.Targets[0].SessionState).Should(Equal(formolv1alpha1.New))
restoreSession.Status.Targets[0].SessionState = formolv1alpha1.Running
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
Eventually(func() formolv1alpha1.SessionState {
_ = k8sClient.Get(ctx, key, restoreSession)
return restoreSession.Status.Targets[0].SessionState
}, timeout, interval).Should(Equal(formolv1alpha1.Running))
restoreSession.Status.Targets[0].SessionState = formolv1alpha1.Failure
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
Eventually(func() formolv1alpha1.SessionState {
_ = k8sClient.Get(ctx, key, restoreSession)
return restoreSession.Status.SessionState
}, timeout, interval).Should(Equal(formolv1alpha1.Failure))
})
It("Should move to the new task if the first one is a success and be a success if all the tasks succeed", func() {
restoreSession := &formolv1alpha1.RestoreSession{}
Expect(k8sClient.Get(ctx, key, restoreSession)).Should(Succeed())
Expect(len(restoreSession.Status.Targets)).Should(Equal(1))
restoreSession.Status.Targets[0].SessionState = formolv1alpha1.Success
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
Eventually(func() int {
_ = k8sClient.Get(ctx, key, restoreSession)
return len(restoreSession.Status.Targets)
}, timeout, interval).Should(Equal(2))
restoreSession.Status.Targets[1].SessionState = formolv1alpha1.Success
Expect(k8sClient.Status().Update(ctx, restoreSession)).Should(Succeed())
Eventually(func() formolv1alpha1.SessionState {
_ = k8sClient.Get(ctx, key, restoreSession)
return restoreSession.Status.SessionState
}, timeout, interval).Should(Equal(formolv1alpha1.Success))
})
})
})

View File

@ -18,6 +18,7 @@ package controllers
import ( import (
"context" "context"
"fmt"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
@ -43,16 +44,10 @@ import (
// These tests use Ginkgo (BDD-style Go testing framework). Refer to // These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo. // http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
const ( const (
TestBackupFuncName = "test-backup-func" BackupFuncName = "test-backup-func"
TestFunc = "test-norestore-func"
TestRestoreFuncName = "test-restore-func"
TestNamespace = "test-namespace" TestNamespace = "test-namespace"
TestRepoName = "test-repo" RepoName = "test-repo"
TestDeploymentName = "test-deployment" DeploymentName = "test-deployment"
TestBackupConfName = "test-backupconf"
TestBackupSessionName = "test-backupsession"
TestDataVolume = "data"
TestDataMountPath = "/data"
timeout = time.Second * 10 timeout = time.Second * 10
interval = time.Millisecond * 250 interval = time.Millisecond * 250
) )
@ -69,7 +64,7 @@ var (
} }
deployment = &appsv1.Deployment{ deployment = &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: TestDeploymentName, Name: DeploymentName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, },
Spec: appsv1.DeploymentSpec{ Spec: appsv1.DeploymentSpec{
@ -87,11 +82,6 @@ var (
Image: "test-image", Image: "test-image",
}, },
}, },
Volumes: []corev1.Volume{
corev1.Volume{
Name: TestDataVolume,
},
},
}, },
}, },
}, },
@ -115,7 +105,7 @@ var (
} }
repo = &formolv1alpha1.Repo{ repo = &formolv1alpha1.Repo{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: TestRepoName, Name: RepoName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, },
Spec: formolv1alpha1.RepoSpec{ Spec: formolv1alpha1.RepoSpec{
@ -130,29 +120,7 @@ var (
} }
function = &formolv1alpha1.Function{ function = &formolv1alpha1.Function{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
Name: TestFunc, Name: BackupFuncName,
Namespace: TestNamespace,
},
Spec: corev1.Container{
Name: "norestore-func",
Image: "myimage",
Args: []string{"a", "set", "of", "args"},
},
}
backupFunc = &formolv1alpha1.Function{
ObjectMeta: metav1.ObjectMeta{
Name: TestRestoreFuncName,
Namespace: TestNamespace,
},
Spec: corev1.Container{
Name: "restore-func",
Image: "myimage",
Args: []string{"a", "set", "of", "args"},
},
}
restoreFunc = &formolv1alpha1.Function{
ObjectMeta: metav1.ObjectMeta{
Name: TestBackupFuncName,
Namespace: TestNamespace, Namespace: TestNamespace,
}, },
Spec: corev1.Container{ Spec: corev1.Container{
@ -161,69 +129,6 @@ var (
Args: []string{"a", "set", "of", "args"}, Args: []string{"a", "set", "of", "args"},
}, },
} }
testBackupConf = &formolv1alpha1.BackupConfiguration{
ObjectMeta: metav1.ObjectMeta{
Name: TestBackupConfName,
Namespace: TestNamespace,
},
Spec: formolv1alpha1.BackupConfigurationSpec{
Repository: TestRepoName,
Schedule: "1 * * * *",
Keep: formolv1alpha1.Keep{
Last: 2,
},
Targets: []formolv1alpha1.Target{
formolv1alpha1.Target{
Kind: formolv1alpha1.SidecarKind,
Name: TestDeploymentName,
Steps: []formolv1alpha1.Step{
formolv1alpha1.Step{
Name: TestFunc,
},
},
Paths: []string{
TestDataMountPath,
},
VolumeMounts: []corev1.VolumeMount{
corev1.VolumeMount{
Name: TestDataVolume,
MountPath: TestDataMountPath,
},
},
},
formolv1alpha1.Target{
Kind: formolv1alpha1.JobKind,
Name: TestBackupFuncName,
Steps: []formolv1alpha1.Step{
formolv1alpha1.Step{
Name: TestFunc,
},
formolv1alpha1.Step{
Name: TestBackupFuncName,
Env: []corev1.EnvVar{
corev1.EnvVar{
Name: "foo",
Value: "bar",
},
},
},
},
},
},
},
}
testBackupSession = &formolv1alpha1.BackupSession{
ObjectMeta: metav1.ObjectMeta{
Name: TestBackupSessionName,
Namespace: TestNamespace,
},
Spec: formolv1alpha1.BackupSessionSpec{
Ref: corev1.ObjectReference{
Name: TestBackupConfName,
Namespace: TestNamespace,
},
},
}
) )
func TestAPIs(t *testing.T) { func TestAPIs(t *testing.T) {
@ -263,20 +168,6 @@ var _ = BeforeSuite(func() {
}).SetupWithManager(k8sManager) }).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
err = (&BackupSessionReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
Log: ctrl.Log.WithName("controllers").WithName("BackupSession"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())
err = (&RestoreSessionReconciler{
Client: k8sManager.GetClient(),
Scheme: k8sManager.GetScheme(),
Log: ctrl.Log.WithName("controllers").WithName("RestoreSession"),
}).SetupWithManager(k8sManager)
Expect(err).ToNot(HaveOccurred())
go func() { go func() {
err = k8sManager.Start(ctrl.SetupSignalHandler()) err = k8sManager.Start(ctrl.SetupSignalHandler())
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@ -291,36 +182,11 @@ var _ = BeforeSuite(func() {
Expect(k8sClient.Create(ctx, repo)).Should(Succeed()) Expect(k8sClient.Create(ctx, repo)).Should(Succeed())
Expect(k8sClient.Create(ctx, deployment)).Should(Succeed()) Expect(k8sClient.Create(ctx, deployment)).Should(Succeed())
Expect(k8sClient.Create(ctx, function)).Should(Succeed()) Expect(k8sClient.Create(ctx, function)).Should(Succeed())
Expect(k8sClient.Create(ctx, backupFunc)).Should(Succeed())
Expect(k8sClient.Create(ctx, restoreFunc)).Should(Succeed())
Expect(k8sClient.Create(ctx, testBackupConf)).Should(Succeed())
Expect(k8sClient.Create(ctx, testBackupSession)).Should(Succeed())
Eventually(func() error {
return k8sClient.Get(ctx, client.ObjectKey{
Name: TestBackupSessionName,
Namespace: TestNamespace,
}, testBackupSession)
}, timeout, interval).Should(Succeed())
testBackupSession.Status.SessionState = formolv1alpha1.Success
testBackupSession.Status.Targets = []formolv1alpha1.TargetStatus{
formolv1alpha1.TargetStatus{
Name: TestDeploymentName,
Kind: formolv1alpha1.SidecarKind,
SessionState: formolv1alpha1.Success,
SnapshotId: "12345abcdef",
},
formolv1alpha1.TargetStatus{
Name: TestBackupFuncName,
Kind: formolv1alpha1.JobKind,
SessionState: formolv1alpha1.Success,
SnapshotId: "67890ghijk",
},
}
Expect(k8sClient.Status().Update(ctx, testBackupSession)).Should(Succeed())
}, 60) }, 60)
var _ = AfterSuite(func() { var _ = AfterSuite(func() {
By("tearing down the test environment") By("tearing down the test environment")
err := testEnv.Stop() err := testEnv.Stop()
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
fmt.Println("coucou")
}) })

24
main.go
View File

@ -84,6 +84,16 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "BackupSession") setupLog.Error(err, "unable to create controller", "controller", "BackupSession")
os.Exit(1) os.Exit(1)
} }
if os.Getenv("ENABLE_WEBHOOKS") != "false" {
if err = (&formolv1alpha1.BackupSession{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "BackupSession")
os.Exit(1)
}
if err = (&formolv1alpha1.BackupConfiguration{}).SetupWebhookWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create webhook", "webhook", "BackupConfiguration")
os.Exit(1)
}
}
if err = (&controllers.RestoreSessionReconciler{ if err = (&controllers.RestoreSessionReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("RestoreSession"), Log: ctrl.Log.WithName("controllers").WithName("RestoreSession"),
@ -92,20 +102,6 @@ func main() {
setupLog.Error(err, "unable to create controller", "controller", "RestoreSession") setupLog.Error(err, "unable to create controller", "controller", "RestoreSession")
os.Exit(1) os.Exit(1)
} }
// if os.Getenv("ENABLE_WEBHOOKS") != "false" {
// if err = (&formolv1alpha1.BackupSession{}).SetupWebhookWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create webhook", "webhook", "BackupSession")
// os.Exit(1)
// }
// if err = (&formolv1alpha1.BackupConfiguration{}).SetupWebhookWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create webhook", "webhook", "BackupConfiguration")
// os.Exit(1)
// }
// if err = (&formoldesmojimfrv1alpha1.Function{}).SetupWebhookWithManager(mgr); err != nil {
// setupLog.Error(err, "unable to create webhook", "webhook", "Function")
// os.Exit(1)
// }
// }
// +kubebuilder:scaffold:builder // +kubebuilder:scaffold:builder
setupLog.Info("starting manager") setupLog.Info("starting manager")

View File

@ -75,16 +75,6 @@ spec:
--- ---
apiVersion: formol.desmojim.fr/v1alpha1 apiVersion: formol.desmojim.fr/v1alpha1
kind: Function kind: Function
metadata:
name: restore-pg
namespace: demo
spec:
name: restore-pg
image: desmo999r/formolcli:latest
args: ["postgres", "restore", "--hostname", $(PGHOST), "--database", $(PGDATABASE), "--username", $(PGUSER), "--password", $(PGPASSWD), "--file", "/output/backup-pg.sql"]
---
apiVersion: formol.desmojim.fr/v1alpha1
kind: Function
metadata: metadata:
name: backup-pg name: backup-pg
namespace: demo namespace: demo
@ -92,21 +82,3 @@ spec:
name: backup-pg name: backup-pg
image: desmo999r/formolcli:latest image: desmo999r/formolcli:latest
args: ["postgres", "backup", "--hostname", $(PGHOST), "--database", $(PGDATABASE), "--username", $(PGUSER), "--password", $(PGPASSWD), "--file", "/output/backup-pg.sql"] args: ["postgres", "backup", "--hostname", $(PGHOST), "--database", $(PGDATABASE), "--username", $(PGUSER), "--password", $(PGPASSWD), "--file", "/output/backup-pg.sql"]
---
apiVersion: formol.desmojim.fr/v1alpha1
kind: Function
metadata:
name: maintenance-off
namespace: demo
spec:
name: maintenance-off
command: ["/bin/bash", "-c", "echo $(date +%Y/%m/%d-%H:%M:%S) maintenance-off >> /data/logs.txt"]
---
apiVersion: formol.desmojim.fr/v1alpha1
kind: Function
metadata:
name: maintenance-on
namespace: demo
spec:
name: maintenance-on
command: ["/bin/bash", "-c", "echo $(date +%Y/%m/%d-%H:%M:%S) maintenance-on >> /data/logs.txt"]

View File

@ -8,8 +8,6 @@ metadata:
app: nginx app: nginx
spec: spec:
replicas: 1 replicas: 1
strategy:
type: Recreate
selector: selector:
matchLabels: matchLabels:
app: nginx app: nginx

View File

@ -8,22 +8,19 @@ spec:
repository: repo-minio repository: repo-minio
schedule: "15 * * * *" schedule: "15 * * * *"
targets: targets:
- kind: Sidecar - kind: Deployment
apiVersion: v1 apiVersion: v1
name: nginx-deployment name: nginx-deployment
steps:
- name: maintenance-on
- name: maintenance-off
finalize: true
volumeMounts: volumeMounts:
- name: demo-data - name: demo-data
mountPath: /data mountPath: /data
paths: paths:
- /data - /data
- kind: Job - kind: Task
name: backup-pg name: backup-pg
steps: steps:
- name: backup-pg - name: backup-pg
namespace: demo
env: env:
- name: PGHOST - name: PGHOST
value: postgres value: postgres

View File

@ -1,11 +0,0 @@
apiVersion: formol.desmojim.fr/v1alpha1
kind: RestoreSession
metadata:
namespace: demo
name: restore-demo
spec:
backupSession:
ref:
name: backupsession-backup-demo-1619904678
namespace: demo

View File

@ -1 +0,0 @@
NAMESPACE=demo; for i in $(kubectl -n $NAMESPACE get bs | awk 'NR>1 { print $1 }'); do kubectl -n $NAMESPACE get bs -o json $i | sed '/finalizers/,+2d' | curl -vvv -X PUT -H 'Content-type: application/json' -d @- http://127.0.0.1:8001/apis/formol.desmojim.fr/v1alpha1/namespaces/$NAMESPACE/backupsessions/$i; kubectl -n $NAMESPACE delete bs $i; done