Compare commits

...

11 Commits

17 changed files with 905 additions and 71 deletions

View File

@ -23,16 +23,6 @@ import (
// 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 BackupState string
const (
New BackupState = "New"
Running BackupState = "Running"
Success BackupState = "Success"
Failure BackupState = "Failure"
Deleted BackupState = "Deleted"
)
type Ref struct { type Ref struct {
Name string `json:"name"` Name string `json:"name"`
} }
@ -46,19 +36,6 @@ type BackupSessionSpec struct {
Ref `json:"ref"` Ref `json:"ref"`
} }
type TargetStatus struct {
Name string `json:"name"`
Kind string `json:"kind"`
// +optional
BackupState `json:"state,omitempty"`
// +optional
SnapshotId string `json:"snapshotId,omitempty"`
// +optional
StartTime *metav1.Time `json:"startTime,omitempty"`
// +optional
Duration *metav1.Duration `json:"duration,omitempty"`
}
// BackupSessionStatus defines the observed state of BackupSession // BackupSessionStatus defines the observed state of BackupSession
type BackupSessionStatus struct { type BackupSessionStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
@ -66,7 +43,7 @@ type BackupSessionStatus struct {
// +optional // +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"` ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional // +optional
BackupState `json:"state,omitempty"` SessionState `json:"state,omitempty"`
// +optional // +optional
StartTime *metav1.Time `json:"startTime,omitempty"` StartTime *metav1.Time `json:"startTime,omitempty"`
// +optional // +optional

31
api/v1alpha1/common.go Normal file
View File

@ -0,0 +1,31 @@
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type SessionState string
const (
New SessionState = "New"
Running SessionState = "Running"
Success SessionState = "Success"
Failure SessionState = "Failure"
Deleted SessionState = "Deleted"
TARGET_NAME string = "TARGET_NAME"
RESTORESESSION_NAMESPACE string = "RESTORESESSION_NAMESPACE"
RESTORESESSION_NAME string = "RESTORESESSION_NAME"
)
type TargetStatus struct {
Name string `json:"name"`
Kind string `json:"kind"`
// +optional
SessionState `json:"state,omitempty"`
// +optional
SnapshotId string `json:"snapshotId,omitempty"`
// +optional
StartTime *metav1.Time `json:"startTime,omitempty"`
// +optional
Duration *metav1.Duration `json:"duration,omitempty"`
}

View File

@ -0,0 +1,71 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// RestoreSessionSpec defines the desired state of RestoreSession
type RestoreSessionSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
BackupSessionRef metav1.ObjectMeta `json:"backupSessionRef"`
}
// RestoreSessionStatus defines the observed state of RestoreSession
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
SessionState `json:"state,omitempty"`
// +optional
StartTime *metav1.Time `json:"startTime,omitempty"`
// +optional
Targets []TargetStatus `json:"target,omitempty"`
}
// +kubebuilder:object:root=true
// +kubebuilder:resource:shortName="rs"
// +kubebuilder:subresource:status
// RestoreSession is the Schema for the restoresessions API
type RestoreSession struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec RestoreSessionSpec `json:"spec,omitempty"`
Status RestoreSessionStatus `json:"status,omitempty"`
}
// +kubebuilder:object:root=true
// RestoreSessionList contains a list of RestoreSession
type RestoreSessionList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []RestoreSession `json:"items"`
}
func init() {
SchemeBuilder.Register(&RestoreSession{}, &RestoreSessionList{})
}

View File

