Compare commits

..

No commits in common. "bce58d5f651c4c93bf64d081532ee39d3a116582" and "8b1259c8bad82572e95ab68e061e678dd3644836" have entirely different histories.

19 changed files with 55 additions and 846 deletions

View File

@ -23,6 +23,16 @@ import (
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
type BackupState string
const (
New BackupState = "New"
Running BackupState = "Running"
Success BackupState = "Success"
Failure BackupState = "Failure"
Deleted BackupState = "Deleted"
)
type Ref struct {
Name string `json:"name"`
}
@ -36,6 +46,19 @@ type BackupSessionSpec struct {
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
type BackupSessionStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
@ -43,7 +66,7 @@ type BackupSessionStatus struct {
// +optional
ObservedGeneration int64 `json:"observedGeneration,omitempty"`
// +optional
SessionState `json:"state,omitempty"`
BackupState `json:"state,omitempty"`
// +optional
StartTime *metav1.Time `json:"startTime,omitempty"`
// +optional

View File

@ -1,71 +0,0 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN!
// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized.
// 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,107 +458,6 @@ func (in *Repository) DeepCopy() *Repository {
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.
func (in *S3) DeepCopyInto(out *S3) {
*out = *in

View File

@ -6,7 +6,6 @@ resources:
- bases/formol.desmojim.fr_backupconfigurations.yaml
- bases/formol.desmojim.fr_backupsessions.yaml
- bases/formol.desmojim.fr_repoes.yaml
- bases/formol.desmojim.fr_restoresessions.yaml
# +kubebuilder:scaffold:crdkustomizeresource
patchesStrategicMerge:
@ -17,7 +16,6 @@ patchesStrategicMerge:
#- patches/webhook_in_backupconfigurations.yaml
- patches/webhook_in_backupsessions.yaml
#- patches/webhook_in_repoes.yaml
#- patches/webhook_in_restoresessions.yaml
# +kubebuilder:scaffold:crdkustomizewebhookpatch
# [CERTMANAGER] To enable webhook, uncomment all the sections with [CERTMANAGER] prefix.
@ -26,7 +24,6 @@ patchesStrategicMerge:
- patches/cainjection_in_backupconfigurations.yaml
- patches/cainjection_in_backupsessions.yaml
#- patches/cainjection_in_repoes.yaml
#- patches/cainjection_in_restoresessions.yaml
# +kubebuilder:scaffold:crdkustomizecainjectionpatch
# the following config is for teaching kustomize how to do kustomization for CRDs.

View File

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

View File

@ -1,17 +0,0 @@
# The following patch enables conversion webhook for CRD
# CRD conversion requires k8s 1.13 or later.
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: 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

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

View File

@ -18,7 +18,6 @@ package controllers
import (
"context"
"time"
formolrbac "github.com/desmo999r/formol/pkg/rbac"
formolutils "github.com/desmo999r/formol/pkg/utils"
@ -55,15 +54,16 @@ func (r *BackupConfigurationReconciler) getDeployment(namespace string, name str
return deployment, err
}
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=*,verbs=*
// +kubebuilder:rbac:groups=formol.desmojim.fr,resources=backupconfigurations,verbs=get;list;watch;create;update;patch;delete
// +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=replicasets,verbs=get;list;watch;create;update;patch;delete
// +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=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=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/status,verbs=get
@ -135,7 +135,7 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
sidecar := corev1.Container{
Name: "backup",
Image: "desmo999r/formolcli:latest",
Args: []string{"backupsession", "server"},
Args: []string{"create", "server"},
//Image: "busybox",
//Command: []string{
// "sh",
@ -190,7 +190,7 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
}
log.V(1).Info("getting pods matching label", "label", selector)
pods := &corev1.PodList{}
err = r.List(context.Background(), pods, client.InNamespace(backupConf.Namespace), client.MatchingLabels(selector))
err = r.List(context.Background(), pods, client.MatchingLabels(selector))
if err != nil {
log.Error(err, "unable to get deployment pods")
return nil
@ -220,10 +220,14 @@ func (r *BackupConfigurationReconciler) addSidecarContainer(backupConf *formolv1
deployment.Spec.Template.Spec.Containers = append(deployment.Spec.Template.Spec.Containers, sidecar)
deployment.Spec.Template.Spec.ShareProcessNamespace = func() *bool { b := true; return &b }()
if err := formolrbac.CreateFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
if err := formolrbac.CreateBackupSessionListenerRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
log.Error(err, "unable to create backupsessionlistener 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
}
log.V(0).Info("Adding a sicar container")
if err := r.Update(context.Background(), deployment); err != nil {
@ -262,16 +266,10 @@ func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.Ba
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 {
log.Error(err, "unable to create backupsession creattor RBAC")
log.Error(err, "unable to create backupsession-creator 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
@ -295,8 +293,8 @@ func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.Ba
Name: "job-createbackupsession-" + backupConf.Name,
Image: "desmo999r/formolcli:latest",
Args: []string{
"backupsession",
"create",
"backupsession",
"--namespace",
backupConf.Namespace,
"--name",
@ -325,7 +323,6 @@ func (r *BackupConfigurationReconciler) addCronJob(backupConf *formolv1alpha1.Ba
func (r *BackupConfigurationReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
ctx := context.Background()
log := r.Log.WithValues("backupconfiguration", req.NamespacedName)
time.Sleep(300 * time.Millisecond)
log.V(1).Info("Enter Reconcile with req", "req", req)
@ -397,7 +394,7 @@ func (r *BackupConfigurationReconciler) deleteExternalResources(backupConf *form
if err != nil {
return err
}
if err := formolrbac.DeleteFormolRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
if err := formolrbac.DeleteBackupSessionListenerRBAC(r.Client, deployment.Spec.Template.Spec.ServiceAccountName, deployment.Namespace); err != nil {
return err
}
if err := r.deleteSidecarContainer(backupConf, target); err != nil {
@ -408,9 +405,6 @@ 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
}

View File

@ -63,7 +63,6 @@ func (r *BackupSessionReconciler) StatusUpdate() error {
Name: target.Name,
Kind: target.Kind,
SessionState: formolv1alpha1.New,
StartTime: &metav1.Time{Time: time.Now()},
}
r.BackupSession.Status.Targets = append(r.BackupSession.Status.Targets, targetStatus)
switch target.Kind {
@ -248,6 +247,7 @@ func (r *BackupSessionReconciler) IsBackupOngoing() bool {
// +kubebuilder:rbac:groups=batch,resources=jobs,verbs=get;list;create;update;patch;delete;watch
func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
time.Sleep(100 * time.Millisecond)
log := r.Log.WithValues("backupsession", req.NamespacedName)
ctx := context.Background()
@ -257,13 +257,6 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
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)
}
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
@ -276,6 +269,14 @@ func (r *BackupSessionReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
reschedule := ctrl.Result{RequeueAfter: 5 * time.Second}
r.BackupSession.Status.StartTime = &metav1.Time{Time: time.Now()}
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.IsBackupOngoing() {
// There is already a backup ongoing. We don't do anything and we reschedule
log.V(0).Info("there is an ongoing backup. let's reschedule this operation")
@ -337,7 +338,7 @@ func (r *BackupSessionReconciler) CreateBackupJob(target formolv1alpha1.Target)
restic := corev1.Container{
Name: "restic",
Image: "desmo999r/formolcli:latest",
Args: []string{"volume", "backup", "--tag", r.BackupSession.Name, "--path", "/output"},
Args: []string{"backup", "volume", "--tag", r.BackupSession.Name, "--path", "/output"},
VolumeMounts: []corev1.VolumeMount{output},
Env: backupSessionEnv,
}
@ -417,7 +418,7 @@ func (r *BackupSessionReconciler) deleteExternalResources() error {
deleteSnapshots = append(deleteSnapshots, corev1.Container{
Name: target.Name,
Image: "desmo999r/formolcli:latest",
Args: []string{"snapshot", "delete", "--snapshot-id", target.SnapshotId},
Args: []string{"delete", "snapshot", "--snapshot", target.SnapshotId},
Env: env,
})
}

View File

@ -1,267 +0,0 @@
/*
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package 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,7 +30,6 @@ import (
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
formoldesmojimfrv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
// +kubebuilder:scaffold:imports
)
@ -69,9 +68,6 @@ var _ = BeforeSuite(func(done Done) {
err = formolv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
err = formoldesmojimfrv1alpha1.AddToScheme(scheme.Scheme)
Expect(err).NotTo(HaveOccurred())
// +kubebuilder:scaffold:scheme
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})

10
main.go
View File

@ -26,7 +26,6 @@ import (
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
formoldesmojimfrv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1"
"github.com/desmo999r/formol/controllers"
// +kubebuilder:scaffold:imports
@ -41,7 +40,6 @@ func init() {
_ = clientgoscheme.AddToScheme(scheme)
_ = formolv1alpha1.AddToScheme(scheme)
_ = formoldesmojimfrv1alpha1.AddToScheme(scheme)
// +kubebuilder:scaffold:scheme
}
@ -90,14 +88,6 @@ func main() {
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
setupLog.Info("starting manager")

View File

@ -10,7 +10,6 @@ import (
)
const (
formolRole = "formol-sidecar-role"
backupListenerRole = "backup-listener-role"
backupListenerRoleBinding = "backup-listener-rolebinding"
backupSessionCreatorSA = "backupsession-creator"
@ -163,104 +162,6 @@ func DeleteBackupSessionListenerRBAC(cl client.Client, saName string, namespace
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 {
if saName == "" {
saName = "default"
@ -291,12 +192,12 @@ func CreateBackupSessionListenerRBAC(cl client.Client, saName string, namespace
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"restoresessions", "backupsessions", "backupconfigurations"},
Resources: []string{"backupsessions", "backupconfigurations"},
},
rbacv1.PolicyRule{
Verbs: []string{"update", "delete"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"restoresessions", "backupsessions"},
Resources: []string{"backupsessions"},
},
},
}
@ -333,7 +234,6 @@ func CreateBackupSessionListenerRBAC(cl client.Client, saName string, namespace
return err
}
}
return nil
}
@ -392,12 +292,12 @@ func CreateBackupSessionStatusUpdaterRBAC(cl client.Client, saName string, names
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch", "patch", "update"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"restoresessions/status", "backupsessions/status"},
Resources: []string{"backupsessions/status"},
},
rbacv1.PolicyRule{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"formol.desmojim.fr"},
Resources: []string{"restoresessions", "backupsessions"},
Resources: []string{"backupsessions"},
},
},
}

View File

@ -81,4 +81,4 @@ metadata:
spec:
name: backup-pg
image: desmo999r/formolcli:latest
args: ["postgres", "backup", "--hostname", $(PGHOST), "--database", $(PGDATABASE), "--username", $(PGUSER), "--password", $(PGPASSWD), "--file", "/output/backup-pg.sql"]
args: ["backup", "postgres", "--hostname", $(PGHOST), "--database", $(PGDATABASE), "--username", $(PGUSER), "--password", $(PGPASSWD), "--file", "/output/backup-pg.sql"]

View File

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

View File

@ -1,84 +0,0 @@
---
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

@ -1,88 +0,0 @@
---
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

View File

@ -1,39 +0,0 @@
---
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

@ -1,10 +0,0 @@
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