Compare commits

...

9 Commits

20 changed files with 1017 additions and 83 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

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

@ -0,0 +1,28 @@
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"
)
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

@ -458,6 +458,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
} }
@ -258,8 +262,18 @@ func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.Ba
return err return err
} }
if err := formolrbac.CreateFormolRBAC(r.Client, "default", backupConf.Namespace); err != nil {
log.Error(err, "unable to create formol 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 creattor RBAC")
return nil
}
if err := formolrbac.CreateBackupSessionStatusUpdaterRBAC(r.Client, "default", backupConf.Namespace); err != nil {
log.Error(err, "unable to create backupsession-statusupdater RBAC")
return nil return nil
} }
@ -281,8 +295,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",
@ -311,6 +325,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)
@ -382,17 +397,26 @@ 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
}
if err := formolrbac.DeleteBackupSessionCreatorRBAC(r.Client, backupConf.Namespace); err != nil {
return err return err
} }
if err := r.deleteSidecarContainer(backupConf, target); err != nil { if err := r.deleteSidecarContainer(backupConf, target); err != nil {
return err return err
} }
case "Task":
} }
} }
// 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

@ -60,16 +60,17 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
if nextTarget < len(r.BackupConf.Spec.Targets) { if nextTarget < len(r.BackupConf.Spec.Targets) {
target := r.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,
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 +80,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 +96,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 +113,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 +204,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 +248,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()
@ -257,18 +257,6 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
log.Error(err, "unable to get backupsession") log.Error(err, "unable to get backupsession")
return ctrl.Result{}, client.IgnoreNotFound(err) 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()
}
r.BackupSession.Status.ObservedGeneration = r.BackupSession.ObjectMeta.Generation
r.BackupSession.Status.BackupState = formolv1alpha1.New
// Prepare the next schedule to start the first task
reschedule := ctrl.Result{RequeueAfter: 5 * time.Second}
r.BackupSession.Status.StartTime = &metav1.Time{Time: time.Now()}
r.BackupConf = &formolv1alpha1.BackupConfiguration{} r.BackupConf = &formolv1alpha1.BackupConfiguration{}
if err := r.Get(ctx, client.ObjectKey{ if err := r.Get(ctx, client.ObjectKey{
Namespace: r.BackupSession.Namespace, Namespace: r.BackupSession.Namespace,
@ -276,7 +264,18 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
log.Error(err, "unable to get backupConfiguration") log.Error(err, "unable to get backupConfiguration")
return ctrl.Result{}, client.IgnoreNotFound(err) 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()
}
r.BackupSession.Status.ObservedGeneration = r.BackupSession.ObjectMeta.Generation
r.BackupSession.Status.SessionState = formolv1alpha1.New
// Prepare the next schedule to start the first task
reschedule := ctrl.Result{RequeueAfter: 5 * time.Second}
r.BackupSession.Status.StartTime = &metav1.Time{Time: time.Now()}
if r.IsBackupOngoing() { if r.IsBackupOngoing() {
// There is already a backup ongoing. We don't do anything and we reschedule // 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") log.V(0).Info("there is an ongoing backup. let's reschedule this operation")
@ -313,8 +312,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 +337,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{
@ -414,11 +413,11 @@ 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,
}) })
} }
@ -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,267 @@
/*
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"
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"
)
// 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) 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[len(r.BackupConf.Spec.Targets)-1-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 "Task":
if err := r.CreateRestoreJob(target); err != nil {
log.V(0).Info("unable to create restore task", "task", target)
targetStatus.SessionState = formolv1alpha1.Failure
return nil, err
}
}
return &targetStatus, nil
} else {
return nil, nil
}
}
switch r.RestoreSession.Status.SessionState {
case formolv1alpha1.New:
targetStatus, err := startNextTask()
if err != nil {
return err
}
log.V(0).Info("New restore. Start the first task", "target", targetStatus)
r.RestoreSession.Status.SessionState = formolv1alpha1.Running
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:
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
log.V(0).Info("No more task to run. 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
}
}
}
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. the restore has already started and the status got updated.
// let's see what happened and figure out what are the next actions
log.V(0).Info("status update")
return ctrl.Result{}, r.StatusUpdate()
}
// a new restore session has been created.
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 reschedule, 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
} }
@ -88,6 +90,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 (
backupListenerRole = "backup-listener-role" formolRole = "formol-sidecar-role"
backupListenerRoleBinding = "backup-listener-rolebinding" backupListenerRole = "backup-listener-role"
backupSessionCreatorSA = "backupsession-creator" backupListenerRoleBinding = "backup-listener-rolebinding"
backupSessionCreatorRole = "backupsession-creator-role" backupSessionCreatorSA = "backupsession-creator"
backupSessionCreatorRoleBinding = "backupsession-creator-rolebinding" backupSessionCreatorRole = "backupsession-creator-role"
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,12 @@ 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"},
},
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch", "patch", "update"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"backupsessions/status"},
}, },
}, },
} }
@ -237,5 +333,106 @@ func CreateBackupSessionListenerRBAC(cl client.Client, saName string, namespace
return err 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{
Verbs: []string{"get", "list", "watch", "patch", "update"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"restoresessions/status", "backupsessions/status"},
},
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"restoresessions", "backupsessions"},
},
},
}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: backupSessionStatusUpdaterRole,
}, 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: backupSessionStatusUpdaterRoleBinding,
},
Subjects: []rbacv1.Subject{
rbacv1.Subject{
Kind: "ServiceAccount",
Name: saName,
},
},
RoleRef: rbacv1.RoleRef{
APIGroup: "rbac.authorization.k8s.io",
Kind: "Role",
Name: backupSessionStatusUpdaterRole,
},
}
if err := cl.Get(context.Background(), client.ObjectKey{
Namespace: namespace,
Name: backupSessionStatusUpdaterRoleBinding,
}, rolebinding); err != nil && errors.IsNotFound(err) {
if err = cl.Create(context.Background(), rolebinding); err != nil {
return err
}
}
return nil return nil
} }

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: "40 * * * *"
targets: targets:
- kind: Deployment - kind: Deployment
apiVersion: v1 apiVersion: v1

View File

@ -0,0 +1,84 @@
---
apiVersion: v1
kind: Namespace
metadata:
name: restore-demo
---
apiVersion: v1
kind: Secret
metadata:
namespace: restore-demo
name: demo-chap-secret
type: "kubernetes.io/iscsi-chap"
data:
discovery.sendtargets.auth.username: ZGVtbw==
discovery.sendtargets.auth.password: VHJtK1lZaXZvMUNZSGszcGFGVWMrcTdCMmdJPQo=
node.session.auth.username: ZGVtbw==
node.session.auth.password: VHJtK1lZaXZvMUNZSGszcGFGVWMrcTdCMmdJPQo=
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: restore-demo-pv
namespace: restore-demo
spec:
storageClassName: manual
capacity:
storage: 50Mi
accessModes:
- ReadWriteOnce
iscsi:
targetPortal: 192.168.1.159
iqn: iqn.2020-08.raid5:restore-demo
lun: 1
fsType: ext4
readOnly: false
chapAuthDiscovery: true
chapAuthSession: true
secretRef:
name: demo-chap-secret
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: restore-demo-pvc
namespace: restore-demo
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 50Mi
---
apiVersion: v1
kind: Secret
metadata:
name: secret-minio
namespace: restore-demo
data:
RESTIC_PASSWORD: bHIyOXhtOTU=
AWS_ACCESS_KEY_ID: OWFTSXZBSEVzWlNVMmkyTU9zVGxWSk1lL1NjPQ==
AWS_SECRET_ACCESS_KEY: WVN5ck9ncVllcjBWNFNLdlVOcmx2OGhjTllhZGZuN2xaNjBIaXRlL3djWT0=
---
apiVersion: formol.desmojim.fr/v1alpha1
kind: Repo
metadata:
name: repo-minio
namespace: restore-demo
spec:
backend:
s3:
server: raid5.desmojim.fr:9000
bucket: testbucket2
repositorySecrets: secret-minio
---
apiVersion: formol.desmojim.fr/v1alpha1
kind: Function
metadata:
name: restore-pg
namespace: restore-demo
spec:
name: backup-pg
image: desmo999r/formolcli:latest
args: ["postgres", "restore", "--hostname", $(PGHOST), "--database", $(PGDATABASE), "--username", $(PGUSER), "--password", $(PGPASSWD), "--file", "/output/backup-pg.sql"]

View File

@ -0,0 +1,88 @@
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: restore-demo
labels:
app: nginx
spec:
replicas: 1
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80
volumeMounts:
- name: demo-data
mountPath: /data
volumes:
- name: demo-data
persistentVolumeClaim:
claimName: restore-demo-pvc
---
apiVersion: v1
kind: ConfigMap
metadata:
name: postgres-config-demo
namespace: restore-demo
labels:
app: postgres
data:
POSTGRES_DB: demopostgres
POSTGRES_USER: demopostgres
POSTGRES_PASSWORD: password123!
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: restore-demo
labels:
app: postgres
spec:
ports:
- port: 5432
name: postgres
clusterIP: None
selector:
app: postgres
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: postgres-demo
namespace: restore-demo
spec:
serviceName: "postgres"
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: postgres:12
envFrom:
- configMapRef:
name: postgres-config-demo
ports:
- containerPort: 5432
name: postgredb
volumeMounts:
- name: postgredb
mountPath: /var/lib/postgresql/data
volumes:
- name: postgredb

39
test/05-backupconf.yaml Normal file
View File

@ -0,0 +1,39 @@
---
apiVersion: formol.desmojim.fr/v1alpha1
kind: BackupConfiguration
metadata:
name: backup-demo
namespace: restore-demo
spec:
repository:
name: repo-minio
schedule: "1 * * * *"
targets:
- kind: Deployment
apiVersion: v1
name: nginx-deployment
volumeMounts:
- name: demo-data
mountPath: /data
paths:
- /data
- kind: Task
name: backup-pg
steps:
- name: backup-pg
namespace: demo
env:
- name: PGHOST
value: postgres
- name: PGDATABASE
value: demopostgres
- name: PGUSER
value: demopostgres
- name: PGPASSWD
value: password123!
keep:
last: 5
daily: 2
weekly: 2
monthly: 6
yearly: 3

View File

@ -0,0 +1,10 @@
apiVersion: formol.desmojim.fr/v1alpha1
kind: RestoreSession
metadata:
namespace: restore-demo
name: restore-demo
spec:
backupSessionRef:
namespace: demo
name: backupsession-backup-demo-1612734804
# name: backupsession-backup-demo-1612713316