@ -463,6 +463,107 @@ func (in *Repository) DeepCopy() *Repository {
return out return out
} }
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RestoreSession) DeepCopyInto(out *RestoreSession) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSession.
func (in *RestoreSession) DeepCopy() *RestoreSession {
if in == nil {
return nil
}
out := new(RestoreSession)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RestoreSession) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RestoreSessionList) DeepCopyInto(out *RestoreSessionList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]RestoreSession, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSessionList.
func (in *RestoreSessionList) DeepCopy() *RestoreSessionList {
if in == nil {
return nil
}
out := new(RestoreSessionList)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *RestoreSessionList) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RestoreSessionSpec) DeepCopyInto(out *RestoreSessionSpec) {
*out = *in
in.BackupSessionRef.DeepCopyInto(&out.BackupSessionRef)
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSessionSpec.
func (in *RestoreSessionSpec) DeepCopy() *RestoreSessionSpec {
if in == nil {
return nil
}
out := new(RestoreSessionSpec)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *RestoreSessionStatus) DeepCopyInto(out *RestoreSessionStatus) {
*out = *in
if in.StartTime != nil {
in, out := &in.StartTime, &out.StartTime
*out = (*in).DeepCopy()
}
if in.Targets != nil {
in, out := &in.Targets, &out.Targets
*out = make([]TargetStatus, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSessionStatus.
func (in *RestoreSessionStatus) DeepCopy() *RestoreSessionStatus {
if in == nil {
return nil
}
out := new(RestoreSessionStatus)
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 *S3) DeepCopyInto(out *S3) { func (in *S3) DeepCopyInto(out *S3) {
*out = *in *out = *in

View File

@ -6,6 +6,7 @@ resources:
- bases/formol.desmojim.fr_backupconfigurations.yaml - bases/formol.desmojim.fr_backupconfigurations.yaml
- bases/formol.desmojim.fr_backupsessions.yaml - bases/formol.desmojim.fr_backupsessions.yaml
- bases/formol.desmojim.fr_repoes.yaml - bases/formol.desmojim.fr_repoes.yaml
- bases/formol.desmojim.fr_restoresessions.yaml
# +kubebuilder:scaffold:crdkustomizeresource # +kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge: patchesStrategicMerge:
@ -16,6 +17,7 @@ patchesStrategicMerge:
#- 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
# +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.
@ -24,6 +26,7 @@ patchesStrategicMerge:
- 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
# +kubebuilder:scaffold:crdkustomizecainjectionpatch # +kubebuilder:scaffold:crdkustomizecainjectionpatch
# the following config is for teaching kustomize how to do kustomization for CRDs. # the following config is for teaching kustomize how to do kustomization for CRDs.

View File

@ -0,0 +1,8 @@
# The following patch adds a directive for certmanager to inject CA into the CRD
# CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
annotations:
cert-manager.io/inject-ca-from: $(CERTIFICATE_NAMESPACE)/$(CERTIFICATE_NAME)
name: restoresessions.formol.desmojim.fr.desmojim.fr

View File

@ -0,0 +1,17 @@
# The following patch enables conversion webhook for CRD
# CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: restoresessions.formol.desmojim.fr.desmojim.fr
spec:
conversion:
strategy: Webhook
webhookClientConfig:
# this is "\n" used as a placeholder, otherwise it will be rejected by the apiserver for being blank,
# but we're going to set it later using the cert-manager (or potentially a patch if not using cert-manager)
caBundle: Cg==
service:
namespace: system
name: webhook-service
path: /convert

View File

@ -0,0 +1,7 @@
apiVersion: formol.desmojim.fr.desmojim.fr/v1alpha1
kind: RestoreSession
metadata:
name: restoresession-sample
spec:
# Add fields here
foo: bar

View File

@ -18,6 +18,7 @@ package controllers
import ( import (
"context" "context"
"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"
@ -54,16 +55,15 @@ func (r *BackupConfigurationReconciler) getDeployment(namespace string, name str
return deployment, err return deployment, err
} }
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupconfigurations,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=formol.desmojim.fr,resources=*,verbs=*
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupconfigurations/status,verbs=get;list;watch;update;patch
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupsessions/status,verbs=get;list;watch
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=repoes,verbs=get;list;watch
// +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
// +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch // +kubebuilder:rbac:groups=core,resources=pods,verbs=get;list;watch
// +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=roles,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=rolebindings,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterroles,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=rbac.authorization.k8s.io,resources=clusterrolebindings,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,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
@ -135,7 +135,7 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
sidecar := corev1.Container{ sidecar := corev1.Container{
Name: "backup", Name: "backup",
Image: "desmo999r/formolcli:latest", Image: "desmo999r/formolcli:latest",
Args: []string{"create", "server"}, Args: []string{"backupsession", "server"},
//Image: "busybox", //Image: "busybox",
//Command: []string{ //Command: []string{
// "sh", // "sh",
@ -159,6 +159,10 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
}, },
}, },
}, },
corev1.EnvVar{
Name: "POD_DEPLOYMENT",
Value: target.Name,
},
}, },
VolumeMounts: []corev1.VolumeMount{}, VolumeMounts: []corev1.VolumeMount{},
} }
@ -186,7 +190,7 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
} }
log.V(1).Info("getting pods matching label", "label", selector) log.V(1).Info("getting pods matching label", "label", selector)
pods := &corev1.PodList{} pods := &corev1.PodList{}
err = r.List(context.Background(), pods, client.MatchingLabels(selector)) err = r.List(context.Background(), pods, client.InNamespace(backupConf.Namespace), client.MatchingLabels(selector))
if err != nil { if err != nil {
log.Error(err, "unable to get deployment pods") log.Error(err, "unable to get deployment pods")
return nil return nil
@ -216,7 +220,7 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar) deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar)
deployment.Spec.Template.Spec.ShareProcessNamespace = func() *bool { b := true; return &b }() deployment.Spec.Template.Spec.ShareProcessNamespace = func() *bool { b := true; return &b }()
if err := formolrbac.CreateBackupSessionListenerRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil { if err := formolrbac.CreateFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
log.Error(err, "unable to create backupsessionlistener RBAC") log.Error(err, "unable to create backupsessionlistener RBAC")
return nil return nil
} }
@ -238,6 +242,11 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.BackupConfiguration) error { func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.BackupConfiguration) error {
log := r.Log.WithValues("addCronJob", backupConf.Name) log := r.Log.WithValues("addCronJob", backupConf.Name)
if err := formolrbac.CreateFormolRBAC(r.Client, "default", backupConf.Namespace); err != nil {
log.Error(err, "unable to create backupsessionlistener RBAC")
return nil
}
if err := formolrbac.CreateBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil { if err := formolrbac.CreateBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil {
log.Error(err, "unable to create backupsession-creator RBAC") log.Error(err, "unable to create backupsession-creator RBAC")
return nil return nil
@ -291,8 +300,8 @@ func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.Ba
Name: "job-createbackupsession-" + backupConf.Name, Name: "job-createbackupsession-" + backupConf.Name,
Image: "desmo999r/formolcli:latest", Image: "desmo999r/formolcli:latest",
Args: []string{ Args: []string{
"create",
"backupsession", "backupsession",
"create",
"--namespace", "--namespace",
backupConf.Namespace, backupConf.Namespace,
"--name", "--name",
@ -321,6 +330,7 @@ func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.Ba
func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) { func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background() ctx := context.Background()
log := r.Log.WithValues("backupconfiguration", req.NamespacedName) log := r.Log.WithValues("backupconfiguration", req.NamespacedName)
time.Sleep(300 * time.Millisecond)
log.V(1).Info("Enter Reconcile with req", "req", req) log.V(1).Info("Enter Reconcile with req", "req", req)
@ -392,7 +402,7 @@ func (r *BackupConfigurationReconciler) deleteExternalResources(backupConf *form
if err != nil { if err != nil {
return err return err
} }
if err := formolrbac.DeleteBackupSessionListenerRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil { if err := formolrbac.DeleteFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
return err return err
} }
if err := formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil { if err := formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil {
@ -403,6 +413,16 @@ func (r *BackupConfigurationReconciler) deleteExternalResources(backupConf *form
} }
} }
} }
// TODO: remove the hardcoded "default"
if err := formolrbac.DeleteFormolRBAC(r.Client, "default", backupConf.Namespace); err != nil {
return err
}
if err := formolrbac.DeleteBackupSessionStatusUpdaterRBAC(r.Client, "default", backupConf.Namespace); err != nil {
return err
}
if err := formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil {
return err
}
return nil return nil
} }

View File

@ -37,9 +37,10 @@ import (
formolutils "github.com/desmo999r/formol/pkg/utils" formolutils "github.com/desmo999r/formol/pkg/utils"
) )
var ( const (
sessionState = ".metadata.state" sessionState string = ".metadata.state"
finalizerName = "finalizer.backupsession.formol.desmojim.fr" finalizerName string = "finalizer.backupsession.formol.desmojim.fr"
JOBTTL int32 = 7200
) )
// BackupSessionReconciler reconciles a BackupSession object // BackupSessionReconciler reconciles a BackupSession object
@ -62,14 +63,15 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
targetStatus := formolv1alpha1.TargetStatus{ targetStatus := formolv1alpha1.TargetStatus{
Name: target.Name, Name: target.Name,
Kind: target.Kind, Kind: target.Kind,
BackupState: formolv1alpha1.New, SessionState: formolv1alpha1.New,
StartTime: &metav1.Time{Time: time.Now()},
} }
r.BackupSession.Status.Targets = append(r.BackupSession.Status.Targets, targetStatus) r.BackupSession.Status.Targets = append(r.BackupSession.Status.Targets, targetStatus)
switch target.Kind { switch target.Kind {
case "Task": case "Task":
if err := r.CreateJob(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.BackupState = formolv1alpha1.Failure targetStatus.SessionState = formolv1alpha1.Failure
return nil, err return nil, err
} }
} }
@ -79,10 +81,10 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
} }
} }
// Test the backupsession backupstate to decide what to do // Test the backupsession backupstate to decide what to do
switch r.BackupSession.Status.BackupState { switch r.BackupSession.Status.SessionState {
case formolv1alpha1.New: case formolv1alpha1.New:
// Brand new backupsession; start the first task // Brand new backupsession; start the first task
r.BackupSession.Status.BackupState = formolv1alpha1.Running r.BackupSession.Status.SessionState = formolv1alpha1.Running
targetStatus, err := startNextTask() targetStatus, err := startNextTask()
if err != nil { if err != nil {
return err return err
@ -95,11 +97,11 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
case formolv1alpha1.Running: case formolv1alpha1.Running:
// Backup ongoing. Check the status of the last task to decide what to do // 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] currentTargetStatus := r.BackupSession.Status.Targets[len(r.BackupSession.Status.Targets)-1]
switch currentTargetStatus.BackupState { switch currentTargetStatus.SessionState {
case formolv1alpha1.Failure: case formolv1alpha1.Failure:
// The last task failed. We mark the backupsession as failed and we stop here. // 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) log.V(0).Info("last backup task failed. Stop here", "targetStatus", currentTargetStatus)
r.BackupSession.Status.BackupState = formolv1alpha1.Failure r.BackupSession.Status.SessionState = formolv1alpha1.Failure
case formolv1alpha1.Running: case formolv1alpha1.Running:
// The current task is still running. Nothing to do // The current task is still running. Nothing to do
log.V(0).Info("task is still running", "targetStatus", currentTargetStatus) log.V(0).Info("task is still running", "targetStatus", currentTargetStatus)
@ -112,7 +114,7 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
} }
if targetStatus == nil { if targetStatus == nil {
// No more task to start. The backup is a success // No more task to start. The backup is a success
r.BackupSession.Status.BackupState = formolv1alpha1.Success r.BackupSession.Status.SessionState = formolv1alpha1.Success
log.V(0).Info("Backup is successful. Let's try to do some cleanup") 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(r.BackupConf.Namespace), client.MatchingFieldsSelector{Selector: fields.SelectorFromSet(fields.Set{sessionState: "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 {
@ -203,14 +205,14 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
} }
} }
} }
log.V(1).Info("New BackupSession status", "status", r.BackupSession.Status.BackupState) log.V(1).Info("New BackupSession status", "status", r.BackupSession.Status.SessionState)
if err := r.Status().Update(ctx, r.BackupSession); err != nil { if err := r.Status().Update(ctx, r.BackupSession); err != nil {
log.Error(err, "unable to update BackupSession status") log.Error(err, "unable to update BackupSession status")
return err return err
} }
case formolv1alpha1.Deleted: case formolv1alpha1.Deleted:
for _, target := range r.BackupSession.Status.Targets { for _, target := range r.BackupSession.Status.Targets {
if target.BackupState != formolv1alpha1.Deleted { if target.SessionState != formolv1alpha1.Deleted {
log.V(1).Info("snaphot has not been deleted. won't delete the backupsession", "target", target) log.V(1).Info("snaphot has not been deleted. won't delete the backupsession", "target", target)
return nil return nil
} }
@ -247,7 +249,6 @@ func (r *BackupSessionReconciler) IsBackupOngoing() bool {
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete;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) { func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
time.Sleep(100 * time.Millisecond)
log := r.Log.WithValues("backupsession", req.NamespacedName) log := r.Log.WithValues("backupsession", req.NamespacedName)
ctx := context.Background() ctx := context.Background()
@ -264,7 +265,7 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
return ctrl.Result{}, r.StatusUpdate() return ctrl.Result{}, r.StatusUpdate()
} }
r.BackupSession.Status.ObservedGeneration = r.BackupSession.ObjectMeta.Generation r.BackupSession.Status.ObservedGeneration = r.BackupSession.ObjectMeta.Generation
r.BackupSession.Status.BackupState = formolv1alpha1.New r.BackupSession.Status.SessionState = formolv1alpha1.New
// Prepare the next schedule to start the first task // Prepare the next schedule to start the first task
reschedule := ctrl.Result{RequeueAfter: 5 * time.Second} reschedule := ctrl.Result{RequeueAfter: 5 * time.Second}
@ -313,8 +314,8 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
return reschedule, nil return reschedule, nil
} }
func (r *BackupSessionReconciler) CreateJob(target formolv1alpha1.Target) error { func (r *BackupSessionReconciler) CreateBackupJob(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createjob", target.Name) log := r.Log.WithValues("createbackupjob", target.Name)
ctx := context.Background() ctx := context.Background()
backupSessionEnv := []corev1.EnvVar{ backupSessionEnv := []corev1.EnvVar{
corev1.EnvVar{ corev1.EnvVar{
@ -338,11 +339,11 @@ func (r *BackupSessionReconciler) CreateJob(target formolv1alpha1.Target) error
restic := corev1.Container{ restic := corev1.Container{
Name: "restic", Name: "restic",
Image: "desmo999r/formolcli:latest", Image: "desmo999r/formolcli:latest",
Args: []string{"backup", "volume", "--tag", r.BackupSession.Name, "--path", "/output"}, Args: []string{"volume", "backup", "--tag", r.BackupSession.Name, "--path", "/output"},
VolumeMounts: []corev1.VolumeMount{output}, VolumeMounts: []corev1.VolumeMount{output},
Env: backupSessionEnv, Env: backupSessionEnv,
} }
log.V(1).Info("creating a tagget backup job", "container", restic) log.V(1).Info("creating a tagged backup job", "container", restic)
// Gather information from the repo // Gather information from the repo
repo := &formolv1alpha1.Repo{} repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{ if err := r.Get(ctx, client.ObjectKey{
@ -353,7 +354,6 @@ func (r *BackupSessionReconciler) CreateJob(target formolv1alpha1.Target) error
return err return err
} }
// S3 backing storage // S3 backing storage
var ttl int32 = 300
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...) restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
job := &batchv1.Job{ job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
@ -361,7 +361,7 @@ func (r *BackupSessionReconciler) CreateJob(target formolv1alpha1.Target) error
Namespace: r.BackupConf.Namespace, Namespace: r.BackupConf.Namespace,
}, },
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &ttl, TTLSecondsAfterFinished: &JOBTTL,
Template: corev1.PodTemplateSpec{ Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
InitContainers: []corev1.Container{}, InitContainers: []corev1.Container{},
@ -414,25 +414,24 @@ func (r *BackupSessionReconciler) deleteExternalResources() error {
// container that will delete the restic snapshot(s) matching the backupsession // container that will delete the restic snapshot(s) matching the backupsession
deleteSnapshots := []corev1.Container{} deleteSnapshots := []corev1.Container{}
for _, target := range r.BackupSession.Status.Targets { for _, target := range r.BackupSession.Status.Targets {
if target.BackupState == formolv1alpha1.Success { if target.SessionState == formolv1alpha1.Success {
deleteSnapshots = append(deleteSnapshots, corev1.Container{ deleteSnapshots = append(deleteSnapshots, corev1.Container{
Name: target.Name, Name: target.Name,
Image: "desmo999r/formolcli:latest", Image: "desmo999r/formolcli:latest",
Args: []string{"delete", "snapshot", "--snapshot", target.SnapshotId}, Args: []string{"snapshot", "delete", "--snapshot-id", target.SnapshotId},
Env: env, Env: env,
}) })
} }
} }
// create a job to delete the restic snapshot(s) with the backupsession name tag // create a job to delete the restic snapshot(s) with the backupsession name tag
if len(deleteSnapshots) > 0 { if len(deleteSnapshots) > 0 {
var ttl int32 = 300
job := &batchv1.Job{ job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("delete-%s-", r.BackupSession.Name), GenerateName: fmt.Sprintf("delete-%s-", r.BackupSession.Name),
Namespace: r.BackupSession.Namespace, Namespace: r.BackupSession.Namespace,
}, },
Spec: batchv1.JobSpec{ Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &ttl, TTLSecondsAfterFinished: &JOBTTL,
Template: corev1.PodTemplateSpec{ Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{ Spec: corev1.PodSpec{
InitContainers: []corev1.Container{}, InitContainers: []corev1.Container{},
@ -454,7 +453,7 @@ func (r *BackupSessionReconciler) deleteExternalResources() error {
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)
return []string{string(session.Status.BackupState)} return []string{string(session.Status.SessionState)}
}); err != nil { }); err != nil {
return err return err
} }

View File

@ -0,0 +1,385 @@
/*
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 controllers
import (
"context"
"fmt"
"strings"
"time"
"github.com/go-logr/logr"
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
formolutils "github.com/desmo999r/formol/pkg/utils"
)
const (
RESTORESESSION string = "restoresession"
UPDATESTATUS string = "updatestatus"
)
// RestoreSessionReconciler reconciles a RestoreSession object
type RestoreSessionReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
RestoreSession *formolv1alpha1.RestoreSession
BackupSession *formolv1alpha1.BackupSession
BackupConf *formolv1alpha1.BackupConfiguration
}
func (r *RestoreSessionReconciler) CreateRestoreJob(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createrestorejob", target.Name)
ctx := context.Background()
restoreSessionEnv := []corev1.EnvVar{
corev1.EnvVar{
Name: "TARGET_NAME",
Value: target.Name,
},
corev1.EnvVar{
Name: "RESTORESESSION_NAME",
Value: r.RestoreSession.Name,
},
corev1.EnvVar{
Name: "RESTORESESSION_NAMESPACE",
Value: r.RestoreSession.Namespace,
},
}
output := corev1.VolumeMount{
Name: "output",
MountPath: "/output",
}
for _, targetStatus := range r.BackupSession.Status.Targets {
if targetStatus.Name == target.Name {
snapshotId := targetStatus.SnapshotId
restic := corev1.Container{
Name: "restic",
Image: "desmo999r/formolcli:latest",
Args: []string{"volume", "restore", "--snapshot-id", snapshotId},
VolumeMounts: []corev1.VolumeMount{output},
Env: restoreSessionEnv,
}
finalizer := corev1.Container{
Name: "finalizer",
Image: "desmo999r/formolcli:latest",
Args: []string{"target", "finalize"},
VolumeMounts: []corev1.VolumeMount{output},
Env: restoreSessionEnv,
}
repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: r.BackupConf.Namespace,
Name: r.BackupConf.Spec.Repository.Name,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
// S3 backing storage
var ttl int32 = 300
restic.Env = append(restic.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
job := &batchv1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("%s-%s-", r.RestoreSession.Name, target.Name),
Namespace: r.RestoreSession.Namespace,
},
Spec: batchv1.JobSpec{
TTLSecondsAfterFinished: &ttl,
Template: corev1.PodTemplateSpec{
Spec: corev1.PodSpec{
InitContainers: []corev1.Container{restic},
Containers: []corev1.Container{finalizer},
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: r.RestoreSession.Namespace,
Name: strings.Replace(step.Name, "backup", "restore", 1)}, function); err != nil {
log.Error(err, "unable to get function", "function", step)
return err
}
function.Spec.Env = append(step.Env, restoreSessionEnv...)
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.RestoreSession, job, r.Scheme); err != nil {
log.Error(err, "unable to set controller on job", "job", job, "restoresession", r.RestoreSession)
return err
}
log.V(0).Info("creating a restore job", "target", target.Name)
if err := r.Create(ctx, job); err != nil {
log.Error(err, "unable to create job", "job", job)
return err
}
}
}
return nil
}
func (r *RestoreSessionReconciler) DeleteRestoreInitContainer(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createrestoreinitcontainer", target.Name)
ctx := context.Background()
deployment := &appsv1.Deployment{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: r.BackupConf.Namespace,
Name: target.Name,
}, deployment); err != nil {
log.Error(err, "unable to get deployment")
return err
}
log.V(1).Info("got deployment", "namespace", deployment.Namespace, "name", deployment.Name)
newInitContainers := []corev1.Container{}
for _, initContainer := range deployment.Spec.Template.Spec.InitContainers {
if initContainer.Name == RESTORESESSION {
log.V(0).Info("Found our restoresession container. Removing it from the list of init containers", "container", initContainer)
} else {
newInitContainers = append(newInitContainers, initContainer)
}
}
deployment.Spec.Template.Spec.InitContainers = newInitContainers
if err := r.Update(ctx, deployment); err != nil {
log.Error(err, "unable to update deployment")
return err
}
return nil
}
func (r *RestoreSessionReconciler) CreateRestoreInitContainer(target formolv1alpha1.Target) error {
log := r.Log.WithValues("createrestoreinitcontainer", target.Name)
ctx := context.Background()
deployment := &appsv1.Deployment{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: r.RestoreSession.Namespace,
Name: target.Name,
}, deployment); err != nil {
log.Error(err, "unable to get deployment")
return err
}
log.V(1).Info("got deployment", "namespace", deployment.Namespace, "name", deployment.Name)
for _, initContainer := range deployment.Spec.Template.Spec.InitContainers {
if initContainer.Name == RESTORESESSION {
log.V(0).Info("there is already a restoresession initcontainer", "deployment", deployment.Spec.Template.Spec.InitContainers)
return nil
}
}
var snapshotId string
for _, targetStatus := range r.BackupSession.Status.Targets {
if targetStatus.Name == target.Name && targetStatus.Kind == target.Kind {
snapshotId = targetStatus.SnapshotId
}
}
restoreSessionEnv := []corev1.EnvVar{
corev1.EnvVar{
Name: formolv1alpha1.TARGET_NAME,
Value: target.Name,
},
corev1.EnvVar{
Name: formolv1alpha1.RESTORESESSION_NAME,
Value: r.RestoreSession.Name,
},
corev1.EnvVar{
Name: formolv1alpha1.RESTORESESSION_NAMESPACE,
Value: r.RestoreSession.Namespace,
},
}
initContainer := corev1.Container{
Name: RESTORESESSION,
Image: formolutils.FORMOLCLI,
Args: []string{"volume", "restore", "--snapshot-id", snapshotId},
VolumeMounts: target.VolumeMounts,
Env: restoreSessionEnv,
}
repo := &formolv1alpha1.Repo{}
if err := r.Get(ctx, client.ObjectKey{
Namespace: r.BackupConf.Namespace,
Name: r.BackupConf.Spec.Repository.Name,
}, repo); err != nil {
log.Error(err, "unable to get Repo from BackupConfiguration")
return err
}
// S3 backing storage
initContainer.Env = append(initContainer.Env, formolutils.ConfigureResticEnvVar(r.BackupConf, repo)...)
deployment.Spec.Template.Spec.InitContainers = append([]corev1.Container{initContainer},
deployment.Spec.Template.Spec.InitContainers...)
if err := r.Update(ctx, deployment); err != nil {
log.Error(err, "unable to update deployment")
return err
}
return nil
}
func (r *RestoreSessionReconciler) StatusUpdate() error {
log := r.Log.WithValues("statusupdate", r.RestoreSession.Name)
ctx := context.Background()
startNextTask := func() (*formolv1alpha1.TargetStatus, error) {
nextTarget := len(r.RestoreSession.Status.Targets)
if nextTarget < len(r.BackupConf.Spec.Targets) {
target := r.BackupConf.Spec.Targets[nextTarget]
targetStatus := formolv1alpha1.TargetStatus{
Name: target.Name,
Kind: target.Kind,
SessionState: formolv1alpha1.New,
StartTime: &metav1.Time{Time: time.Now()},
}
r.RestoreSession.Status.Targets = append(r.RestoreSession.Status.Targets, targetStatus)
switch target.Kind {
case "Deployment":
if err := r.CreateRestoreInitContainer(target); err != nil {
log.V(0).Info("unable to create restore init container", "task", target)
targetStatus.SessionState = formolv1alpha1.Failure
return nil, err
}
case "Task":
if err := r.CreateRestoreJob(target); err != nil {
log.V(0).Info("unable to create restore job", "task", target)
targetStatus.SessionState = formolv1alpha1.Failure
return nil, err
}
}
return &targetStatus, nil
} else {
return nil, nil
}
}
endTask := func() error {
target := r.BackupConf.Spec.Targets[len(r.RestoreSession.Status.Targets)-1]
switch target.Kind {
case "Deployment":
if err := r.DeleteRestoreInitContainer(target); err != nil {
log.Error(err, "unable to delete restore init container")
return err
}
}
return nil
}
switch r.RestoreSession.Status.SessionState {
case formolv1alpha1.New:
r.RestoreSession.Status.SessionState = formolv1alpha1.Running
targetStatus, err := startNextTask()
if err != nil {
return 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:
currentTargetStatus := r.RestoreSession.Status.Targets[len(r.RestoreSession.Status.Targets)-1]
switch currentTargetStatus.SessionState {
case formolv1alpha1.Failure:
log.V(0).Info("last restore task failed. Stop here", "target", currentTargetStatus.Name)
r.RestoreSession.Status.SessionState = formolv1alpha1.Failure
if err := r.Status().Update(ctx, r.RestoreSession); err != nil {
log.Error(err, "unable to update restoresession")
return err
}
case formolv1alpha1.Running:
log.V(0).Info("task is still running", "target", currentTargetStatus.Name)
return nil
case formolv1alpha1.Success:
_ = endTask()
log.V(0).Info("last task was a success. start a new one", "target", currentTargetStatus)
targetStatus, err := startNextTask()
if err != nil {
return err
}
if targetStatus == nil {
// No more task to start. The restore is over
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, 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")
return ctrl.Result{}, err
}
return reschedule, nil
}
func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&formolv1alpha1.RestoreSession{}).
Owns(&batchv1.Job{}).
Complete(r)
}

View File

@ -30,6 +30,7 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log" logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/log/zap"
formoldesmojimfrv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
@ -68,6 +69,9 @@ var _ = BeforeSuite(func(done Done) {
err = formolv1alpha1.AddToScheme(scheme.Scheme) err = formolv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
err = formoldesmojimfrv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme // +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme}) k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})

10
main.go
View File

@ -26,6 +26,7 @@ import (
ctrl "sigs.k8s.io/controller-runtime" ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap" "sigs.k8s.io/controller-runtime/pkg/log/zap"
formoldesmojimfrv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
"github.com/desmo999r/formol/controllers" "github.com/desmo999r/formol/controllers"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
@ -40,6 +41,7 @@ func init() {
_ = clientgoscheme.AddToScheme(scheme) _ = clientgoscheme.AddToScheme(scheme)
_ = formolv1alpha1.AddToScheme(scheme) _ = formolv1alpha1.AddToScheme(scheme)
_ = formoldesmojimfrv1alpha1.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme // +kubebuilder:scaffold:scheme
} }
@ -92,6 +94,14 @@ func main() {
os.Exit(1) os.Exit(1)
} }
} }
if err = (&controllers.RestoreSessionReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("RestoreSession"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "RestoreSession")
os.Exit(1)
}
// +kubebuilder:scaffold:builder // +kubebuilder:scaffold:builder
setupLog.Info("starting manager") setupLog.Info("starting manager")

View File

@ -10,11 +10,14 @@ import (
) )
const ( const (
formolRole = "formol-sidecar-role"
backupListenerRole = "backup-listener-role" backupListenerRole = "backup-listener-role"
backupListenerRoleBinding = "backup-listener-rolebinding" backupListenerRoleBinding = "backup-listener-rolebinding"
backupSessionCreatorSA = "backupsession-creator" backupSessionCreatorSA = "backupsession-creator"
backupSessionCreatorRole = "backupsession-creator-role" backupSessionCreatorRole = "backupsession-creator-role"
backupSessionCreatorRoleBinding = "backupsession-creator-rolebinding" backupSessionCreatorRoleBinding = "backupsession-creator-rolebinding"
backupSessionStatusUpdaterRole = "backupsession-statusupdater-role"
backupSessionStatusUpdaterRoleBinding = "backupsession-statusupdater-rolebinding"
) )
func DeleteBackupSessionCreatorRBAC(cl client.Client, namespace string) error { func DeleteBackupSessionCreatorRBAC(cl client.Client, namespace string) error {
@ -160,6 +163,104 @@ func DeleteBackupSessionListenerRBAC(cl client.Client, saName string, namespace
return nil return nil
} }
func DeleteFormolRBAC(cl client.Client, saName string, namespace string) error {
if saName == "" {
saName = "default"
}
formolRoleBinding := namespace + "-" + saName + "-formol-sidecar-rolebinding"
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: formolRoleBinding,
},
Subjects: []rbacv1.Subject{
rbacv1.Subject{
Kind: "ServiceAccount",
Namespace: namespace,
Name: saName,
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: formolRole,
},
}
if err := cl.Delete(context.Background(), clusterRoleBinding); err != nil {
return client.IgnoreNotFound(err)
}
return nil
}
func CreateFormolRBAC(cl client.Client, saName string, namespace string) error {
if saName == "" {
saName = "default"
}
sa := &corev1.ServiceAccount{}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: saName,
}, sa); err != nil {
return err
}
clusterRole := &rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{
Name: formolRole,
},
Rules: []rbacv1.PolicyRule{
rbacv1.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"*"},
//APIGroups: []string{"formol.desmojim.fr"},
//Resources: []string{"restoresessions", "backupsessions", "backupconfigurations"},
},
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{""},
Resources: []string{"pods"},
},
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"apps"},
Resources: []string{"deployments", "replicasets"},
},
},
}
if err := cl.Get(context.Background(), client.ObjectKey{
Name: formolRole,
}, clusterRole); err != nil && errors.IsNotFound(err) {
if err = cl.Create(context.Background(), clusterRole); err != nil {
return err
}
}
formolRoleBinding := namespace + "-" + saName + "-formol-rolebinding"
clusterRoleBinding := &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: formolRoleBinding,
},
Subjects: []rbacv1.Subject{
rbacv1.Subject{
Kind: "ServiceAccount",
Namespace: namespace,
Name: saName,
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "ClusterRole",
Name: formolRole,
},
}
if err := cl.Get(context.Background(), client.ObjectKey{
Name: formolRoleBinding,
}, clusterRoleBinding); err != nil && errors.IsNotFound(err) {
if err = cl.Create(context.Background(), clusterRoleBinding); err != nil {
return err
}
}
return nil
}
func CreateBackupSessionListenerRBAC(cl client.Client, saName string, namespace string) error { func CreateBackupSessionListenerRBAC(cl client.Client, saName string, namespace string) error {
if saName == "" { if saName == "" {
saName = "default" saName = "default"
@ -190,17 +291,113 @@ func CreateBackupSessionListenerRBAC(cl client.Client, saName string, namespace
rbacv1.PolicyRule{ rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch"}, Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"formol.desmojim.fr"}, APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"backupsessions", "backupconfigurations"}, Resources: []string{"restoresessions", "backupsessions", "backupconfigurations"},
}, },
rbacv1.PolicyRule{ rbacv1.PolicyRule{
Verbs: []string{"update", "delete"}, Verbs: []string{"update", "delete"},
APIGroups: []string{"formol.desmojim.fr"}, APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"backupsessions"}, Resources: []string{"restoresessions", "backupsessions"},
}, },
},
}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: backupListenerRole,
}, role); err != nil && errors.IsNotFound(err) {
if err = cl.Create(context.Background(), role); err != nil {
return err
}
}
rolebinding := &rbacv1.RoleBinding{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: backupListenerRoleBinding,
},
Subjects: []rbacv1.Subject{
rbacv1.Subject{
Kind: "ServiceAccount",
Name: saName,
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: backupListenerRole,
},
}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: backupListenerRoleBinding,
}, rolebinding); err != nil && errors.IsNotFound(err) {
if err = cl.Create(context.Background(), rolebinding); err != nil {
return err
}
}
return nil
}
func DeleteBackupSessionStatusUpdaterRBAC(cl client.Client, saName string, namespace string) error {
if saName == "" {
saName = "default"
}
sa := &corev1.ServiceAccount{}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: saName,
}, sa); err != nil {
return err
}
role := &rbacv1.Role{}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: backupSessionStatusUpdaterRole,
}, role); err == nil {
if err = cl.Delete(context.Background(), role); err != nil {
return err
}
}
rolebinding := &rbacv1.RoleBinding{}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: backupSessionStatusUpdaterRoleBinding,
}, rolebinding); err == nil {
if err = cl.Delete(context.Background(), rolebinding); err != nil {
return err
}
}
return nil
}
func CreateBackupSessionStatusUpdaterRBAC(cl client.Client, saName string, namespace string) error {
if saName == "" {
saName = "default"
}
sa := &corev1.ServiceAccount{}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: saName,
}, sa); err != nil {
return err
}
role := &rbacv1.Role{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: backupSessionStatusUpdaterRole,
},
Rules: []rbacv1.PolicyRule{
rbacv1.PolicyRule{ rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch", "patch", "update"}, Verbs: []string{"get", "list", "watch", "patch", "update"},
APIGroups: []string{"formol.desmojim.fr"}, APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"backupsessions/status"}, Resources: []string{"restoresessions/status", "backupsessions/status"},
},
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"restoresessions", "backupsessions"},
}, },
}, },
} }

View File

@ -7,6 +7,10 @@ import (
"strings" "strings"
) )
const (
FORMOLCLI string = "desmo999r/formolcli:latest"
)
func ContainsString(slice []string, s string) bool { func ContainsString(slice []string, s string) bool {
for _, item := range slice { for _, item := range slice {
if item == s { if item == s {

View File

@ -81,4 +81,4 @@ metadata:
spec: spec:
name: backup-pg name: backup-pg
image: desmo999r/formolcli:latest image: desmo999r/formolcli:latest
args: ["backup", "postgres", "--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"]

View File

@ -7,7 +7,7 @@ metadata:
spec: spec:
repository: repository:
name: repo-minio name: repo-minio
schedule: "1 * * * *" schedule: "15 * * * *"
targets: targets:
- kind: Deployment - kind: Deployment
apiVersion: v1 apiVersion: v1