From 28658aef39e1c9562eecbf9323034c4e0f399142 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Fri, 3 Feb 2023 18:36:09 +0100 Subject: [PATCH 01/37] Initial commit --- .gitignore | 3 +- Dockerfile | 9 - Dockerfile.deployment | 39 ---- LICENSE | 202 ----------------- Makefile | 23 +- backupsession/create.go | 82 +++++++ cmd/backupsession.go | 40 ++++ cmd/root.go | 46 ++++ go.mod | 82 +++++-- main.go | 16 +- manifests/formolcli-rbac.yaml | 69 ------ pkg/backup/root.go | 58 ----- pkg/cmd/backupsession.go | 67 ------ pkg/cmd/postgres.go | 106 --------- pkg/cmd/root.go | 91 -------- pkg/cmd/snapshot.go | 46 ---- pkg/cmd/target.go | 44 ---- pkg/cmd/volume.go | 70 ------ pkg/controllers/backupsession_controller.go | 225 ------------------- pkg/controllers/restoresession_controller.go | 158 ------------- pkg/restic/root.go | 111 --------- pkg/restore/root.go | 61 ----- pkg/server/root.go | 74 ------ pkg/session/root.go | 142 ------------ pkg/utils/root.go | 95 -------- 25 files changed, 246 insertions(+), 1713 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Dockerfile.deployment create mode 100644 backupsession/create.go create mode 100644 cmd/backupsession.go create mode 100644 cmd/root.go delete mode 100644 manifests/formolcli-rbac.yaml delete mode 100644 pkg/backup/root.go delete mode 100644 pkg/cmd/backupsession.go delete mode 100644 pkg/cmd/postgres.go delete mode 100644 pkg/cmd/root.go delete mode 100644 pkg/cmd/snapshot.go delete mode 100644 pkg/cmd/target.go delete mode 100644 pkg/cmd/volume.go delete mode 100644 pkg/controllers/backupsession_controller.go delete mode 100644 pkg/controllers/restoresession_controller.go delete mode 100644 pkg/restic/root.go delete mode 100644 pkg/restore/root.go delete mode 100644 pkg/server/root.go delete mode 100644 pkg/session/root.go delete mode 100644 pkg/utils/root.go diff --git a/.gitignore b/.gitignore index c0e88cc..6a8579a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ +*~ +bin/ go.sum -bin diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 15086dc..0000000 --- a/Dockerfile +++ /dev/null @@ -1,9 +0,0 @@ -# Build a small image -FROM arm64v8/alpine:3.14 - -RUN apk add --no-cache su-exec restic postgresql-client -COPY bin/formolcli /usr/local/bin - -# Command to run -ENTRYPOINT ["/usr/local/bin/formolcli"] -CMD ["--help"] diff --git a/Dockerfile.deployment b/Dockerfile.deployment deleted file mode 100644 index 3e7b6e8..0000000 --- a/Dockerfile.deployment +++ /dev/null @@ -1,39 +0,0 @@ -FROM golang:alpine AS builder - -# Set necessary environmet variables needed for our image -ENV GO111MODULE=on \ - CGO_ENABLED=0 \ - GOOS=linux \ - GOARCH=arm \ - GOARM=7 - -# Move to working directory /build -WORKDIR /build - -# Copy and download dependency using go mod -COPY src/go.mod . -COPY src/go.sum . -RUN go mod download - -# Copy the code into the container -COPY src . - -# Build the application -RUN go build -o formolcli . - -# Move to /dist directory as the place for resulting binary folder -WORKDIR /dist - -# Copy binary from build to main folder -RUN cp /build/formolcli . - -# Build a small image -FROM arm32v7/alpine:3.12 - -RUN apk add --no-cache restic postgresql-client -#COPY bin/restic /usr/local/bin -COPY --from=builder /dist/formolcli /usr/local/bin - -# Command to run -ENTRYPOINT ["/usr/local/bin/formolcli"] -CMD ["--help"] diff --git a/LICENSE b/LICENSE index d645695..e69de29 100644 --- a/LICENSE +++ b/LICENSE @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - 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. diff --git a/Makefile b/Makefile index c64d981..8bd8afa 100644 --- a/Makefile +++ b/Makefile @@ -1,25 +1,16 @@ -.PHONY: all formolcli docker docker-build docker-push - -IMG ?= desmo999r/formolcli:latest +GOARCH ?= arm64 +.PHONY: formolcli formolcli: fmt vet - GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -o bin/formolcli main.go - -test: fmt vet - go test ./... -coverprofile cover.out + GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -o bin/formolcli main.go +.PHONY: fmt fmt: go fmt ./... +.PHONY: vet vet: go vet ./... -docker-build: - buildah bud --disable-compression --format=docker -t ${IMG} . - -docker-push: - buildah push ${IMG} - -docker: formolcli docker-build docker-push - -all: docker +.PHONY: all +all: formolcli diff --git a/backupsession/create.go b/backupsession/create.go new file mode 100644 index 0000000..713e97e --- /dev/null +++ b/backupsession/create.go @@ -0,0 +1,82 @@ +package backupsession + +import ( + "context" + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "os" + "path/filepath" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "strconv" + "strings" + "time" +) + +var ( + config *rest.Config + scheme *runtime.Scheme + cl client.Client + logger logr.Logger + ctx context.Context +) + +func init() { + logger = zap.New(zap.UseDevMode(true)) + ctx = context.Background() + log := logger.WithName("InitBackupSession") + ctrl.SetLogger(logger) + config, err := rest.InClusterConfig() + if err != nil { + config, err = clientcmd.BuildConfigFromFlags("", filepath.Join(os.Getenv("HOME"), ".kube", "config")) + if err != nil { + log.Error(err, "unable to get config") + os.Exit(1) + } + } + scheme = runtime.NewScheme() + _ = formolv1alpha1.AddToScheme(scheme) + _ = clientgoscheme.AddToScheme(scheme) + cl, err = client.New(config, client.Options{Scheme: scheme}) + if err != nil { + log.Error(err, "unable to get client") + os.Exit(1) + } +} + +func CreateBackupSession(ref corev1.ObjectReference) { + log := logger.WithName("CreateBackupSession") + log.V(0).Info("CreateBackupSession called") + backupConf := formolv1alpha1.BackupConfiguration{} + if err := cl.Get(ctx, types.NamespacedName{ + Namespace: ref.Namespace, + Name: ref.Name, + }, &backupConf); err != nil { + log.Error(err, "unable to get backupconf") + os.Exit(1) + } + log.V(0).Info("got backupConf", "backupConf", backupConf) + + backupSession := &formolv1alpha1.BackupSession{ + ObjectMeta: metav1.ObjectMeta{ + Name: strings.Join([]string{"backupsession", ref.Name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), + Namespace: ref.Namespace, + }, + Spec: formolv1alpha1.BackupSessionSpec{ + Ref: ref, + }, + } + log.V(1).Info("create backupsession", "backupSession", backupSession) + if err := cl.Create(ctx, backupSession); err != nil { + log.Error(err, "unable to create backupsession") + os.Exit(1) + } +} diff --git a/cmd/backupsession.go b/cmd/backupsession.go new file mode 100644 index 0000000..00ecbe6 --- /dev/null +++ b/cmd/backupsession.go @@ -0,0 +1,40 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "fmt" + "github.com/desmo999r/formolcli/backupsession" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" +) + +var createBackupSessionCmd = &cobra.Command{ + Use: "create", + Short: "Create a backupsession", + Run: func(cmd *cobra.Command, args []string) { + name, _ := cmd.Flags().GetString("name") + namespace, _ := cmd.Flags().GetString("namespace") + fmt.Println("create backupsession called") + backupsession.CreateBackupSession(corev1.ObjectReference{ + Namespace: namespace, + Name: name, + }) + }, +} + +// backupsessionCmd represents the backupsession command +var backupSessionCmd = &cobra.Command{ + Use: "backupsession", + Short: "All the BackupSession related commands", +} + +func init() { + rootCmd.AddCommand(backupSessionCmd) + backupSessionCmd.AddCommand(createBackupSessionCmd) + createBackupSessionCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") + createBackupSessionCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") + createBackupSessionCmd.MarkFlagRequired("namespace") + createBackupSessionCmd.MarkFlagRequired("name") +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..cd47515 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,46 @@ +/* +Copyright © 2023 NAME HERE +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "formolcli", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.formolcli.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/go.mod b/go.mod index dd846cf..0a4d79d 100644 --- a/go.mod +++ b/go.mod @@ -1,21 +1,73 @@ module github.com/desmo999r/formolcli -go 1.14 +go 1.19 require ( - github.com/desmo999r/formol v0.7.1 - github.com/go-logr/logr v0.3.0 - github.com/go-logr/zapr v0.2.0 - github.com/mitchellh/go-homedir v1.1.0 - github.com/onsi/ginkgo v1.14.1 - github.com/onsi/gomega v1.10.2 - github.com/spf13/cobra v1.1.1 - github.com/spf13/viper v1.7.0 - go.uber.org/zap v1.15.0 - k8s.io/api v0.20.2 - k8s.io/apimachinery v0.20.2 - k8s.io/client-go v0.20.2 - sigs.k8s.io/controller-runtime v0.8.3 + github.com/desmo999r/formol v0.8.0 + github.com/go-logr/logr v1.2.3 + github.com/spf13/cobra v1.6.1 + k8s.io/api v0.26.1 + k8s.io/apimachinery v0.26.1 + k8s.io/client-go v0.26.1 + sigs.k8s.io/controller-runtime v0.14.2 ) -replace github.com/desmo999r/formol => /home/jandre/devel/golang/formol +require ( + github.com/beorn7/perks v1.0.1 // indirect + github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/emicklei/go-restful/v3 v3.9.0 // indirect + github.com/evanphx/json-patch/v5 v5.6.0 // indirect + github.com/fsnotify/fsnotify v1.6.0 // indirect + github.com/go-logr/zapr v1.2.3 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/swag v0.19.14 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/golang/protobuf v1.5.2 // indirect + github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/go-cmp v0.5.9 // indirect + github.com/google/gofuzz v1.1.0 // indirect + github.com/google/uuid v1.1.2 // indirect + github.com/imdario/mergo v0.3.12 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/prometheus/client_golang v1.14.0 // indirect + github.com/prometheus/client_model v0.3.0 // indirect + github.com/prometheus/common v0.37.0 // indirect + github.com/prometheus/procfs v0.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + go.uber.org/zap v1.24.0 // indirect + golang.org/x/net v0.3.1-0.20221206200815-1e63c2f08a10 // indirect + golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + golang.org/x/time v0.3.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect + google.golang.org/appengine v1.6.7 // indirect + google.golang.org/protobuf v1.28.1 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/apiextensions-apiserver v0.26.1 // indirect + k8s.io/component-base v0.26.1 // indirect + k8s.io/klog/v2 v2.80.1 // indirect + k8s.io/kube-openapi v0.0.0-20221012153701-172d655c2280 // indirect + k8s.io/utils v0.0.0-20221128185143-99ec85e7a448 // indirect + sigs.k8s.io/json v0.0.0-20220713155537-f223a00ba0e2 // indirect + sigs.k8s.io/structured-merge-diff/v4 v4.2.3 // indirect + sigs.k8s.io/yaml v1.3.0 // indirect +) + +replace github.com/desmo999r/formol => ../formol diff --git a/main.go b/main.go index 81f4c1a..74459e8 100644 --- a/main.go +++ b/main.go @@ -1,21 +1,9 @@ /* -Copyright © 2020 NAME HERE - -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. +Copyright © 2023 NAME HERE */ package main -import "github.com/desmo999r/formolcli/pkg/cmd" +import "github.com/desmo999r/formolcli/cmd" func main() { cmd.Execute() diff --git a/manifests/formolcli-rbac.yaml b/manifests/formolcli-rbac.yaml deleted file mode 100644 index 51cb035..0000000 --- a/manifests/formolcli-rbac.yaml +++ /dev/null @@ -1,69 +0,0 @@ -apiVersion: v1 -kind: Namespace -metadata: - name: backup ---- -apiVersion: v1 -kind: ServiceAccount -metadata: - name: backupsession-creator - namespace: backup - labels: - app: backupsession-creator ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRole -metadata: - name: backup-listener - labels: - app: backup-listener -rules: - - apiGroups: ["formol.desmojim.fr"] - resources: ["backupsessions", "backupconfigurations"] - verbs: ["get", "list", "watch"] - - apiGroups: ["formol.desmojim.fr"] - resources: ["backupsessions/status"] - verbs: ["update"] ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRole -metadata: - name: backupsession-creator - labels: - app: backupsession-creator -rules: - - apiGroups: ["formol.desmojim.fr"] - resources: ["backupsessions"] - verbs: ["get", "list", "create", "delete"] ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding -metadata: - name: backupsession-creator - labels: - app: backupsession-creator -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: backupsession-creator -subjects: - - name: backupsession-creator - namespace: backup - kind: ServiceAccount - ---- -apiVersion: rbac.authorization.k8s.io/v1beta1 -kind: ClusterRoleBinding -metadata: - name: backup-listener - labels: - app: backup-listener -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: backup-listener -subjects: - - name: default - namespace: default - kind: ServiceAccount - diff --git a/pkg/backup/root.go b/pkg/backup/root.go deleted file mode 100644 index c813036..0000000 --- a/pkg/backup/root.go +++ /dev/null @@ -1,58 +0,0 @@ -package backup - -import ( - "fmt" - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/desmo999r/formolcli/pkg/restic" - "github.com/desmo999r/formolcli/pkg/session" - "github.com/go-logr/logr" - "github.com/go-logr/zapr" - "go.uber.org/zap" - "io/ioutil" - "os" - "os/exec" -) - -var ( - pg_dumpExec = "/usr/bin/pg_dump" - logger logr.Logger -) - -func init() { - zapLog, _ := zap.NewDevelopment() - logger = zapr.NewLogger(zapLog) -} - -func BackupVolume(tag string, paths []string) error { - log := logger.WithName("backup-volume") - state := formolv1alpha1.Success - output, err := restic.BackupPaths(tag, paths) - var snapshotId string - if err != nil { - log.Error(err, "unable to backup volume", "output", string(output)) - state = formolv1alpha1.Failure - } else { - snapshotId = restic.GetBackupResults(output) - } - session.BackupSessionUpdateTargetStatus(state, snapshotId) - return err -} - -func BackupPostgres(file string, hostname string, database string, username string, password string) error { - log := logger.WithName("backup-postgres") - pgpass := []byte(fmt.Sprintf("%s:*:%s:%s:%s", hostname, database, username, password)) - if err := ioutil.WriteFile("/output/.pgpass", pgpass, 0600); err != nil { - log.Error(err, "unable to write password to /output/.pgpass") - return err - } - defer os.Remove("/output/.pgpass") - cmd := exec.Command(pg_dumpExec, "--format=custom", "--clean", "--create", "--file", file, "--host", hostname, "--dbname", database, "--username", username, "--no-password") - cmd.Env = append(os.Environ(), "PGPASSFILE=/output/.pgpass") - output, err := cmd.CombinedOutput() - log.V(1).Info("postgres backup output", "output", string(output)) - if err != nil { - log.Error(err, "something went wrong during the backup") - return err - } - return nil -} diff --git a/pkg/cmd/backupsession.go b/pkg/cmd/backupsession.go deleted file mode 100644 index 12e3a26..0000000 --- a/pkg/cmd/backupsession.go +++ /dev/null @@ -1,67 +0,0 @@ -/* -Copyright © 2020 NAME HERE - -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 cmd - -import ( - "github.com/desmo999r/formolcli/pkg/server" - "github.com/desmo999r/formolcli/pkg/session" - "github.com/spf13/cobra" -) - -var serverBackupSessionCmd = &cobra.Command{ - Use: "server", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - server.Server() - }, -} - -var createBackupSessionCmd = &cobra.Command{ - Use: "create", - Short: "Creates a backupsession", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - name, _ := cmd.Flags().GetString("name") - namespace, _ := cmd.Flags().GetString("namespace") - session.CreateBackupSession(name, namespace) - }, -} - -var backupSessionCmd = &cobra.Command{ - Use: "backupsession", - Short: "backupsession related commands", -} - -func init() { - rootCmd.AddCommand(backupSessionCmd) - backupSessionCmd.AddCommand(createBackupSessionCmd) - backupSessionCmd.AddCommand(serverBackupSessionCmd) - createBackupSessionCmd.Flags().String("namespace", "", "The referenced BackupSessionConfiguration namespace") - createBackupSessionCmd.Flags().String("name", "", "The referenced BackupSessionConfiguration name") - createBackupSessionCmd.MarkFlagRequired("namespace") - createBackupSessionCmd.MarkFlagRequired("name") -} diff --git a/pkg/cmd/postgres.go b/pkg/cmd/postgres.go deleted file mode 100644 index 91bf264..0000000 --- a/pkg/cmd/postgres.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -Copyright © 2020 NAME HERE - -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 cmd - -import ( - "fmt" - - "github.com/desmo999r/formolcli/pkg/backup" - "github.com/desmo999r/formolcli/pkg/restore" - "github.com/spf13/cobra" -) - -// postgresCmd represents the postgres command -var postgresRestoreCmd = &cobra.Command{ - Use: "restore", - Short: "restore a PostgreSQL database", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("postgres called") - file, _ := cmd.Flags().GetString("file") - hostname, _ := cmd.Flags().GetString("hostname") - database, _ := cmd.Flags().GetString("database") - username, _ := cmd.Flags().GetString("username") - password, _ := cmd.Flags().GetString("password") - _ = restore.RestorePostgres(file, hostname, database, username, password) - }, -} - -var postgresBackupCmd = &cobra.Command{ - Use: "backup", - Short: "backup a PostgreSQL database", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("postgres called") - file, _ := cmd.Flags().GetString("file") - hostname, _ := cmd.Flags().GetString("hostname") - database, _ := cmd.Flags().GetString("database") - username, _ := cmd.Flags().GetString("username") - password, _ := cmd.Flags().GetString("password") - _ = backup.BackupPostgres(file, hostname, database, username, password) - }, -} - -var postgresCmd = &cobra.Command{ - Use: "postgres", - Short: "postgres actions", -} - -func init() { - rootCmd.AddCommand(postgresCmd) - postgresCmd.AddCommand(postgresBackupCmd) - postgresCmd.AddCommand(postgresRestoreCmd) - - // Here you will define your flags and configuration settings. - - // Cobra supports Persistent Flags which will work for this command - // and all subcommands, e.g.: - // backupPostgresCmd.PersistentFlags().String("foo", "", "A help for foo") - - // Cobra supports local flags which will only run when this command - // is called directly, e.g.: - // backupPostgresCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") - postgresBackupCmd.Flags().String("file", "", "The file the backup will be stored") - postgresBackupCmd.Flags().String("hostname", "", "The postgresql server host") - postgresBackupCmd.Flags().String("database", "", "The postgresql database") - postgresBackupCmd.Flags().String("username", "", "The postgresql username") - postgresBackupCmd.Flags().String("password", "", "The postgresql password") - postgresBackupCmd.MarkFlagRequired("path") - postgresBackupCmd.MarkFlagRequired("hostname") - postgresBackupCmd.MarkFlagRequired("database") - postgresBackupCmd.MarkFlagRequired("username") - postgresBackupCmd.MarkFlagRequired("password") - postgresRestoreCmd.Flags().String("file", "", "The file the database will be restored from") - postgresRestoreCmd.Flags().String("hostname", "", "The postgresql server host") - postgresRestoreCmd.Flags().String("database", "", "The postgresql database") - postgresRestoreCmd.Flags().String("username", "", "The postgresql username") - postgresRestoreCmd.Flags().String("password", "", "The postgresql password") - postgresRestoreCmd.MarkFlagRequired("path") - postgresRestoreCmd.MarkFlagRequired("hostname") - postgresRestoreCmd.MarkFlagRequired("database") - postgresRestoreCmd.MarkFlagRequired("username") - postgresRestoreCmd.MarkFlagRequired("password") -} diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go deleted file mode 100644 index 86f2468..0000000 --- a/pkg/cmd/root.go +++ /dev/null @@ -1,91 +0,0 @@ -/* -Copyright © 2020 NAME HERE - -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 cmd - -import ( - "fmt" - "github.com/spf13/cobra" - "os" - - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/viper" -) - -var cfgFile string - -// rootCmd represents the base command when called without any subcommands -var rootCmd = &cobra.Command{ - Use: "formolcli", - Short: "A brief description of your application", - Long: `A longer description that spans multiple lines and likely contains -examples and usage of using your application. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, - // Uncomment the following line if your bare application - // has an action associated with it: - // Run: func(cmd *cobra.Command, args []string) { }, -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} - -func init() { - cobra.OnInitialize(initConfig) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.formolcli.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") -} - -// initConfig reads in config file and ENV variables if set. -func initConfig() { - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - // Search config in home directory with name ".formolcli" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".formolcli") - } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - fmt.Println("Using config file:", viper.ConfigFileUsed()) - } -} diff --git a/pkg/cmd/snapshot.go b/pkg/cmd/snapshot.go deleted file mode 100644 index 0cadb76..0000000 --- a/pkg/cmd/snapshot.go +++ /dev/null @@ -1,46 +0,0 @@ -/* Copyright © 2020 NAME HERE - -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 cmd - -import ( - "os" - - "github.com/desmo999r/formolcli/pkg/restic" - "github.com/spf13/cobra" -) - -// snapshotCmd represents the snapshot command -var snapshotCmd = &cobra.Command{ - Use: "snapshot", - Short: "A brief description of your command", -} - -var snapshotDeleteCmd = &cobra.Command{ - Use: "delete", - Short: "delete a snapshot", - Run: func(cmd *cobra.Command, args []string) { - snapshot, _ := cmd.Flags().GetString("snapshot-id") - if err := restic.DeleteSnapshot(snapshot); err != nil { - os.Exit(1) - } - }, -} - -func init() { - rootCmd.AddCommand(snapshotCmd) - snapshotCmd.AddCommand(snapshotDeleteCmd) - snapshotDeleteCmd.Flags().String("snapshot-id", "", "The snapshot to delete") - snapshotDeleteCmd.MarkFlagRequired("snapshot-id") -} diff --git a/pkg/cmd/target.go b/pkg/cmd/target.go deleted file mode 100644 index f7a63c7..0000000 --- a/pkg/cmd/target.go +++ /dev/null @@ -1,44 +0,0 @@ -/* -Copyright © 2021 NAME HERE - -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 cmd - -import ( - "fmt" - - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/desmo999r/formolcli/pkg/session" - "github.com/spf13/cobra" -) - -// targetCmd represents the target command -var targetFinalizeCmd = &cobra.Command{ - Use: "finalize", - Short: "Update the session target status", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("target called") - session.RestoreSessionUpdateTargetStatus(formolv1alpha1.Success) - }, -} - -var targetCmd = &cobra.Command{ - Use: "target", - Short: "A brief description of your command", -} - -func init() { - rootCmd.AddCommand(targetCmd) - targetCmd.AddCommand(targetFinalizeCmd) -} diff --git a/pkg/cmd/volume.go b/pkg/cmd/volume.go deleted file mode 100644 index 4cd2e7f..0000000 --- a/pkg/cmd/volume.go +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright © 2020 NAME HERE - -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 cmd - -import ( - "os" - - "github.com/desmo999r/formolcli/pkg/backup" - "github.com/desmo999r/formolcli/pkg/restore" - "github.com/spf13/cobra" -) - -var volumeRestoreCmd = &cobra.Command{ - Use: "restore", - Short: "restore a volume", - Run: func(cmd *cobra.Command, args []string) { - snapshotId, _ := cmd.Flags().GetString("snapshot-id") - if err := restore.RestoreVolume(snapshotId); err != nil { - os.Exit(1) - } - }, -} - -var volumeBackupCmd = &cobra.Command{ - Use: "backup", - Short: "backup a volume", - Run: func(cmd *cobra.Command, args []string) { - paths, _ := cmd.Flags().GetStringSlice("path") - tag, _ := cmd.Flags().GetString("tag") - if err := backup.BackupVolume(tag, paths); err != nil { - os.Exit(1) - } - }, -} - -var volumeCmd = &cobra.Command{ - Use: "volume", - Short: "volume actions", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, -} - -func init() { - rootCmd.AddCommand(volumeCmd) - volumeCmd.AddCommand(volumeBackupCmd) - volumeCmd.AddCommand(volumeRestoreCmd) - - volumeBackupCmd.Flags().StringSlice("path", nil, "Path to the data to backup") - volumeBackupCmd.Flags().String("tag", "", "Tag associated to the backup") - volumeBackupCmd.MarkFlagRequired("path") - volumeRestoreCmd.Flags().String("snapshot-id", "", "snapshot id associated to the backup") - volumeRestoreCmd.MarkFlagRequired("snapshot-id") -} diff --git a/pkg/controllers/backupsession_controller.go b/pkg/controllers/backupsession_controller.go deleted file mode 100644 index faecf7c..0000000 --- a/pkg/controllers/backupsession_controller.go +++ /dev/null @@ -1,225 +0,0 @@ -package controllers - -import ( - "context" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - "os" - "regexp" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "time" - - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/desmo999r/formolcli/pkg/restic" - formolcliutils "github.com/desmo999r/formolcli/pkg/utils" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// BackupSessionReconciler reconciles a BackupSession object -type BackupSessionReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme -} - -var _ reconcile.Reconciler = &BackupSessionReconciler{} - -func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - time.Sleep(2 * time.Second) - log := r.Log.WithValues("backupsession", req.NamespacedName) - - // your logic here - backupSession := &formolv1alpha1.BackupSession{} - if err := r.Get(ctx, req.NamespacedName, backupSession); err != nil { - log.Error(err, "unable to get backupsession") - return reconcile.Result{}, client.IgnoreNotFound(err) - } - backupConf := &formolv1alpha1.BackupConfiguration{} - if err := r.Get(ctx, client.ObjectKey{ - Namespace: backupSession.Namespace, - Name: backupSession.Spec.Ref.Name, - }, backupConf); err != nil { - log.Error(err, "unable to get backupConfiguration") - return reconcile.Result{}, client.IgnoreNotFound(err) - } - - deploymentName := os.Getenv(formolv1alpha1.TARGET_NAME) - for _, target := range backupConf.Spec.Targets { - switch target.Kind { - case formolv1alpha1.SidecarKind: - if target.Name == deploymentName { - // We are involved in that Backup, let's see if it's our turn - status := &(backupSession.Status.Targets[len(backupSession.Status.Targets)-1]) - if status.Name == deploymentName { - log.V(0).Info("It's for us!", "target", target) - switch status.SessionState { - case formolv1alpha1.New: - log.V(0).Info("New session, move to Initializing state") - status.SessionState = formolv1alpha1.Init - if err := r.Status().Update(ctx, backupSession); err != nil { - log.Error(err, "unable to update backupsession status") - return reconcile.Result{}, err - } - case formolv1alpha1.Init: - log.V(0).Info("Start to run the backup initializing steps if any") - result := formolv1alpha1.Running - for _, step := range target.Steps { - if step.Finalize != nil && *step.Finalize == true { - continue - } - function := &formolv1alpha1.Function{} - if err := r.Get(ctx, client.ObjectKey{ - Name: step.Name, - Namespace: backupConf.Namespace, - }, function); err != nil { - log.Error(err, "unable to get function", "function", step.Name) - return reconcile.Result{}, err - } - // TODO: check the command arguments for $(VAR_NAME) arguments. If some are found, try to expand them from - // the Function.Spec EnvFrom and Env in that order - pattern := regexp.MustCompile(`^\$\((\w+)\)$`) - for i, arg := range function.Spec.Command[1:] { - i++ - if match, _ := regexp.MatchString(`^\$\$`, arg); match { - continue - } - if pattern.MatchString(arg) { - arg = pattern.ReplaceAllString(arg, "$1") - // TODO: Find arg in EnvFrom key and replace it by the value in Command[i] - for _, envFrom := range function.Spec.EnvFrom { - if envFrom.SecretRef != nil { - secret := &corev1.Secret{} - if err := r.Get(ctx, client.ObjectKey{ - Name: envFrom.SecretRef.Name, - Namespace: backupConf.Namespace, - }, secret); err != nil { - log.Error(err, "unable to get secret", "secret", envFrom.SecretRef.Name) - return reconcile.Result{}, err - } - if val, ok := secret.Data[arg]; ok { - log.V(1).Info("Found EnvFrom value for arg", "arg", arg) - function.Spec.Command[i] = string(val) - } - - } - if envFrom.ConfigMapRef != nil { - configMap := &corev1.ConfigMap{} - if err := r.Get(ctx, client.ObjectKey{ - Name: envFrom.ConfigMapRef.Name, - Namespace: backupConf.Namespace, - }, configMap); err != nil { - log.Error(err, "unable to get configMap", "configMap", envFrom.ConfigMapRef.Name) - return reconcile.Result{}, err - } - if val, ok := configMap.Data[arg]; ok { - log.V(1).Info("Found EnvFrom value for arg", "arg", arg) - function.Spec.Command[i] = val - } - } - } - for _, env := range function.Spec.Env { - if env.Name == arg { - if env.Value == "" { - if env.ValueFrom != nil { - if env.ValueFrom.SecretKeyRef != nil { - secret := &corev1.Secret{} - if err := r.Get(ctx, client.ObjectKey{ - Name: env.ValueFrom.SecretKeyRef.Name, - Namespace: backupConf.Namespace, - }, secret); err != nil { - log.Error(err, "unable to get secret", "secret", env.ValueFrom.SecretKeyRef.Name) - return reconcile.Result{}, err - } - log.V(1).Info("Found Env value for arg", "arg", arg) - function.Spec.Command[i] = string(secret.Data[env.ValueFrom.SecretKeyRef.Key]) - } - } - } else { - function.Spec.Command[i] = env.Value - } - } - } - } - } - if err := formolcliutils.RunChroot(target.ContainerName != "", function.Spec.Command[0], function.Spec.Command[1:]...); err != nil { - log.Error(err, "unable to run function command", "command", function.Spec.Command) - result = formolv1alpha1.Failure - break - } - } - status.SessionState = result - - if err := r.Status().Update(ctx, backupSession); err != nil { - log.Error(err, "unable to update backupsession status") - return reconcile.Result{}, err - } - case formolv1alpha1.Running: - log.V(0).Info("Running session. Do the backup") - result := formolv1alpha1.Finalize - status.StartTime = &metav1.Time{Time: time.Now()} - output, err := restic.BackupPaths(backupSession.Name, target.Paths) - if err != nil { - log.Error(err, "unable to backup deployment", "output", string(output)) - result = formolv1alpha1.Failure - } else { - snapshotId := restic.GetBackupResults(output) - status.SnapshotId = snapshotId - status.Duration = &metav1.Duration{Duration: time.Now().Sub(status.StartTime.Time)} - } - status.SessionState = result - log.V(1).Info("current backupSession status", "status", backupSession.Status) - if err := r.Status().Update(ctx, backupSession); err != nil { - log.Error(err, "unable to update backupsession status") - return reconcile.Result{}, err - } - case formolv1alpha1.Finalize: - log.V(0).Info("Start to run the backup finalizing steps if any") - result := formolv1alpha1.Success - for _, step := range target.Steps { - if step.Finalize != nil && *step.Finalize == true { - function := &formolv1alpha1.Function{} - if err := r.Get(ctx, client.ObjectKey{ - Name: step.Name, - Namespace: backupConf.Namespace, - }, function); err != nil { - log.Error(err, "unable to get function", "function", step.Name) - return reconcile.Result{}, err - } - if err := formolcliutils.RunChroot(target.ContainerName != "", function.Spec.Command[0], function.Spec.Command[1:]...); err != nil { - log.Error(err, "unable to run function command", "command", function.Spec.Command) - result = formolv1alpha1.Failure - break - } - } - } - status.SessionState = result - - if err := r.Status().Update(ctx, backupSession); err != nil { - log.Error(err, "unable to update backupsession status") - return reconcile.Result{}, err - } - - case formolv1alpha1.Success, formolv1alpha1.Failure: - log.V(0).Info("Backup is over") - } - } - } - } - } - return reconcile.Result{}, nil -} - -func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&formolv1alpha1.BackupSession{}). - WithEventFilter(predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { return false }, - DeleteFunc: func(e event.DeleteEvent) bool { return false }, - }). - Complete(r) -} diff --git a/pkg/controllers/restoresession_controller.go b/pkg/controllers/restoresession_controller.go deleted file mode 100644 index 6cb7877..0000000 --- a/pkg/controllers/restoresession_controller.go +++ /dev/null @@ -1,158 +0,0 @@ -package controllers - -import ( - "context" - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - formolcliutils "github.com/desmo999r/formolcli/pkg/utils" - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/runtime" - "os" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/event" - "sigs.k8s.io/controller-runtime/pkg/predicate" - "sigs.k8s.io/controller-runtime/pkg/reconcile" - "strings" - "time" -) - -type RestoreSessionReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme -} - -var _ reconcile.Reconciler = &RestoreSessionReconciler{} - -func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) { - log := r.Log.WithValues("restoresession", req.NamespacedName) - - restoreSession := &formolv1alpha1.RestoreSession{} - if err := r.Get(ctx, req.NamespacedName, restoreSession); err != nil { - log.Error(err, "unable to get restoresession") - return reconcile.Result{}, client.IgnoreNotFound(err) - } - backupSession := &formolv1alpha1.BackupSession{} - if err := r.Get(ctx, client.ObjectKey{ - Namespace: restoreSession.Namespace, - Name: restoreSession.Spec.BackupSessionRef.Ref.Name, - }, backupSession); err != nil { - if errors.IsNotFound(err) { - backupSession = &formolv1alpha1.BackupSession{ - Spec: restoreSession.Spec.BackupSessionRef.Spec, - Status: restoreSession.Spec.BackupSessionRef.Status, - } - log.V(1).Info("generated backupsession", "backupsession", backupSession) - } else { - log.Error(err, "unable to get backupsession", "restoresession", restoreSession.Spec) - return reconcile.Result{}, client.IgnoreNotFound(err) - } - } - backupConf := &formolv1alpha1.BackupConfiguration{} - if err := r.Get(ctx, client.ObjectKey{ - Namespace: restoreSession.Namespace, // we use the BackupConfiguration in RestoreSession namespace. - Name: backupSession.Spec.Ref.Name, - }, backupConf); err != nil { - log.Error(err, "unable to get backupConfiguration") - return reconcile.Result{}, client.IgnoreNotFound(err) - } - deploymentName := os.Getenv(formolv1alpha1.TARGET_NAME) - currentTargetStatus := &(restoreSession.Status.Targets[len(restoreSession.Status.Targets)-1]) - currentTarget := backupConf.Spec.Targets[len(restoreSession.Status.Targets)-1] - switch currentTarget.Kind { - case formolv1alpha1.SidecarKind: - if currentTarget.Name == deploymentName { - switch currentTargetStatus.SessionState { - case formolv1alpha1.Finalize: - log.V(0).Info("It's for us!", "target", currentTarget.Name) - podName := os.Getenv(formolv1alpha1.POD_NAME) - podNamespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) - pod := &corev1.Pod{} - if err := r.Get(ctx, client.ObjectKey{ - Namespace: podNamespace, - Name: podName, - }, pod); err != nil { - log.Error(err, "unable to get pod", "name", podName, "namespace", podNamespace) - return reconcile.Result{}, err - } - for _, containerStatus := range pod.Status.ContainerStatuses { - if !containerStatus.Ready { - log.V(0).Info("Not all the containers in the pod are ready. Reschedule", "name", containerStatus.Name) - return reconcile.Result{RequeueAfter: 10 * time.Second}, nil - } - } - log.V(0).Info("All the containers in the pod are ready. Time to run the restore steps (in reverse order)") - // We iterate through the steps in reverse order - result := formolv1alpha1.Success - for i := range currentTarget.Steps { - step := currentTarget.Steps[len(currentTarget.Steps)-1-i] - log.V(1).Info("current step", "step", step.Name) - backupFunction := &formolv1alpha1.Function{} - if err := r.Get(ctx, client.ObjectKey{ - Namespace: backupConf.Namespace, - Name: step.Name, - }, backupFunction); err != nil { - log.Error(err, "unable to get backup function") - return reconcile.Result{}, err - } - // We got the backup function corresponding to the step from the BackupConfiguration - // Now let's try to get the restore function is there is one - restoreFunction := &formolv1alpha1.Function{} - if restoreFunctionName, exists := backupFunction.Annotations[formolv1alpha1.RESTORE_ANNOTATION]; exists { - log.V(0).Info("got restore function", "name", restoreFunctionName) - if err := r.Get(ctx, client.ObjectKey{ - Namespace: backupConf.Namespace, - Name: restoreFunctionName, - }, restoreFunction); err != nil { - log.Error(err, "unable to get restore function") - continue - } - } else { - if strings.HasPrefix(backupFunction.Name, "backup-") { - log.V(0).Info("backupFunction starts with 'backup-'", "name", backupFunction.Name) - if err := r.Get(ctx, client.ObjectKey{ - Namespace: backupConf.Namespace, - Name: strings.Replace(backupFunction.Name, "backup-", "restore-", 1), - }, restoreFunction); err != nil { - log.Error(err, "unable to get restore function") - continue - } - } - log.V(1).Info("No associated restore function", "step", step.Name) - } - if len(restoreFunction.Spec.Command) > 1 { - log.V(0).Info("Running the restore function", "name", restoreFunction.Name, "command", restoreFunction.Spec.Command) - if err := formolcliutils.RunChroot(currentTarget.ContainerName != "", restoreFunction.Spec.Command[0], restoreFunction.Spec.Command[1:]...); err != nil { - log.Error(err, "unable to run function command", "command", restoreFunction.Spec.Command) - result = formolv1alpha1.Failure - break - } else { - log.V(0).Info("Restore command is successful") - } - } - } - // We are done with the restore of this target. We flag it as success or failure - // so that we can move to the next step - log.V(0).Info("Finalize is over", "target", currentTarget.Name) - currentTargetStatus.SessionState = result - if err := r.Status().Update(ctx, restoreSession); err != nil { - log.Error(err, "unable to update restoresession") - } - } - } - } - - return reconcile.Result{}, nil -} - -func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&formolv1alpha1.RestoreSession{}). - WithEventFilter(predicate.Funcs{ - CreateFunc: func(e event.CreateEvent) bool { return false }, - DeleteFunc: func(e event.DeleteEvent) bool { return false }, - }). - Complete(r) -} diff --git a/pkg/restic/root.go b/pkg/restic/root.go deleted file mode 100644 index d4effe4..0000000 --- a/pkg/restic/root.go +++ /dev/null @@ -1,111 +0,0 @@ -package restic - -import ( - "bufio" - "bytes" - "encoding/json" - "github.com/go-logr/logr" - "github.com/go-logr/zapr" - "go.uber.org/zap" - "os" - "os/exec" - "time" -) - -var ( - repository string - passwordFile string - aws_access_key_id string - aws_secret_access_key string - resticExec = "/usr/bin/restic" - logger logr.Logger -) - -func init() { - zapLog, _ := zap.NewDevelopment() - logger = zapr.NewLogger(zapLog) - repository = os.Getenv("RESTIC_REPOSITORY") - passwordFile = os.Getenv("RESTIC_PASSWORD") - aws_access_key_id = os.Getenv("AWS_ACCESS_KEY_ID") - aws_secret_access_key = os.Getenv("AWS_SECRET_ACCESS_KEY") -} - -func checkRepo(repo string) error { - log := logger.WithValues("backup-checkrepo", repo) - cmd := exec.Command(resticExec, "unlock", "-r", repo) - if err := cmd.Run(); err != nil { - log.Error(err, "unable to unlock repo", "repo", repo) - } - cmd = exec.Command(resticExec, "check", "-r", repo) - output, err := cmd.CombinedOutput() - log.V(1).Info("restic check output", "output", string(output)) - if err != nil { - log.V(0).Info("initializing new repo", "repo", repo) - cmd = exec.Command(resticExec, "init", "-r", repo) - output, err = cmd.CombinedOutput() - log.V(1).Info("restic init repo", "output", string(output)) - if err != nil { - log.Error(err, "something went wrong during repo init") - return err - } - } - return err -} - -func GetBackupResults(output []byte) (snapshotId string) { - log := logger.WithName("backup-getbackupresults") - scanner := bufio.NewScanner(bytes.NewReader(output)) - var dat map[string]interface{} - for scanner.Scan() { - if err := json.Unmarshal(scanner.Bytes(), &dat); err != nil { - log.Error(err, "unable to unmarshal json", "msg", string(scanner.Bytes())) - continue - } - log.V(1).Info("message on stdout", "stdout", dat) - if message_type, ok := dat["message_type"]; ok && message_type == "summary" { - snapshotId = dat["snapshot_id"].(string) - //duration = time.Duration(dat["total_duration"].(float64)*1000) * time.Millisecond - } - } - return -} - -func GetRestoreResults(output []byte) time.Duration { - return 0 * time.Millisecond -} - -func BackupPaths(tag string, paths []string) ([]byte, error) { - log := logger.WithName("backup-deployment") - if err := checkRepo(repository); err != nil { - log.Error(err, "unable to setup newrepo", "newrepo", repository) - return []byte{}, err - } - cmd := exec.Command(resticExec, append([]string{"backup", "--json", "--tag", tag, "-r", repository}, paths...)...) - output, err := cmd.CombinedOutput() - return output, err -} - -func RestorePaths(snapshotId string) ([]byte, error) { - log := logger.WithName("restore-deployment") - if err := checkRepo(repository); err != nil { - log.Error(err, "unable to setup repo", "repo", repository) - return []byte{}, err - } - cmd := exec.Command(resticExec, "restore", "-r", repository, snapshotId, "--target", "/") - output, err := cmd.CombinedOutput() - log.V(1).Info("restic restore output", "output", string(output)) - return output, err -} - -func DeleteSnapshot(snapshot string) error { - log := logger.WithValues("delete-snapshot", snapshot) - cmd := exec.Command(resticExec, "forget", "-r", repository, "--prune", snapshot) - log.V(0).Info("deleting snapshot", "snapshot", snapshot) - output, err := cmd.CombinedOutput() - log.V(1).Info("delete snapshot output", "output", string(output)) - if err != nil { - log.Error(err, "unable to delete the snapshot") - return err - } - return nil -} diff --git a/pkg/restore/root.go b/pkg/restore/root.go deleted file mode 100644 index 3327ba0..0000000 --- a/pkg/restore/root.go +++ /dev/null @@ -1,61 +0,0 @@ -package restore - -import ( - "fmt" - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/desmo999r/formolcli/pkg/restic" - "github.com/desmo999r/formolcli/pkg/session" - "github.com/go-logr/logr" - "github.com/go-logr/zapr" - "go.uber.org/zap" - "io/ioutil" - "os" - "os/exec" -) - -var ( - //psqlExec = "/usr/bin/psql" - pg_restoreExec = "/usr/bin/pg_restore" - logger logr.Logger -) - -func init() { - zapLog, _ := zap.NewDevelopment() - logger = zapr.NewLogger(zapLog) -} - -func RestoreVolume(snapshotId string) error { - log := logger.WithName("restore-volume") - if err := session.RestoreSessionUpdateTargetStatus(formolv1alpha1.Init); err != nil { - return err - } - state := formolv1alpha1.Waiting - output, err := restic.RestorePaths(snapshotId) - if err != nil { - log.Error(err, "unable to restore volume", "output", string(output)) - state = formolv1alpha1.Failure - } - log.V(1).Info("restic restore output", "output", string(output)) - session.RestoreSessionUpdateTargetStatus(state) - return err -} - -func RestorePostgres(file string, hostname string, database string, username string, password string) error { - log := logger.WithName("restore-postgres") - pgpass := []byte(fmt.Sprintf("%s:*:%s:%s:%s", hostname, database, username, password)) - if err := ioutil.WriteFile("/output/.pgpass", pgpass, 0600); err != nil { - log.Error(err, "unable to write password to /output/.pgpass") - return err - } - defer os.Remove("/output/.pgpass") - //cmd := exec.Command(psqlExec, "--file", file, "--host", hostname, "--dbname", database, "--username", username, "--no-password") - cmd := exec.Command(pg_restoreExec, "--format=custom", "--clean", "--host", hostname, "--dbname", database, "--username", username, "--no-password", file) - cmd.Env = append(os.Environ(), "PGPASSFILE=/output/.pgpass") - output, err := cmd.CombinedOutput() - log.V(1).Info("postgres restore output", "output", string(output)) - if err != nil { - log.Error(err, "something went wrong during the restore") - return err - } - return nil -} diff --git a/pkg/server/root.go b/pkg/server/root.go deleted file mode 100644 index e181019..0000000 --- a/pkg/server/root.go +++ /dev/null @@ -1,74 +0,0 @@ -package server - -import ( - // "flag" - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/desmo999r/formolcli/pkg/controllers" - "k8s.io/apimachinery/pkg/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "os" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -var ( - scheme = runtime.NewScheme() - setupLog = ctrl.Log.WithName("server") -) - -func init() { - _ = clientgoscheme.AddToScheme(scheme) - _ = formolv1alpha1.AddToScheme(scheme) -} - -func Server() { - // var metricsAddr string - // var enableLeaderElection bool - // flag.StringVar(&metricsAddr, "metrics-addr", ":8082", "The address the metric endpoint binds to.") - // flag.BoolVar(&enableLeaderElection, "enable-leader-election", false, - // "Enable leader election for controller manager. "+ - // "Enabling this will ensure there is only one active controller manager.") - // flag.Parse() - - ctrl.SetLogger(zap.New(zap.UseDevMode(true))) - - config, err := ctrl.GetConfig() - mgr, err := ctrl.NewManager(config, ctrl.Options{ - Scheme: scheme, - // MetricsBindAddress: metricsAddr, - // Port: 9443, - // LeaderElection: enableLeaderElection, - // LeaderElectionID: "12345.desmojim.fr", - Namespace: os.Getenv("POD_NAMESPACE"), - }) - if err != nil { - setupLog.Error(err, "unable to create manager") - os.Exit(1) - } - - // BackupSession controller - if err = (&controllers.BackupSessionReconciler{ - Client: mgr.GetClient(), - Log: ctrl.Log.WithName("controllers").WithName("BackupSession"), - Scheme: mgr.GetScheme(), - }).SetupWithManager(mgr); err != nil { - setupLog.Error(err, "unable to create controller", "controller", "BackupSession") - os.Exit(1) - } - - // RestoreSession controller - 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) - } - - setupLog.Info("starting manager") - if err = mgr.Start(ctrl.SetupSignalHandler()); err != nil { - setupLog.Error(err, "problem running the manager") - os.Exit(1) - } -} diff --git a/pkg/session/root.go b/pkg/session/root.go deleted file mode 100644 index 43d8e8a..0000000 --- a/pkg/session/root.go +++ /dev/null @@ -1,142 +0,0 @@ -package session - -import ( - "context" - "errors" - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "os" - "path/filepath" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log/zap" - "strconv" - "strings" - "time" -) - -var ( - config *rest.Config - scheme *runtime.Scheme - cl client.Client - logger logr.Logger - //backupSession *formolv1alpha1.BackupSession -) - -func init() { - logger = zap.New(zap.UseDevMode(true)) - log := logger.WithName("InitBackupSession") - ctrl.SetLogger(logger) - config, err := rest.InClusterConfig() - if err != nil { - config, err = clientcmd.BuildConfigFromFlags("", filepath.Join(os.Getenv("HOME"), ".kube", "config")) - if err != nil { - log.Error(err, "unable to get config") - os.Exit(1) - } - } - scheme = runtime.NewScheme() - _ = formolv1alpha1.AddToScheme(scheme) - _ = clientgoscheme.AddToScheme(scheme) - cl, err = client.New(config, client.Options{Scheme: scheme}) - if err != nil { - log.Error(err, "unable to get client") - os.Exit(1) - } -} - -func BackupSessionUpdateTargetStatus(state formolv1alpha1.SessionState, snapshotId string) error { - log := logger.WithName("BackupSessionUpdateStatus") - targetName := os.Getenv("TARGET_NAME") - backupSession := &formolv1alpha1.BackupSession{} - if err := cl.Get(context.Background(), client.ObjectKey{ - Namespace: os.Getenv("BACKUPSESSION_NAMESPACE"), - Name: os.Getenv("BACKUPSESSION_NAME"), - }, backupSession); err != nil { - log.Error(err, "unable to get backupsession", "BACKUPSESSION_NAME", os.Getenv("BACKUPSESSION_NAME"), "BACKUPSESSION_NAMESPACE", os.Getenv("BACKUPSESSION_NAMESPACE")) - return err - } - target := &(backupSession.Status.Targets[len(backupSession.Status.Targets)-1]) - if target.Name == targetName { - target.SessionState = state - target.SnapshotId = snapshotId - target.Duration = &metav1.Duration{Duration: time.Now().Sub(target.StartTime.Time)} - } - - if err := cl.Status().Update(context.Background(), backupSession); err != nil { - log.Error(err, "unable to update status", "backupsession", backupSession) - return err - } - return nil -} - -func RestoreSessionUpdateTargetStatus(state formolv1alpha1.SessionState) error { - log := logger.WithName("RestoreSessionUpdateStatus") - targetName := os.Getenv("TARGET_NAME") - restoreSession := &formolv1alpha1.RestoreSession{} - if err := cl.Get(context.Background(), client.ObjectKey{ - Namespace: os.Getenv("RESTORESESSION_NAMESPACE"), - Name: os.Getenv("RESTORESESSION_NAME"), - }, restoreSession); err != nil { - log.Error(err, "unable to get backupsession", "RESTORESESSION_NAME", os.Getenv("RESTORESESSION_NAME"), "RESTORESESSION_NAMESPACE", os.Getenv("RESTORESESSION_NAMESPACE")) - return err - } - for i, target := range restoreSession.Status.Targets { - if target.Name == targetName { - if target.SessionState == formolv1alpha1.Success { - return errors.New("the restore has already been done. Skipping") - } - restoreSession.Status.Targets[i].SessionState = state - if state == formolv1alpha1.Success || state == formolv1alpha1.Failure { - restoreSession.Status.Targets[i].Duration = &metav1.Duration{Duration: time.Now().Sub(restoreSession.Status.Targets[i].StartTime.Time)} - } - } - } - - if err := cl.Status().Update(context.Background(), restoreSession); err != nil { - log.Error(err, "unable to update restoresession status", "restoresession", restoreSession) - return err - } - return nil -} - -func CreateBackupSession(name string, namespace string) { - log := logger.WithName("CreateBackupSession") - log.V(0).Info("CreateBackupSession called") - backupConfList := &formolv1alpha1.BackupConfigurationList{} - if err := cl.List(context.TODO(), backupConfList, client.InNamespace(namespace)); err != nil { - log.Error(err, "unable to get backupconf") - os.Exit(1) - } - backupConf := &formolv1alpha1.BackupConfiguration{} - for _, bc := range backupConfList.Items { - if bc.Name == name { - *backupConf = bc - } - } - log.V(0).Info("got backupConf", "backupConf", backupConf) - - backupSession := &formolv1alpha1.BackupSession{ - ObjectMeta: metav1.ObjectMeta{ - Name: strings.Join([]string{"backupsession", name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), - Namespace: namespace, - }, - Spec: formolv1alpha1.BackupSessionSpec{ - Ref: corev1.ObjectReference{ - Name: name, - Namespace: namespace, - }, - }, - } - log.V(1).Info("create backupsession", "backupSession", backupSession) - if err := cl.Create(context.TODO(), backupSession); err != nil { - log.Error(err, "unable to create backupsession") - os.Exit(1) - } -} diff --git a/pkg/utils/root.go b/pkg/utils/root.go deleted file mode 100644 index 7bf5ce7..0000000 --- a/pkg/utils/root.go +++ /dev/null @@ -1,95 +0,0 @@ -package utils - -import ( - "bytes" - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/go-logr/logr" - "github.com/go-logr/zapr" - "go.uber.org/zap" - "io/ioutil" - "os" - "os/exec" - "path/filepath" - "regexp" - "strconv" -) - -var logger logr.Logger - -func init() { - zapLog, _ := zap.NewDevelopment() - logger = zapr.NewLogger(zapLog) -} - -func Run(runCmd string, args []string) error { - log := logger.WithValues("Run", runCmd, "Args", args) - cmd := exec.Command(runCmd, args...) - output, err := cmd.CombinedOutput() - log.V(1).Info("result", "output", string(output)) - if err != nil { - log.Error(err, "something went wrong") - return err - } - return nil -} - -func RunChroot(lookForTag bool, runCmd string, args ...string) error { - log := logger.WithValues("RunChroot", runCmd, "Args", args) - root := regexp.MustCompile(`/proc/[0-9]+/root`) - env := regexp.MustCompile(`/proc/[0-9]+/environ`) - pid := strconv.Itoa(os.Getpid()) - skip := false - if err := filepath.Walk("/proc", func(path string, info os.FileInfo, err error) error { - if skip { - return filepath.SkipDir - } - if err != nil { - return nil - } - if info.IsDir() && (info.Name() == "1" || info.Name() == pid) { - return filepath.SkipDir - } - if lookForTag && env.MatchString(path) { - log.V(0).Info("Looking for tag", "file", path) - content, err := ioutil.ReadFile(path) - if err != nil { - return filepath.SkipDir - } - - var matched bool - for _, envVar := range bytes.Split(content, []byte{'\000'}) { - matched, err = regexp.Match(formolv1alpha1.TARGETCONTAINER_TAG, envVar) - if err != nil { - log.Error(err, "cannot regexp") - return err - } - if matched { - log.V(0).Info("Found the target tag", "file", path) - break - } - } - if matched == false { - return filepath.SkipDir - } - } - if root.MatchString(path) { - if _, err := filepath.EvalSymlinks(path); err != nil { - return filepath.SkipDir - } - log.V(0).Info("running chroot in", "path", path) - cmd := exec.Command("chroot", append([]string{path, runCmd}, args...)...) - output, err := cmd.CombinedOutput() - log.V(0).Info("result", "output", string(output)) - if err != nil { - log.Error(err, "something went wrong") - return err - } - skip = true - return filepath.SkipDir - } - return nil - }); err != nil { - return err - } - return nil -} From bfc1bdec2aa67b1835769477f7bd9e65b24a5403 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Tue, 7 Feb 2023 18:57:17 +0100 Subject: [PATCH 02/37] Added BackupSession controller skeleton --- .../server/backupsession_controller.go | 31 ++++++++++ backupsession/server/server.go | 61 +++++++++++++++++++ cmd/backupsession.go | 11 ++++ 3 files changed, 103 insertions(+) create mode 100644 backupsession/server/backupsession_controller.go create mode 100644 backupsession/server/server.go diff --git a/backupsession/server/backupsession_controller.go b/backupsession/server/backupsession_controller.go new file mode 100644 index 0000000..dddb8a2 --- /dev/null +++ b/backupsession/server/backupsession_controller.go @@ -0,0 +1,31 @@ +package server + +import ( + "context" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" +) + +type BackupSessionReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Log = log.FromContext(ctx) + r.Log.V(0).Info("Enter Reconcile with req", "req", req) + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&formolv1alpha1.BackupSession{}). + Complete(r) +} diff --git a/backupsession/server/server.go b/backupsession/server/server.go new file mode 100644 index 0000000..4708f45 --- /dev/null +++ b/backupsession/server/server.go @@ -0,0 +1,61 @@ +package server + +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "os" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" +) + +var ( + scheme = runtime.NewScheme() + setupLog = ctrl.Log.WithName("StartServer") +) + +func init() { + utilruntime.Must(clientgoscheme.AddToScheme(scheme)) + utilruntime.Must(formolv1alpha1.AddToScheme(scheme)) +} + +func StartServer() { + opts := zap.Options{ + Development: true, + } + ctrl.SetLogger(zap.New(zap.UseFlagOptions(&opts))) + + mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{ + MetricsBindAddress: "0", // disabling prometheus metrics + Scheme: scheme, + Namespace: os.Getenv("POD_NAMESPACE"), + }) + if err != nil { + setupLog.Error(err, "unable to create manager") + os.Exit(1) + } + if err = (&BackupSessionReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "BackupSession") + os.Exit(1) + } + + if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up health check") + os.Exit(1) + } + if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil { + setupLog.Error(err, "unable to set up ready check") + os.Exit(1) + } + setupLog.Info("starting manager") + if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil { + setupLog.Error(err, "problem starting manager") + os.Exit(1) + } +} diff --git a/cmd/backupsession.go b/cmd/backupsession.go index 00ecbe6..19862ce 100644 --- a/cmd/backupsession.go +++ b/cmd/backupsession.go @@ -6,6 +6,7 @@ package cmd import ( "fmt" "github.com/desmo999r/formolcli/backupsession" + "github.com/desmo999r/formolcli/backupsession/server" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" ) @@ -24,6 +25,15 @@ var createBackupSessionCmd = &cobra.Command{ }, } +var startServerCmd = &cobra.Command{ + Use: "server", + Short: "Start a BackupSession controller", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("starts backupsession controller") + server.StartServer() + }, +} + // backupsessionCmd represents the backupsession command var backupSessionCmd = &cobra.Command{ Use: "backupsession", @@ -33,6 +43,7 @@ var backupSessionCmd = &cobra.Command{ func init() { rootCmd.AddCommand(backupSessionCmd) backupSessionCmd.AddCommand(createBackupSessionCmd) + backupSessionCmd.AddCommand(startServerCmd) createBackupSessionCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") createBackupSessionCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") createBackupSessionCmd.MarkFlagRequired("namespace") From 80d31f10901282505d018bbab7603c46c3605fbf Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Tue, 7 Feb 2023 23:14:25 +0100 Subject: [PATCH 03/37] Most of the state machine is implemented --- Makefile | 2 +- .../server/backupsession_controller.go | 31 ----- cmd/backupsession.go | 4 +- controllers/backupsession_controller.go | 112 ++++++++++++++++++ .../server => controllers}/server.go | 2 +- 5 files changed, 116 insertions(+), 35 deletions(-) delete mode 100644 backupsession/server/backupsession_controller.go create mode 100644 controllers/backupsession_controller.go rename {backupsession/server => controllers}/server.go (98%) diff --git a/Makefile b/Makefile index 8bd8afa..8fd2f7c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -GOARCH ?= arm64 +GOARCH ?= amd64 .PHONY: formolcli formolcli: fmt vet diff --git a/backupsession/server/backupsession_controller.go b/backupsession/server/backupsession_controller.go deleted file mode 100644 index dddb8a2..0000000 --- a/backupsession/server/backupsession_controller.go +++ /dev/null @@ -1,31 +0,0 @@ -package server - -import ( - "context" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log" - - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" -) - -type BackupSessionReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme -} - -func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - r.Log = log.FromContext(ctx) - r.Log.V(0).Info("Enter Reconcile with req", "req", req) - return ctrl.Result{}, nil -} - -// SetupWithManager sets up the controller with the Manager. -func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { - return ctrl.NewControllerManagedBy(mgr). - For(&formolv1alpha1.BackupSession{}). - Complete(r) -} diff --git a/cmd/backupsession.go b/cmd/backupsession.go index 19862ce..38e7495 100644 --- a/cmd/backupsession.go +++ b/cmd/backupsession.go @@ -6,7 +6,7 @@ package cmd import ( "fmt" "github.com/desmo999r/formolcli/backupsession" - "github.com/desmo999r/formolcli/backupsession/server" + "github.com/desmo999r/formolcli/controllers" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" ) @@ -30,7 +30,7 @@ var startServerCmd = &cobra.Command{ Short: "Start a BackupSession controller", Run: func(cmd *cobra.Command, args []string) { fmt.Println("starts backupsession controller") - server.StartServer() + controllers.StartServer() }, } diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go new file mode 100644 index 0000000..59bb5c9 --- /dev/null +++ b/controllers/backupsession_controller.go @@ -0,0 +1,112 @@ +package controllers + +import ( + "context" + "github.com/go-logr/logr" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "os" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + "time" + + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" +) + +type BackupSessionReconciler struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme +} + +func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Log = log.FromContext(ctx) + r.Log.V(0).Info("Enter Reconcile with req", "req", req) + + backupSession := formolv1alpha1.BackupSession{} + err := r.Get(ctx, req.NamespacedName, &backupSession) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + if len(backupSession.Status.Targets) == 0 { + // The main BackupSession controller hasn't assigned a backup task yet + // Wait a bit + r.Log.V(0).Info("No task has been assigned yet. Wait a bit...") + return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + } + // backupConf := formolv1alpha1.BackupConfiguration{} + // err := r.Get(ctx, client.ObjectKey { + // Namespace: backupSession.Spec.Ref.Namespace, + // Name: backupSession.Spec.Ref.Name, + // }, &backupConf) + // if err != nil { + // if errors.IsNotFound(err) { + // return ctrl.Result{}, nil + // } + // return ctrl.Result{}, err + // } + + targetName := os.Getenv(formolv1alpha1.TARGET_NAME) + // we don't want a copy because we will modify and update it. + currentTargetStatus := &(backupSession.Status.Targets[len(backupSession.Status.Targets)-1]) + if currentTargetStatus.TargetName == targetName { + // The current task is for us + switch currentTargetStatus.SessionState { + case formolv1alpha1.New: + r.Log.V(0).Info("New session, move to Initializing state") + currentTargetStatus.SessionState = formolv1alpha1.Init + err := r.Status().Update(ctx, &backupSession) + if err != nil { + r.Log.Error(err, "unable to update BackupSession status") + } + return ctrl.Result{}, err + case formolv1alpha1.Init: + r.Log.V(0).Info("Start to run the backup initializing steps is any") + // Runs the Steps functions in chroot env + result := formolv1alpha1.Running + currentTargetStatus.SessionState = result + err := r.Status().Update(ctx, &backupSession) + if err != nil { + r.Log.Error(err, "unable to update BackupSession status") + } + return ctrl.Result{}, err + case formolv1alpha1.Running: + r.Log.V(0).Info("Running state. Do the backup") + // Actually do the backup with restic + currentTargetStatus.SessionState = formolv1alpha1.Finalize + err := r.Status().Update(ctx, &backupSession) + if err != nil { + r.Log.Error(err, "unable to update BackupSession status") + } + return ctrl.Result{}, err + case formolv1alpha1.Finalize: + r.Log.V(0).Info("Backup is over. Run the finalize steps is any") + // Runs the finalize Steps functions in chroot env + if currentTargetStatus.SnapshotId == "" { + currentTargetStatus.SessionState = formolv1alpha1.Failure + } else { + currentTargetStatus.SessionState = formolv1alpha1.Success + } + err := r.Status().Update(ctx, &backupSession) + if err != nil { + r.Log.Error(err, "unable to update BackupSession status") + } + return ctrl.Result{}, err + case formolv1alpha1.Success: + case formolv1alpha1.Failure: + r.Log.V(0).Info("Backup is over") + } + } + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *BackupSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&formolv1alpha1.BackupSession{}). + Complete(r) +} diff --git a/backupsession/server/server.go b/controllers/server.go similarity index 98% rename from backupsession/server/server.go rename to controllers/server.go index 4708f45..8a38c72 100644 --- a/backupsession/server/server.go +++ b/controllers/server.go @@ -1,4 +1,4 @@ -package server +package controllers import ( "k8s.io/apimachinery/pkg/runtime" From 120b24b9aa5af975e58522c1142ec661dc2d059c Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Tue, 7 Feb 2023 23:28:54 +0100 Subject: [PATCH 04/37] No need to reschedule. --- controllers/backupsession_controller.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 59bb5c9..500ebbf 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -9,7 +9,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" - "time" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" ) @@ -35,8 +34,8 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques if len(backupSession.Status.Targets) == 0 { // The main BackupSession controller hasn't assigned a backup task yet // Wait a bit - r.Log.V(0).Info("No task has been assigned yet. Wait a bit...") - return ctrl.Result{RequeueAfter: 10 * time.Second}, nil + r.Log.V(0).Info("No task has been assigned yet. Wait for the next update...") + return ctrl.Result{}, nil } // backupConf := formolv1alpha1.BackupConfiguration{} // err := r.Get(ctx, client.ObjectKey { From 8d75e1450c7e76ebd4fba523cd1d25d53c258250 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Wed, 8 Feb 2023 12:49:05 +0100 Subject: [PATCH 05/37] Added Docker file --- .gitmodules | 3 +++ Dockerfile | 16 ++++++++++++++++ Makefile | 18 ++++++++++++++---- formol | 1 + go.mod | 2 +- 5 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 .gitmodules create mode 100644 Dockerfile create mode 160000 formol diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..dd965f7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "formol"] + path = formol + url = ../formol diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..27b86a2 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +# Build a small image +FROM golang:alpine3.17 AS builder + +ARG TARGETARCH +RUN echo "I'm building for $TARGETARCH" +WORKDIR /go/src +COPY . . +RUN GO111MODULE=on CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o bin/formolcli main.go + +FROM alpine:3.17 +RUN apk add --no-cache su-exec restic postgresql-client +COPY --from=builder /go/src/bin/formolcli /usr/local/bin + +# Command to run +ENTRYPOINT ["/usr/local/bin/formolcli"] +CMD ["--help"] diff --git a/Makefile b/Makefile index 8fd2f7c..3a8beca 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,10 @@ GOARCH ?= amd64 +GOOS ?= linux +IMG ?= desmo999r/formolcli:latest +BINDIR = ./bin -.PHONY: formolcli -formolcli: fmt vet - GO111MODULE=on CGO_ENABLED=0 GOOS=linux GOARCH=$(GOARCH) go build -o bin/formolcli main.go +$(BINDIR)/formolcli: fmt vet + GO111MODULE=on CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(BINDIR)/formolcli main.go .PHONY: fmt fmt: @@ -12,5 +14,13 @@ fmt: vet: go vet ./... +.PHONY: docker-build +docker-build: $(BINDIR)/formolcli + buildah build --disable-compression --format=docker --platform $(GOOS)/$(GOARCH) -t $(IMG) . + +.PHONY: docker-push +docker-push: docker-build + buildah push $(IMG) + .PHONY: all -all: formolcli +all: $(BINDIR)/formolcli docker-build diff --git a/formol b/formol new file mode 160000 index 0000000..06999eb --- /dev/null +++ b/formol @@ -0,0 +1 @@ +Subproject commit 06999eb5537620aba484ea47875c3aef80389ca2 diff --git a/go.mod b/go.mod index 0a4d79d..3a950b6 100644 --- a/go.mod +++ b/go.mod @@ -70,4 +70,4 @@ require ( sigs.k8s.io/yaml v1.3.0 // indirect ) -replace github.com/desmo999r/formol => ../formol +replace github.com/desmo999r/formol => ./formol From 53369058b9a6ac04d9b1d4d70d6047042faddfca Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Wed, 8 Feb 2023 17:58:54 +0100 Subject: [PATCH 06/37] Faster build --- Dockerfile | 2 -- Dockerfile.amd64 | 8 ++++++++ Makefile | 11 ++++++----- 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 Dockerfile.amd64 diff --git a/Dockerfile b/Dockerfile index 27b86a2..868bc0e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,6 @@ # Build a small image FROM golang:alpine3.17 AS builder -ARG TARGETARCH -RUN echo "I'm building for $TARGETARCH" WORKDIR /go/src COPY . . RUN GO111MODULE=on CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o bin/formolcli main.go diff --git a/Dockerfile.amd64 b/Dockerfile.amd64 new file mode 100644 index 0000000..b603055 --- /dev/null +++ b/Dockerfile.amd64 @@ -0,0 +1,8 @@ +# Build a small image +FROM alpine:3.17 +RUN apk add --no-cache su-exec restic postgresql-client +COPY ./bin/formolcli /usr/local/bin + +# Command to run +ENTRYPOINT ["/usr/local/bin/formolcli"] +CMD ["--help"] diff --git a/Makefile b/Makefile index 3a8beca..cd9fa58 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,10 @@ GOARCH ?= amd64 GOOS ?= linux -IMG ?= desmo999r/formolcli:latest +IMG ?= docker.io/desmo999r/formolcli:latest BINDIR = ./bin -$(BINDIR)/formolcli: fmt vet +.PHONY: formolcli +formolcli: fmt vet GO111MODULE=on CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(BINDIR)/formolcli main.go .PHONY: fmt @@ -15,12 +16,12 @@ vet: go vet ./... .PHONY: docker-build -docker-build: $(BINDIR)/formolcli - buildah build --disable-compression --format=docker --platform $(GOOS)/$(GOARCH) -t $(IMG) . +docker-build: formolcli + buildah bud --disable-compression --format=docker --platform $(GOOS)/$(GOARCH) --manifest $(IMG) Dockerfile.$(GOARCH) .PHONY: docker-push docker-push: docker-build buildah push $(IMG) .PHONY: all -all: $(BINDIR)/formolcli docker-build +all: formolcli docker-build From f3735e1409065b05b776195c8c575cb9d3b59ff1 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Wed, 8 Feb 2023 22:40:00 +0100 Subject: [PATCH 07/37] Small commit to start working on the Steps --- controllers/backupsession_controller.go | 2 ++ .../backupsession_controller_helpers.go | 27 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 controllers/backupsession_controller_helpers.go diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 500ebbf..38e407d 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -17,10 +17,12 @@ type BackupSessionReconciler struct { client.Client Log logr.Logger Scheme *runtime.Scheme + context.Context } func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { r.Log = log.FromContext(ctx) + r.Context = ctx r.Log.V(0).Info("Enter Reconcile with req", "req", req) backupSession := formolv1alpha1.BackupSession{} diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go new file mode 100644 index 0000000..9d4896f --- /dev/null +++ b/controllers/backupsession_controller_helpers.go @@ -0,0 +1,27 @@ +package controllers + +import ( + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "os" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func (r *BackupSessionReconciler) runInitBackupSteps(target formolv1alpha1.Target) error { + namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + for _, container := range target.Containers { + for _, step := range container.Steps { + if step.Finalize != nil && *step.Finalize == true { + continue + } + function := formolv1alpha1.Function{} + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: namespace, + Name: step.Name, + }, &function); err != nil { + r.Log.Error(err, "unable to get Function", "Function", step.Name) + return err + } + } + } + return nil +} From fd8df677c251c9c950166adf4464eaf528fee5c6 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Wed, 15 Feb 2023 13:55:34 +0100 Subject: [PATCH 08/37] Gather env variables from container.Env and container.EnvFrom --- .../backupsession_controller_helpers.go | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 9d4896f..e35f505 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -2,12 +2,95 @@ package controllers import ( formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + corev1 "k8s.io/api/core/v1" "os" "sigs.k8s.io/controller-runtime/pkg/client" ) +func (r *BackupSessionReconciler) getSecretData(name string) map[string][]byte { + secret := corev1.Secret{} + namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &secret); err != nil { + r.Log.Error(err, "unable to get Secret", "Secret", name) + return nil + } + return secret.Data +} + +func (r *BackupSessionReconciler) getEnvFromSecretKeyRef(name string, key string) string { + if data := r.getSecretData(name); data != nil { + return string(data[key]) + } + return "" +} + +func (r *BackupSessionReconciler) getConfigMapData(name string) map[string]string { + configMap := corev1.ConfigMap{} + namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &configMap); err != nil { + r.Log.Error(err, "unable to get ConfigMap", "configmap", name) + return nil + } + return configMap.Data +} + +func (r *BackupSessionReconciler) getEnvFromConfigMapKeyRef(name string, key string) string { + if data := r.getConfigMapData(name); data != nil { + return string(data[key]) + } + return "" +} + +func (r *BackupSessionReconciler) getFuncEnv(vars map[string]string, envVars []corev1.EnvVar) { + for _, env := range envVars { + if env.ValueFrom != nil { + if env.ValueFrom.ConfigMapKeyRef != nil { + vars[env.Name] = r.getEnvFromConfigMapKeyRef(env.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name, env.ValueFrom.ConfigMapKeyRef.Key) + } + if env.ValueFrom.SecretKeyRef != nil { + vars[env.Name] = r.getEnvFromSecretKeyRef(env.ValueFrom.SecretKeyRef.LocalObjectReference.Name, env.ValueFrom.SecretKeyRef.Key) + } + } + } +} + +func (r *BackupSessionReconciler) getEnvFromSecretEnvSource(vars map[string]string, name string) { + for key, value := range r.getSecretData(name) { + vars[key] = string(value) + } +} + +func (r *BackupSessionReconciler) getEnvFromConfigMapEnvSource(vars map[string]string, name string) { + for key, value := range r.getConfigMapData(name) { + vars[key] = value + } +} + +func (r *BackupSessionReconciler) getFuncEnvFrom(vars map[string]string, envVars []corev1.EnvFromSource) { + for _, env := range envVars { + if env.ConfigMapRef != nil { + r.getEnvFromConfigMapEnvSource(vars, env.ConfigMapRef.LocalObjectReference.Name) + } + if env.SecretRef != nil { + r.getEnvFromSecretEnvSource(vars, env.SecretRef.LocalObjectReference.Name) + } + } +} + +func (r *BackupSessionReconciler) getFuncVars(function formolv1alpha1.Function, vars map[string]string) { + r.getFuncEnvFrom(vars, function.Spec.EnvFrom) + r.getFuncEnv(vars, function.Spec.Env) +} + func (r *BackupSessionReconciler) runInitBackupSteps(target formolv1alpha1.Target) error { namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + r.Log.V(0).Info("start to run the backup initializing steps it any") for _, container := range target.Containers { for _, step := range container.Steps { if step.Finalize != nil && *step.Finalize == true { @@ -21,6 +104,9 @@ func (r *BackupSessionReconciler) runInitBackupSteps(target formolv1alpha1.Targe r.Log.Error(err, "unable to get Function", "Function", step.Name) return err } + vars := make(map[string]string) + r.getFuncVars(function, vars) + } } return nil From 7f227c1ec4d73ec9e7239b94b048584348f046b9 Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Mon, 20 Feb 2023 10:30:31 +0100 Subject: [PATCH 09/37] Should backup paths --- Dockerfile.amd64 | 2 +- Makefile | 2 +- controllers/backupsession_controller.go | 85 +++++---- .../backupsession_controller_helpers.go | 167 +++++++++++++++++- 4 files changed, 215 insertions(+), 41 deletions(-) diff --git a/Dockerfile.amd64 b/Dockerfile.amd64 index b603055..1df3e77 100644 --- a/Dockerfile.amd64 +++ b/Dockerfile.amd64 @@ -1,5 +1,5 @@ # Build a small image -FROM alpine:3.17 +FROM --platform=linux/amd64 alpine:3 RUN apk add --no-cache su-exec restic postgresql-client COPY ./bin/formolcli /usr/local/bin diff --git a/Makefile b/Makefile index cd9fa58..aeece64 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ vet: .PHONY: docker-build docker-build: formolcli - buildah bud --disable-compression --format=docker --platform $(GOOS)/$(GOARCH) --manifest $(IMG) Dockerfile.$(GOARCH) + buildah bud --tag $(IMG) Dockerfile.$(GOARCH) .PHONY: docker-push docker-push: docker-build diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 38e407d..0bbd590 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -4,11 +4,14 @@ import ( "context" "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "os" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + "strings" + "time" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" ) @@ -23,7 +26,6 @@ type BackupSessionReconciler struct { func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { r.Log = log.FromContext(ctx) r.Context = ctx - r.Log.V(0).Info("Enter Reconcile with req", "req", req) backupSession := formolv1alpha1.BackupSession{} err := r.Get(ctx, req.NamespacedName, &backupSession) @@ -39,70 +41,79 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques r.Log.V(0).Info("No task has been assigned yet. Wait for the next update...") return ctrl.Result{}, nil } - // backupConf := formolv1alpha1.BackupConfiguration{} - // err := r.Get(ctx, client.ObjectKey { - // Namespace: backupSession.Spec.Ref.Namespace, - // Name: backupSession.Spec.Ref.Name, - // }, &backupConf) - // if err != nil { - // if errors.IsNotFound(err) { - // return ctrl.Result{}, nil - // } - // return ctrl.Result{}, err - // } + backupConf := formolv1alpha1.BackupConfiguration{} + err = r.Get(ctx, client.ObjectKey{ + Namespace: backupSession.Spec.Ref.Namespace, + Name: backupSession.Spec.Ref.Name, + }, &backupConf) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } targetName := os.Getenv(formolv1alpha1.TARGET_NAME) // we don't want a copy because we will modify and update it. currentTargetStatus := &(backupSession.Status.Targets[len(backupSession.Status.Targets)-1]) + currentTarget := backupConf.Spec.Targets[len(backupSession.Status.Targets)-1] + var result error if currentTargetStatus.TargetName == targetName { // The current task is for us + var newSessionState formolv1alpha1.SessionState switch currentTargetStatus.SessionState { case formolv1alpha1.New: r.Log.V(0).Info("New session, move to Initializing state") - currentTargetStatus.SessionState = formolv1alpha1.Init - err := r.Status().Update(ctx, &backupSession) - if err != nil { - r.Log.Error(err, "unable to update BackupSession status") - } - return ctrl.Result{}, err + newSessionState = formolv1alpha1.Init case formolv1alpha1.Init: r.Log.V(0).Info("Start to run the backup initializing steps is any") // Runs the Steps functions in chroot env - result := formolv1alpha1.Running - currentTargetStatus.SessionState = result - err := r.Status().Update(ctx, &backupSession) - if err != nil { - r.Log.Error(err, "unable to update BackupSession status") + if result = r.runInitializeBackupSteps(currentTarget); result != nil { + r.Log.Error(result, "unable to run the initialization steps") + newSessionState = formolv1alpha1.Finalize + } else { + newSessionState = formolv1alpha1.Running } - return ctrl.Result{}, err case formolv1alpha1.Running: r.Log.V(0).Info("Running state. Do the backup") // Actually do the backup with restic - currentTargetStatus.SessionState = formolv1alpha1.Finalize - err := r.Status().Update(ctx, &backupSession) - if err != nil { - r.Log.Error(err, "unable to update BackupSession status") + backupPaths := strings.Split(os.Getenv(formolv1alpha1.BACKUP_PATHS), string(os.PathListSeparator)) + if backupResult, result := r.backupPaths(backupSession.Name, backupPaths); result != nil { + r.Log.Error(result, "unable to backup paths", "target name", targetName, "paths", backupPaths) + } else { + r.Log.V(0).Info("Backup of the paths is over", "target name", targetName, "paths", backupPaths, + "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) + currentTargetStatus.SnapshotId = backupResult.SnapshotId + currentTargetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(currentTargetStatus.StartTime.Time)} } - return ctrl.Result{}, err + newSessionState = formolv1alpha1.Finalize case formolv1alpha1.Finalize: r.Log.V(0).Info("Backup is over. Run the finalize steps is any") // Runs the finalize Steps functions in chroot env - if currentTargetStatus.SnapshotId == "" { - currentTargetStatus.SessionState = formolv1alpha1.Failure - } else { - currentTargetStatus.SessionState = formolv1alpha1.Success + if result = r.runFinalizeBackupSteps(currentTarget); result != nil { + r.Log.Error(err, "unable to run finalize steps") } + if currentTargetStatus.SnapshotId == "" { + newSessionState = formolv1alpha1.Failure + } else { + newSessionState = formolv1alpha1.Success + } + case formolv1alpha1.Success: + r.Log.V(0).Info("Backup is over") + case formolv1alpha1.Failure: + r.Log.V(0).Info("Backup is over") + } + if newSessionState != "" { + currentTargetStatus.SessionState = newSessionState err := r.Status().Update(ctx, &backupSession) if err != nil { r.Log.Error(err, "unable to update BackupSession status") } return ctrl.Result{}, err - case formolv1alpha1.Success: - case formolv1alpha1.Failure: - r.Log.V(0).Info("Backup is over") + } } - return ctrl.Result{}, nil + return ctrl.Result{}, result } // SetupWithManager sets up the controller with the Manager. diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index e35f505..5a8f61b 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -1,12 +1,39 @@ package controllers import ( + "bufio" + "bytes" + "encoding/json" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "io" + "io/ioutil" corev1 "k8s.io/api/core/v1" "os" + "os/exec" + "path/filepath" + "regexp" "sigs.k8s.io/controller-runtime/pkg/client" + "strconv" ) +var ( + REPOSITORY string + PASSWORD_FILE string + AWS_ACCESS_KEY_ID string + AWS_SECRET_ACCESS_KEY string +) + +const ( + RESTIC_EXEC = "/usr/bin/restic" +) + +func init() { + REPOSITORY = os.Getenv(formolv1alpha1.RESTIC_REPOSITORY) + PASSWORD_FILE = os.Getenv(formolv1alpha1.RESTIC_PASSWORD) + AWS_ACCESS_KEY_ID = os.Getenv(formolv1alpha1.AWS_ACCESS_KEY_ID) + AWS_SECRET_ACCESS_KEY = os.Getenv(formolv1alpha1.AWS_SECRET_ACCESS_KEY) +} + func (r *BackupSessionReconciler) getSecretData(name string) map[string][]byte { secret := corev1.Secret{} namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) @@ -88,12 +115,14 @@ func (r *BackupSessionReconciler) getFuncVars(function formolv1alpha1.Function, r.getFuncEnv(vars, function.Spec.Env) } -func (r *BackupSessionReconciler) runInitBackupSteps(target formolv1alpha1.Target) error { +func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target formolv1alpha1.Target) error { namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) r.Log.V(0).Info("start to run the backup initializing steps it any") + // For every container listed in the target, run the initialization steps for _, container := range target.Containers { + // Runs the steps one after the other for _, step := range container.Steps { - if step.Finalize != nil && *step.Finalize == true { + if step.Finalize != nil && *step.Finalize == true && initializeSteps { continue } function := formolv1alpha1.Function{} @@ -107,7 +136,141 @@ func (r *BackupSessionReconciler) runInitBackupSteps(target formolv1alpha1.Targe vars := make(map[string]string) r.getFuncVars(function, vars) + // Loop through the function.Spec.Command arguments to replace ${ARG}|$(ARG)|$ARG + // with the environment variable value + pattern := regexp.MustCompile(`^\$(\{(?P\w+)\}|^\$\((?P\w+)\)|(?P\w+))$`) + for i, arg := range function.Spec.Command[1:] { + if pattern.MatchString(arg) { + arg = pattern.ReplaceAllString(arg, "$env") + function.Spec.Command[i] = vars[arg] + } + } + if err := r.runTargetContainerChroot(function.Spec.Command[0], + function.Spec.Command[1:]...); err != nil { + r.Log.Error(err, "unable to run command", "command", function.Spec.Command) + return err + } } } return nil } + +func (r *BackupSessionReconciler) runFinalizeBackupSteps(target formolv1alpha1.Target) error { + return r.runBackupSteps(true, target) +} + +func (r *BackupSessionReconciler) runInitializeBackupSteps(target formolv1alpha1.Target) error { + return r.runBackupSteps(true, target) +} + +func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args ...string) error { + env := regexp.MustCompile(`/proc/[0-9]+/env`) + if err := filepath.Walk("/proc", func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + // Skip process 1 and ourself + if info.IsDir() && (info.Name() == "1" || info.Name() == strconv.Itoa(os.Getpid())) { + return filepath.SkipDir + } + // Found an environ file. Start looking for TARGETCONTAINER_TAG + if env.MatchString(path) { + r.Log.V(0).Info("Looking for tag", "file", path, "TARGETCONTAINER_TAG", formolv1alpha1.TARGETCONTAINER_TAG) + content, err := ioutil.ReadFile(path) + // cannot read environ file. not the process we want to backup + if err != nil { + r.Log.Error(err, "unable to read file", "file", path) + return filepath.SkipDir + } + // Loops over the process environement variable looking for TARGETCONTAINER_TAG + for _, env := range bytes.Split(content, []byte{'\000'}) { + matched, err := regexp.Match(formolv1alpha1.TARGETCONTAINER_TAG, env) + if err != nil { + r.Log.Error(err, "unable to regexp", "env", string(env)) + return err + } + if matched { + // Found the right process. Now run the command in its 'root' + r.Log.V(0).Info("Found the tag", "file", path) + root := filepath.Join(filepath.Dir(path), "root") + if _, err := filepath.EvalSymlinks(root); err != nil { + r.Log.Error(err, "cannot EvalSymlink.") + return err + } + r.Log.V(0).Info("running cmd in chroot", "path", root) + cmd := exec.Command("chroot", append([]string{root, runCmd}, args...)...) + stdout, _ := cmd.StdoutPipe() + stderr, _ := cmd.StderrPipe() + _ = cmd.Start() + + scanner := bufio.NewScanner(io.MultiReader(stdout, stderr)) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + r.Log.V(0).Info("cmd output", "output", scanner.Text()) + } + + return cmd.Wait() + } + } + } + return nil + }); err != nil { + r.Log.Error(err, "cannot walk /proc") + return err + } + return nil +} + +func (r *BackupSessionReconciler) checkRepo(repo string) error { + r.Log.V(0).Info("Checking repo", "repo", repo) + if err := exec.Command(RESTIC_EXEC, "unlock", "-r", repo).Run(); err != nil { + r.Log.Error(err, "unable to unlock repo", "repo", repo) + return err + } + output, err := exec.Command(RESTIC_EXEC, "check", "-r", repo).CombinedOutput() + if err != nil { + r.Log.V(0).Info("Initializing new repo", "repo", repo) + output, err = exec.Command(RESTIC_EXEC, "init", "-r", repo).CombinedOutput() + if err != nil { + r.Log.Error(err, "something went wrong during repo init", "output", output) + } + } + return err +} + +type BackupResult struct { + SnapshotId string + Duration float64 +} + +func (r *BackupSessionReconciler) backupPaths(tag string, paths []string) (result BackupResult, err error) { + if err = r.checkRepo(REPOSITORY); err != nil { + r.Log.Error(err, "unable to setup repo", "repo", REPOSITORY) + return + } + r.Log.V(0).Info("backing up paths", "paths", paths) + cmd := exec.Command(RESTIC_EXEC, append([]string{"backup", "--json", "--tag", tag, "-r", REPOSITORY}, paths...)...) + stdout, _ := cmd.StdoutPipe() + stderr, _ := cmd.StderrPipe() + _ = cmd.Start() + + scanner := bufio.NewScanner(io.MultiReader(stdout, stderr)) + scanner.Split(bufio.ScanLines) + var data map[string]interface{} + for scanner.Scan() { + if err := json.Unmarshal(scanner.Bytes(), &data); err != nil { + r.Log.Error(err, "unable to unmarshal json", "data", scanner.Text()) + continue + } + switch data["message_type"].(string) { + case "summary": + result.SnapshotId = data["snapshot_id"].(string) + result.Duration = data["total_duration"].(float64) + case "status": + r.Log.V(0).Info("backup running", "percent done", data["percent_done"].(float64)) + } + } + + err = cmd.Wait() + return +} From 7773fadceadf974dc07e58232c3e8ee60f43f8d8 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Tue, 21 Feb 2023 01:08:07 +0100 Subject: [PATCH 10/37] /proc/../environ and not /proc/../env --- .../backupsession_controller_helpers.go | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 5a8f61b..a96748e 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -117,12 +117,12 @@ func (r *BackupSessionReconciler) getFuncVars(function formolv1alpha1.Function, func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target formolv1alpha1.Target) error { namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) - r.Log.V(0).Info("start to run the backup initializing steps it any") + r.Log.V(0).Info("start to run the backup steps it any") // For every container listed in the target, run the initialization steps for _, container := range target.Containers { // Runs the steps one after the other for _, step := range container.Steps { - if step.Finalize != nil && *step.Finalize == true && initializeSteps { + if (initializeSteps == true && step.Finalize != nil && *step.Finalize == true) || (initializeSteps == false && (step.Finalize == nil || step.Finalize != nil && *step.Finalize == false)) { continue } function := formolv1alpha1.Function{} @@ -133,6 +133,7 @@ func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target fo r.Log.Error(err, "unable to get Function", "Function", step.Name) return err } + r.Log.V(1).Info("About to run Function", "Function", step.Name) vars := make(map[string]string) r.getFuncVars(function, vars) @@ -155,16 +156,22 @@ func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target fo return nil } +// Run the initializing steps in the INITIALIZING state of the controller +// before actualy doing the backup in the RUNNING state func (r *BackupSessionReconciler) runFinalizeBackupSteps(target formolv1alpha1.Target) error { - return r.runBackupSteps(true, target) + return r.runBackupSteps(false, target) } +// Run the finalizing steps in the FINALIZE state of the controller +// after the backup in the RUNNING state. +// The finalize happens whatever the result of the backup. func (r *BackupSessionReconciler) runInitializeBackupSteps(target formolv1alpha1.Target) error { return r.runBackupSteps(true, target) } +// Runs the given command in the target container chroot func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args ...string) error { - env := regexp.MustCompile(`/proc/[0-9]+/env`) + env := regexp.MustCompile(`/proc/[0-9]+/environ`) if err := filepath.Walk("/proc", func(path string, info os.FileInfo, err error) error { if err != nil { return nil @@ -175,11 +182,9 @@ func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args . } // Found an environ file. Start looking for TARGETCONTAINER_TAG if env.MatchString(path) { - r.Log.V(0).Info("Looking for tag", "file", path, "TARGETCONTAINER_TAG", formolv1alpha1.TARGETCONTAINER_TAG) content, err := ioutil.ReadFile(path) // cannot read environ file. not the process we want to backup if err != nil { - r.Log.Error(err, "unable to read file", "file", path) return filepath.SkipDir } // Loops over the process environement variable looking for TARGETCONTAINER_TAG @@ -225,7 +230,6 @@ func (r *BackupSessionReconciler) checkRepo(repo string) error { r.Log.V(0).Info("Checking repo", "repo", repo) if err := exec.Command(RESTIC_EXEC, "unlock", "-r", repo).Run(); err != nil { r.Log.Error(err, "unable to unlock repo", "repo", repo) - return err } output, err := exec.Command(RESTIC_EXEC, "check", "-r", repo).CombinedOutput() if err != nil { From e3aa5f3844eb17660180b68e17b239c1b4985f1e Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Sun, 26 Feb 2023 01:35:10 +0100 Subject: [PATCH 11/37] got job backup to work --- controllers/backupsession_controller.go | 27 ++++-- .../backupsession_controller_helpers.go | 87 +++++++++++++------ formol | 2 +- 3 files changed, 80 insertions(+), 36 deletions(-) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 0bbd590..13f7cf8 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -77,14 +77,25 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques case formolv1alpha1.Running: r.Log.V(0).Info("Running state. Do the backup") // Actually do the backup with restic - backupPaths := strings.Split(os.Getenv(formolv1alpha1.BACKUP_PATHS), string(os.PathListSeparator)) - if backupResult, result := r.backupPaths(backupSession.Name, backupPaths); result != nil { - r.Log.Error(result, "unable to backup paths", "target name", targetName, "paths", backupPaths) - } else { - r.Log.V(0).Info("Backup of the paths is over", "target name", targetName, "paths", backupPaths, - "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) - currentTargetStatus.SnapshotId = backupResult.SnapshotId - currentTargetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(currentTargetStatus.StartTime.Time)} + switch currentTarget.BackupType { + case formolv1alpha1.JobKind: + if backupResult, err := r.backupJob(backupSession.Name, currentTarget); err != nil { + r.Log.Error(err, "unable to run backup job", "target", targetName) + } else { + r.Log.V(0).Info("Backup Job is over", "target", targetName, "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) + currentTargetStatus.SnapshotId = backupResult.SnapshotId + currentTargetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(currentTargetStatus.StartTime.Time)} + } + case formolv1alpha1.OnlineKind: + backupPaths := strings.Split(os.Getenv(formolv1alpha1.BACKUP_PATHS), string(os.PathListSeparator)) + if backupResult, result := r.backupPaths(backupSession.Name, backupPaths); result != nil { + r.Log.Error(result, "unable to backup paths", "target name", targetName, "paths", backupPaths) + } else { + r.Log.V(0).Info("Backup of the paths is over", "target name", targetName, "paths", backupPaths, + "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) + currentTargetStatus.SnapshotId = backupResult.SnapshotId + currentTargetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(currentTargetStatus.StartTime.Time)} + } } newSessionState = formolv1alpha1.Finalize case formolv1alpha1.Finalize: diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index a96748e..d0321e1 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -83,6 +83,8 @@ func (r *BackupSessionReconciler) getFuncEnv(vars map[string]string, envVars []c if env.ValueFrom.SecretKeyRef != nil { vars[env.Name] = r.getEnvFromSecretKeyRef(env.ValueFrom.SecretKeyRef.LocalObjectReference.Name, env.ValueFrom.SecretKeyRef.Key) } + } else { + vars[env.Name] = env.Value } } } @@ -115,8 +117,40 @@ func (r *BackupSessionReconciler) getFuncVars(function formolv1alpha1.Function, r.getFuncEnv(vars, function.Spec.Env) } -func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target formolv1alpha1.Target) error { +func (r *BackupSessionReconciler) runFunction(name string) error { namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + function := formolv1alpha1.Function{} + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &function); err != nil { + r.Log.Error(err, "unable to get Function", "Function", name) + return err + } + vars := make(map[string]string) + r.getFuncVars(function, vars) + + r.Log.V(0).Info("function vars", "vars", vars) + // Loop through the function.Spec.Command arguments to replace ${ARG}|$(ARG)|$ARG + // with the environment variable value + pattern := regexp.MustCompile(`^\$\((?P\w+)\)$`) + for i, arg := range function.Spec.Args { + if pattern.MatchString(arg) { + r.Log.V(0).Info("arg matches $()", "arg", arg) + arg = pattern.ReplaceAllString(arg, "$env") + function.Spec.Args[i] = vars[arg] + } + } + r.Log.V(1).Info("about to run Function", "Function", name, "command", function.Spec.Command, "args", function.Spec.Args) + if err := r.runTargetContainerChroot(function.Spec.Command[0], + function.Spec.Args...); err != nil { + r.Log.Error(err, "unable to run command", "command", function.Spec.Command) + return err + } + return nil +} + +func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target formolv1alpha1.Target) error { r.Log.V(0).Info("start to run the backup steps it any") // For every container listed in the target, run the initialization steps for _, container := range target.Containers { @@ -125,32 +159,7 @@ func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target fo if (initializeSteps == true && step.Finalize != nil && *step.Finalize == true) || (initializeSteps == false && (step.Finalize == nil || step.Finalize != nil && *step.Finalize == false)) { continue } - function := formolv1alpha1.Function{} - if err := r.Get(r.Context, client.ObjectKey{ - Namespace: namespace, - Name: step.Name, - }, &function); err != nil { - r.Log.Error(err, "unable to get Function", "Function", step.Name) - return err - } - r.Log.V(1).Info("About to run Function", "Function", step.Name) - vars := make(map[string]string) - r.getFuncVars(function, vars) - - // Loop through the function.Spec.Command arguments to replace ${ARG}|$(ARG)|$ARG - // with the environment variable value - pattern := regexp.MustCompile(`^\$(\{(?P\w+)\}|^\$\((?P\w+)\)|(?P\w+))$`) - for i, arg := range function.Spec.Command[1:] { - if pattern.MatchString(arg) { - arg = pattern.ReplaceAllString(arg, "$env") - function.Spec.Command[i] = vars[arg] - } - } - if err := r.runTargetContainerChroot(function.Spec.Command[0], - function.Spec.Command[1:]...); err != nil { - r.Log.Error(err, "unable to run command", "command", function.Spec.Command) - return err - } + return r.runFunction(step.Name) } } return nil @@ -278,3 +287,27 @@ func (r *BackupSessionReconciler) backupPaths(tag string, paths []string) (resul err = cmd.Wait() return } + +func (r *BackupSessionReconciler) backupJob(tag string, target formolv1alpha1.Target) (result BackupResult, err error) { + paths := []string{} + for _, container := range target.Containers { + for _, job := range container.Job { + if err = r.runFunction(job.Name); err != nil { + r.Log.Error(err, "unable to run job") + return + } + + } + addPath := true + for _, path := range paths { + if path == container.SharePath { + addPath = false + } + } + if addPath { + paths = append(paths, container.SharePath) + } + } + result, err = r.backupPaths(tag, paths) + return +} diff --git a/formol b/formol index 06999eb..b42bd46 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit 06999eb5537620aba484ea47875c3aef80389ca2 +Subproject commit b42bd46efe822ddad250448d7ed2578efe5460f7 From d91f1e3f5d70fd25f2f3598f84a71f1bda3f950b Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Mon, 27 Feb 2023 00:54:43 +0100 Subject: [PATCH 12/37] Reworked the scheduling. Tasks are run by SessionState now and not by Target --- controllers/backupsession_controller.go | 148 ++++++++++-------- .../backupsession_controller_helpers.go | 11 +- formol | 2 +- 3 files changed, 91 insertions(+), 70 deletions(-) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 13f7cf8..4b29a1c 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -53,77 +53,93 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques return ctrl.Result{}, err } - targetName := os.Getenv(formolv1alpha1.TARGET_NAME) + // targetName := os.Getenv(formolv1alpha1.TARGET_NAME) // we don't want a copy because we will modify and update it. - currentTargetStatus := &(backupSession.Status.Targets[len(backupSession.Status.Targets)-1]) - currentTarget := backupConf.Spec.Targets[len(backupSession.Status.Targets)-1] + var target formolv1alpha1.Target + var targetStatus *formolv1alpha1.TargetStatus var result error - if currentTargetStatus.TargetName == targetName { - // The current task is for us - var newSessionState formolv1alpha1.SessionState - switch currentTargetStatus.SessionState { - case formolv1alpha1.New: - r.Log.V(0).Info("New session, move to Initializing state") - newSessionState = formolv1alpha1.Init - case formolv1alpha1.Init: - r.Log.V(0).Info("Start to run the backup initializing steps is any") - // Runs the Steps functions in chroot env - if result = r.runInitializeBackupSteps(currentTarget); result != nil { - r.Log.Error(result, "unable to run the initialization steps") - newSessionState = formolv1alpha1.Finalize - } else { - newSessionState = formolv1alpha1.Running - } - case formolv1alpha1.Running: - r.Log.V(0).Info("Running state. Do the backup") - // Actually do the backup with restic - switch currentTarget.BackupType { - case formolv1alpha1.JobKind: - if backupResult, err := r.backupJob(backupSession.Name, currentTarget); err != nil { - r.Log.Error(err, "unable to run backup job", "target", targetName) - } else { - r.Log.V(0).Info("Backup Job is over", "target", targetName, "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) - currentTargetStatus.SnapshotId = backupResult.SnapshotId - currentTargetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(currentTargetStatus.StartTime.Time)} - } - case formolv1alpha1.OnlineKind: - backupPaths := strings.Split(os.Getenv(formolv1alpha1.BACKUP_PATHS), string(os.PathListSeparator)) - if backupResult, result := r.backupPaths(backupSession.Name, backupPaths); result != nil { - r.Log.Error(result, "unable to backup paths", "target name", targetName, "paths", backupPaths) - } else { - r.Log.V(0).Info("Backup of the paths is over", "target name", targetName, "paths", backupPaths, - "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) - currentTargetStatus.SnapshotId = backupResult.SnapshotId - currentTargetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(currentTargetStatus.StartTime.Time)} - } - } - newSessionState = formolv1alpha1.Finalize - case formolv1alpha1.Finalize: - r.Log.V(0).Info("Backup is over. Run the finalize steps is any") - // Runs the finalize Steps functions in chroot env - if result = r.runFinalizeBackupSteps(currentTarget); result != nil { - r.Log.Error(err, "unable to run finalize steps") - } - if currentTargetStatus.SnapshotId == "" { - newSessionState = formolv1alpha1.Failure - } else { - newSessionState = formolv1alpha1.Success - } - case formolv1alpha1.Success: - r.Log.V(0).Info("Backup is over") - case formolv1alpha1.Failure: - r.Log.V(0).Info("Backup is over") - } - if newSessionState != "" { - currentTargetStatus.SessionState = newSessionState - err := r.Status().Update(ctx, &backupSession) - if err != nil { - r.Log.Error(err, "unable to update BackupSession status") - } - return ctrl.Result{}, err + targetName := os.Getenv(formolv1alpha1.TARGET_NAME) + for i, t := range backupConf.Spec.Targets { + if t.TargetName == targetName { + target = t + targetStatus = &(backupSession.Status.Targets[i]) + break } } + + var newSessionState formolv1alpha1.SessionState + switch targetStatus.SessionState { + case formolv1alpha1.New: + // New session move to Initializing + r.Log.V(0).Info("New session. Move to Initializing state") + newSessionState = formolv1alpha1.Initializing + case formolv1alpha1.Initializing: + // Run the initializing Steps and then move to Initialized or Failure + r.Log.V(0).Info("Start to run the backup initializing steps is any") + // Runs the Steps functions in chroot env + if err := r.runInitializeBackupSteps(target); err != nil { + r.Log.Error(err, "unable to run the initialization steps") + newSessionState = formolv1alpha1.Failure + } else { + r.Log.V(0).Info("Done with the initializing Steps. Move to Initialized state") + newSessionState = formolv1alpha1.Initialized + } + case formolv1alpha1.Running: + // Actually do the backup and move to Waiting or Failure + r.Log.V(0).Info("Running state. Do the backup") + // Actually do the backup with restic + newSessionState = formolv1alpha1.Waiting + switch target.BackupType { + case formolv1alpha1.JobKind: + if backupResult, err := r.backupJob(backupSession.Name, target); err != nil { + r.Log.Error(err, "unable to run backup job", "target", targetName) + newSessionState = formolv1alpha1.Failure + } else { + r.Log.V(0).Info("Backup Job is over", "target", targetName, "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) + targetStatus.SnapshotId = backupResult.SnapshotId + targetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(targetStatus.StartTime.Time)} + } + case formolv1alpha1.OnlineKind: + backupPaths := strings.Split(os.Getenv(formolv1alpha1.BACKUP_PATHS), string(os.PathListSeparator)) + if backupResult, result := r.backupPaths(backupSession.Name, backupPaths); result != nil { + r.Log.Error(result, "unable to backup paths", "target name", targetName, "paths", backupPaths) + newSessionState = formolv1alpha1.Failure + } else { + r.Log.V(0).Info("Backup of the paths is over", "target name", targetName, "paths", backupPaths, + "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) + targetStatus.SnapshotId = backupResult.SnapshotId + targetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(targetStatus.StartTime.Time)} + } + } + r.Log.V(0).Info("Backup is over and is a success. Move to Waiting state") + case formolv1alpha1.Finalize: + // Run the finalize Steps and move to Success or Failure + r.Log.V(0).Info("Backup is over. Run the finalize steps is any") + // Runs the finalize Steps functions in chroot env + if result = r.runFinalizeBackupSteps(target); result != nil { + r.Log.Error(err, "unable to run finalize steps") + } + if targetStatus.SnapshotId == "" { + newSessionState = formolv1alpha1.Failure + } else { + newSessionState = formolv1alpha1.Success + } + case formolv1alpha1.Success: + // Target backup is a success + r.Log.V(0).Info("Backup was a success") + case formolv1alpha1.Failure: + // Target backup is a failure + } + if newSessionState != "" { + targetStatus.SessionState = newSessionState + err := r.Status().Update(ctx, &backupSession) + if err != nil { + r.Log.Error(err, "unable to update BackupSession status") + } + return ctrl.Result{}, err + } + return ctrl.Result{}, result } diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index d0321e1..fb03a4d 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -6,6 +6,7 @@ import ( "encoding/json" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "io" + "io/fs" "io/ioutil" corev1 "k8s.io/api/core/v1" "os" @@ -181,7 +182,7 @@ func (r *BackupSessionReconciler) runInitializeBackupSteps(target formolv1alpha1 // Runs the given command in the target container chroot func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args ...string) error { env := regexp.MustCompile(`/proc/[0-9]+/environ`) - if err := filepath.Walk("/proc", func(path string, info os.FileInfo, err error) error { + if err := filepath.WalkDir("/proc", func(path string, info fs.DirEntry, err error) error { if err != nil { return nil } @@ -194,7 +195,7 @@ func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args . content, err := ioutil.ReadFile(path) // cannot read environ file. not the process we want to backup if err != nil { - return filepath.SkipDir + return fs.SkipDir } // Loops over the process environement variable looking for TARGETCONTAINER_TAG for _, env := range bytes.Split(content, []byte{'\000'}) { @@ -223,7 +224,11 @@ func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args . r.Log.V(0).Info("cmd output", "output", scanner.Text()) } - return cmd.Wait() + if err := cmd.Wait(); err != nil { + return err + } else { + return filepath.SkipAll + } } } } diff --git a/formol b/formol index b42bd46..b5a217b 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit b42bd46efe822ddad250448d7ed2578efe5460f7 +Subproject commit b5a217bc3a536095084b48e19f935bc1508baedf From 319e226a30378d8caa182cd7b0582285844c3d77 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Mon, 6 Mar 2023 23:05:57 +0100 Subject: [PATCH 13/37] The BackupSession controller in the sidecar should get the latest informtation about the repository everytime it reconciles because it might change --- controllers/backupsession_controller.go | 9 ++- .../backupsession_controller_helpers.go | 59 +++++++++++-------- formol | 2 +- 3 files changed, 44 insertions(+), 26 deletions(-) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 4b29a1c..77051a3 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -21,6 +21,7 @@ type BackupSessionReconciler struct { Log logr.Logger Scheme *runtime.Scheme context.Context + Namespace string } func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -52,8 +53,8 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } return ctrl.Result{}, err } + r.Namespace = backupConf.Namespace - // targetName := os.Getenv(formolv1alpha1.TARGET_NAME) // we don't want a copy because we will modify and update it. var target formolv1alpha1.Target var targetStatus *formolv1alpha1.TargetStatus @@ -68,6 +69,12 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } } + // Do preliminary checks with the repository + if err = r.setResticEnv(backupConf); err != nil { + r.Log.Error(err, "unable to set restic env") + return ctrl.Result{}, err + } + var newSessionState formolv1alpha1.SessionState switch targetStatus.SessionState { case formolv1alpha1.New: diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index fb03a4d..f342a58 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "encoding/json" + "fmt" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "io" "io/fs" @@ -15,26 +16,13 @@ import ( "regexp" "sigs.k8s.io/controller-runtime/pkg/client" "strconv" -) - -var ( - REPOSITORY string - PASSWORD_FILE string - AWS_ACCESS_KEY_ID string - AWS_SECRET_ACCESS_KEY string + "strings" ) const ( RESTIC_EXEC = "/usr/bin/restic" ) -func init() { - REPOSITORY = os.Getenv(formolv1alpha1.RESTIC_REPOSITORY) - PASSWORD_FILE = os.Getenv(formolv1alpha1.RESTIC_PASSWORD) - AWS_ACCESS_KEY_ID = os.Getenv(formolv1alpha1.AWS_ACCESS_KEY_ID) - AWS_SECRET_ACCESS_KEY = os.Getenv(formolv1alpha1.AWS_SECRET_ACCESS_KEY) -} - func (r *BackupSessionReconciler) getSecretData(name string) map[string][]byte { secret := corev1.Secret{} namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) @@ -118,6 +106,29 @@ func (r *BackupSessionReconciler) getFuncVars(function formolv1alpha1.Function, r.getFuncEnv(vars, function.Spec.Env) } +func (r *BackupSessionReconciler) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { + repo := formolv1alpha1.Repo{} + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: backupConf.Namespace, + Name: backupConf.Spec.Repository, + }, &repo); err != nil { + r.Log.Error(err, "unable to get repo") + return err + } + if repo.Spec.Backend.S3 != nil { + os.Setenv(formolv1alpha1.RESTIC_REPOSITORY, fmt.Sprintf("s3:http://%s/%s/%s-%s", + repo.Spec.Backend.S3.Server, + repo.Spec.Backend.S3.Bucket, + strings.ToUpper(backupConf.Namespace), + strings.ToLower(backupConf.Name))) + data := r.getSecretData(repo.Spec.RepositorySecrets) + os.Setenv(formolv1alpha1.AWS_SECRET_ACCESS_KEY, string(data[formolv1alpha1.AWS_SECRET_ACCESS_KEY])) + os.Setenv(formolv1alpha1.AWS_ACCESS_KEY_ID, string(data[formolv1alpha1.AWS_ACCESS_KEY_ID])) + os.Setenv(formolv1alpha1.RESTIC_PASSWORD, string(data[formolv1alpha1.RESTIC_PASSWORD])) + } + return nil +} + func (r *BackupSessionReconciler) runFunction(name string) error { namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) function := formolv1alpha1.Function{} @@ -240,15 +251,15 @@ func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args . return nil } -func (r *BackupSessionReconciler) checkRepo(repo string) error { - r.Log.V(0).Info("Checking repo", "repo", repo) - if err := exec.Command(RESTIC_EXEC, "unlock", "-r", repo).Run(); err != nil { - r.Log.Error(err, "unable to unlock repo", "repo", repo) +func (r *BackupSessionReconciler) checkRepo() error { + r.Log.V(0).Info("Checking repo") + if err := exec.Command(RESTIC_EXEC, "unlock").Run(); err != nil { + r.Log.Error(err, "unable to unlock repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) } - output, err := exec.Command(RESTIC_EXEC, "check", "-r", repo).CombinedOutput() + output, err := exec.Command(RESTIC_EXEC, "check").CombinedOutput() if err != nil { - r.Log.V(0).Info("Initializing new repo", "repo", repo) - output, err = exec.Command(RESTIC_EXEC, "init", "-r", repo).CombinedOutput() + r.Log.V(0).Info("Initializing new repo") + output, err = exec.Command(RESTIC_EXEC, "init").CombinedOutput() if err != nil { r.Log.Error(err, "something went wrong during repo init", "output", output) } @@ -262,12 +273,12 @@ type BackupResult struct { } func (r *BackupSessionReconciler) backupPaths(tag string, paths []string) (result BackupResult, err error) { - if err = r.checkRepo(REPOSITORY); err != nil { - r.Log.Error(err, "unable to setup repo", "repo", REPOSITORY) + if err = r.checkRepo(); err != nil { + r.Log.Error(err, "unable to setup repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) return } r.Log.V(0).Info("backing up paths", "paths", paths) - cmd := exec.Command(RESTIC_EXEC, append([]string{"backup", "--json", "--tag", tag, "-r", REPOSITORY}, paths...)...) + cmd := exec.Command(RESTIC_EXEC, append([]string{"backup", "--json", "--tag", tag}, paths...)...) stdout, _ := cmd.StdoutPipe() stderr, _ := cmd.StderrPipe() _ = cmd.Start() diff --git a/formol b/formol index b5a217b..19d74cd 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit b5a217bc3a536095084b48e19f935bc1508baedf +Subproject commit 19d74cda40b40eaea1d633f30400abd46d898f7a From d4231768d716754dbe85435359bd7ac8befb9fb7 Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Fri, 17 Mar 2023 16:42:49 +0100 Subject: [PATCH 14/37] Preparing the common code between BackupSession and RestoreSession --- controllers/backupsession_controller.go | 12 +- .../backupsession_controller_helpers.go | 212 ---------------- controllers/server.go | 6 +- controllers/session.go | 233 ++++++++++++++++++ 4 files changed, 240 insertions(+), 223 deletions(-) create mode 100644 controllers/session.go diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 77051a3..b7e0767 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -2,10 +2,8 @@ package controllers import ( "context" - "github.com/go-logr/logr" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" "os" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -17,11 +15,7 @@ import ( ) type BackupSessionReconciler struct { - client.Client - Log logr.Logger - Scheme *runtime.Scheme - context.Context - Namespace string + Session } func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -85,7 +79,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Run the initializing Steps and then move to Initialized or Failure r.Log.V(0).Info("Start to run the backup initializing steps is any") // Runs the Steps functions in chroot env - if err := r.runInitializeBackupSteps(target); err != nil { + if err := r.runInitializeSteps(target); err != nil { r.Log.Error(err, "unable to run the initialization steps") newSessionState = formolv1alpha1.Failure } else { @@ -124,7 +118,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques // Run the finalize Steps and move to Success or Failure r.Log.V(0).Info("Backup is over. Run the finalize steps is any") // Runs the finalize Steps functions in chroot env - if result = r.runFinalizeBackupSteps(target); result != nil { + if result = r.runFinalizeSteps(target); result != nil { r.Log.Error(err, "unable to run finalize steps") } if targetStatus.SnapshotId == "" { diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index f342a58..565a505 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -2,20 +2,13 @@ package controllers import ( "bufio" - "bytes" "encoding/json" "fmt" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "io" - "io/fs" - "io/ioutil" - corev1 "k8s.io/api/core/v1" "os" "os/exec" - "path/filepath" - "regexp" "sigs.k8s.io/controller-runtime/pkg/client" - "strconv" "strings" ) @@ -23,89 +16,6 @@ const ( RESTIC_EXEC = "/usr/bin/restic" ) -func (r *BackupSessionReconciler) getSecretData(name string) map[string][]byte { - secret := corev1.Secret{} - namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) - if err := r.Get(r.Context, client.ObjectKey{ - Namespace: namespace, - Name: name, - }, &secret); err != nil { - r.Log.Error(err, "unable to get Secret", "Secret", name) - return nil - } - return secret.Data -} - -func (r *BackupSessionReconciler) getEnvFromSecretKeyRef(name string, key string) string { - if data := r.getSecretData(name); data != nil { - return string(data[key]) - } - return "" -} - -func (r *BackupSessionReconciler) getConfigMapData(name string) map[string]string { - configMap := corev1.ConfigMap{} - namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) - if err := r.Get(r.Context, client.ObjectKey{ - Namespace: namespace, - Name: name, - }, &configMap); err != nil { - r.Log.Error(err, "unable to get ConfigMap", "configmap", name) - return nil - } - return configMap.Data -} - -func (r *BackupSessionReconciler) getEnvFromConfigMapKeyRef(name string, key string) string { - if data := r.getConfigMapData(name); data != nil { - return string(data[key]) - } - return "" -} - -func (r *BackupSessionReconciler) getFuncEnv(vars map[string]string, envVars []corev1.EnvVar) { - for _, env := range envVars { - if env.ValueFrom != nil { - if env.ValueFrom.ConfigMapKeyRef != nil { - vars[env.Name] = r.getEnvFromConfigMapKeyRef(env.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name, env.ValueFrom.ConfigMapKeyRef.Key) - } - if env.ValueFrom.SecretKeyRef != nil { - vars[env.Name] = r.getEnvFromSecretKeyRef(env.ValueFrom.SecretKeyRef.LocalObjectReference.Name, env.ValueFrom.SecretKeyRef.Key) - } - } else { - vars[env.Name] = env.Value - } - } -} - -func (r *BackupSessionReconciler) getEnvFromSecretEnvSource(vars map[string]string, name string) { - for key, value := range r.getSecretData(name) { - vars[key] = string(value) - } -} - -func (r *BackupSessionReconciler) getEnvFromConfigMapEnvSource(vars map[string]string, name string) { - for key, value := range r.getConfigMapData(name) { - vars[key] = value - } -} - -func (r *BackupSessionReconciler) getFuncEnvFrom(vars map[string]string, envVars []corev1.EnvFromSource) { - for _, env := range envVars { - if env.ConfigMapRef != nil { - r.getEnvFromConfigMapEnvSource(vars, env.ConfigMapRef.LocalObjectReference.Name) - } - if env.SecretRef != nil { - r.getEnvFromSecretEnvSource(vars, env.SecretRef.LocalObjectReference.Name) - } - } -} - -func (r *BackupSessionReconciler) getFuncVars(function formolv1alpha1.Function, vars map[string]string) { - r.getFuncEnvFrom(vars, function.Spec.EnvFrom) - r.getFuncEnv(vars, function.Spec.Env) -} - func (r *BackupSessionReconciler) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { repo := formolv1alpha1.Repo{} if err := r.Get(r.Context, client.ObjectKey{ @@ -129,128 +39,6 @@ func (r *BackupSessionReconciler) setResticEnv(backupConf formolv1alpha1.BackupC return nil } -func (r *BackupSessionReconciler) runFunction(name string) error { - namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) - function := formolv1alpha1.Function{} - if err := r.Get(r.Context, client.ObjectKey{ - Namespace: namespace, - Name: name, - }, &function); err != nil { - r.Log.Error(err, "unable to get Function", "Function", name) - return err - } - vars := make(map[string]string) - r.getFuncVars(function, vars) - - r.Log.V(0).Info("function vars", "vars", vars) - // Loop through the function.Spec.Command arguments to replace ${ARG}|$(ARG)|$ARG - // with the environment variable value - pattern := regexp.MustCompile(`^\$\((?P\w+)\)$`) - for i, arg := range function.Spec.Args { - if pattern.MatchString(arg) { - r.Log.V(0).Info("arg matches $()", "arg", arg) - arg = pattern.ReplaceAllString(arg, "$env") - function.Spec.Args[i] = vars[arg] - } - } - r.Log.V(1).Info("about to run Function", "Function", name, "command", function.Spec.Command, "args", function.Spec.Args) - if err := r.runTargetContainerChroot(function.Spec.Command[0], - function.Spec.Args...); err != nil { - r.Log.Error(err, "unable to run command", "command", function.Spec.Command) - return err - } - return nil -} - -func (r *BackupSessionReconciler) runBackupSteps(initializeSteps bool, target formolv1alpha1.Target) error { - r.Log.V(0).Info("start to run the backup steps it any") - // For every container listed in the target, run the initialization steps - for _, container := range target.Containers { - // Runs the steps one after the other - for _, step := range container.Steps { - if (initializeSteps == true && step.Finalize != nil && *step.Finalize == true) || (initializeSteps == false && (step.Finalize == nil || step.Finalize != nil && *step.Finalize == false)) { - continue - } - return r.runFunction(step.Name) - } - } - return nil -} - -// Run the initializing steps in the INITIALIZING state of the controller -// before actualy doing the backup in the RUNNING state -func (r *BackupSessionReconciler) runFinalizeBackupSteps(target formolv1alpha1.Target) error { - return r.runBackupSteps(false, target) -} - -// Run the finalizing steps in the FINALIZE state of the controller -// after the backup in the RUNNING state. -// The finalize happens whatever the result of the backup. -func (r *BackupSessionReconciler) runInitializeBackupSteps(target formolv1alpha1.Target) error { - return r.runBackupSteps(true, target) -} - -// Runs the given command in the target container chroot -func (r *BackupSessionReconciler) runTargetContainerChroot(runCmd string, args ...string) error { - env := regexp.MustCompile(`/proc/[0-9]+/environ`) - if err := filepath.WalkDir("/proc", func(path string, info fs.DirEntry, err error) error { - if err != nil { - return nil - } - // Skip process 1 and ourself - if info.IsDir() && (info.Name() == "1" || info.Name() == strconv.Itoa(os.Getpid())) { - return filepath.SkipDir - } - // Found an environ file. Start looking for TARGETCONTAINER_TAG - if env.MatchString(path) { - content, err := ioutil.ReadFile(path) - // cannot read environ file. not the process we want to backup - if err != nil { - return fs.SkipDir - } - // Loops over the process environement variable looking for TARGETCONTAINER_TAG - for _, env := range bytes.Split(content, []byte{'\000'}) { - matched, err := regexp.Match(formolv1alpha1.TARGETCONTAINER_TAG, env) - if err != nil { - r.Log.Error(err, "unable to regexp", "env", string(env)) - return err - } - if matched { - // Found the right process. Now run the command in its 'root' - r.Log.V(0).Info("Found the tag", "file", path) - root := filepath.Join(filepath.Dir(path), "root") - if _, err := filepath.EvalSymlinks(root); err != nil { - r.Log.Error(err, "cannot EvalSymlink.") - return err - } - r.Log.V(0).Info("running cmd in chroot", "path", root) - cmd := exec.Command("chroot", append([]string{root, runCmd}, args...)...) - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() - _ = cmd.Start() - - scanner := bufio.NewScanner(io.MultiReader(stdout, stderr)) - scanner.Split(bufio.ScanLines) - for scanner.Scan() { - r.Log.V(0).Info("cmd output", "output", scanner.Text()) - } - - if err := cmd.Wait(); err != nil { - return err - } else { - return filepath.SkipAll - } - } - } - } - return nil - }); err != nil { - r.Log.Error(err, "cannot walk /proc") - return err - } - return nil -} - func (r *BackupSessionReconciler) checkRepo() error { r.Log.V(0).Info("Checking repo") if err := exec.Command(RESTIC_EXEC, "unlock").Run(); err != nil { diff --git a/controllers/server.go b/controllers/server.go index 8a38c72..5eef1a5 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -38,8 +38,10 @@ func StartServer() { os.Exit(1) } if err = (&BackupSessionReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Session: Session{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "BackupSession") os.Exit(1) diff --git a/controllers/session.go b/controllers/session.go new file mode 100644 index 0000000..e00c62c --- /dev/null +++ b/controllers/session.go @@ -0,0 +1,233 @@ +package controllers + +import ( + "bufio" + "bytes" + "context" + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "github.com/go-logr/logr" + "io" + "io/fs" + "io/ioutil" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "os" + "os/exec" + "path/filepath" + "regexp" + "sigs.k8s.io/controller-runtime/pkg/client" + "strconv" +) + +type Session struct { + client.Client + Log logr.Logger + Scheme *runtime.Scheme + context.Context + Namespace string +} + +func (s Session) getSecretData(name string) map[string][]byte { + secret := corev1.Secret{} + namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + if err := s.Get(s.Context, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &secret); err != nil { + s.Log.Error(err, "unable to get Secret", "Secret", name) + return nil + } + return secret.Data +} + +func (s Session) getEnvFromSecretKeyRef(name string, key string) string { + if data := s.getSecretData(name); data != nil { + return string(data[key]) + } + return "" +} + +func (s Session) getConfigMapData(name string) map[string]string { + configMap := corev1.ConfigMap{} + namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + if err := s.Get(s.Context, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &configMap); err != nil { + s.Log.Error(err, "unable to get ConfigMap", "configmap", name) + return nil + } + return configMap.Data +} + +func (s Session) getEnvFromConfigMapKeyRef(name string, key string) string { + if data := s.getConfigMapData(name); data != nil { + return string(data[key]) + } + return "" +} + +func (s Session) getFuncEnv(vars map[string]string, envVars []corev1.EnvVar) { + for _, env := range envVars { + if env.ValueFrom != nil { + if env.ValueFrom.ConfigMapKeyRef != nil { + vars[env.Name] = s.getEnvFromConfigMapKeyRef(env.ValueFrom.ConfigMapKeyRef.LocalObjectReference.Name, env.ValueFrom.ConfigMapKeyRef.Key) + } + if env.ValueFrom.SecretKeyRef != nil { + vars[env.Name] = s.getEnvFromSecretKeyRef(env.ValueFrom.SecretKeyRef.LocalObjectReference.Name, env.ValueFrom.SecretKeyRef.Key) + } + } else { + vars[env.Name] = env.Value + } + } +} + +func (s Session) getEnvFromSecretEnvSource(vars map[string]string, name string) { + for key, value := range s.getSecretData(name) { + vars[key] = string(value) + } +} + +func (s Session) getEnvFromConfigMapEnvSource(vars map[string]string, name string) { + for key, value := range s.getConfigMapData(name) { + vars[key] = value + } +} + +func (s Session) getFuncEnvFrom(vars map[string]string, envVars []corev1.EnvFromSource) { + for _, env := range envVars { + if env.ConfigMapRef != nil { + s.getEnvFromConfigMapEnvSource(vars, env.ConfigMapRef.LocalObjectReference.Name) + } + if env.SecretRef != nil { + s.getEnvFromSecretEnvSource(vars, env.SecretRef.LocalObjectReference.Name) + } + } +} + +func (s Session) getFuncVars(function formolv1alpha1.Function, vars map[string]string) { + s.getFuncEnvFrom(vars, function.Spec.EnvFrom) + s.getFuncEnv(vars, function.Spec.Env) +} + +func (s Session) runFunction(name string) error { + namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) + function := formolv1alpha1.Function{} + if err := s.Get(s.Context, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &function); err != nil { + s.Log.Error(err, "unable to get Function", "Function", name) + return err + } + vars := make(map[string]string) + s.getFuncVars(function, vars) + + s.Log.V(0).Info("function vars", "vars", vars) + // Loop through the function.Spec.Command arguments to replace ${ARG}|$(ARG)|$ARG + // with the environment variable value + pattern := regexp.MustCompile(`^\$\((?P\w+)\)$`) + for i, arg := range function.Spec.Args { + if pattern.MatchString(arg) { + s.Log.V(0).Info("arg matches $()", "arg", arg) + arg = pattern.ReplaceAllString(arg, "$env") + function.Spec.Args[i] = vars[arg] + } + } + s.Log.V(1).Info("about to run Function", "Function", name, "command", function.Spec.Command, "args", function.Spec.Args) + if err := s.runTargetContainerChroot(function.Spec.Command[0], + function.Spec.Args...); err != nil { + s.Log.Error(err, "unable to run command", "command", function.Spec.Command) + return err + } + return nil +} + +// Runs the given command in the target container chroot +func (s Session) runTargetContainerChroot(runCmd string, args ...string) error { + env := regexp.MustCompile(`/proc/[0-9]+/environ`) + if err := filepath.WalkDir("/proc", func(path string, info fs.DirEntry, err error) error { + if err != nil { + return nil + } + // Skip process 1 and ourself + if info.IsDir() && (info.Name() == "1" || info.Name() == strconv.Itoa(os.Getpid())) { + return filepath.SkipDir + } + // Found an environ file. Start looking for TARGETCONTAINER_TAG + if env.MatchString(path) { + content, err := ioutil.ReadFile(path) + // cannot read environ file. not the process we want to backup + if err != nil { + return fs.SkipDir + } + // Loops over the process environement variable looking for TARGETCONTAINER_TAG + for _, env := range bytes.Split(content, []byte{'\000'}) { + matched, err := regexp.Match(formolv1alpha1.TARGETCONTAINER_TAG, env) + if err != nil { + s.Log.Error(err, "unable to regexp", "env", string(env)) + return err + } + if matched { + // Found the right process. Now run the command in its 'root' + s.Log.V(0).Info("Found the tag", "file", path) + root := filepath.Join(filepath.Dir(path), "root") + if _, err := filepath.EvalSymlinks(root); err != nil { + s.Log.Error(err, "cannot EvalSymlink.") + return err + } + s.Log.V(0).Info("running cmd in chroot", "path", root) + cmd := exec.Command("chroot", append([]string{root, runCmd}, args...)...) + stdout, _ := cmd.StdoutPipe() + stderr, _ := cmd.StderrPipe() + _ = cmd.Start() + + scanner := bufio.NewScanner(io.MultiReader(stdout, stderr)) + scanner.Split(bufio.ScanLines) + for scanner.Scan() { + s.Log.V(0).Info("cmd output", "output", scanner.Text()) + } + + if err := cmd.Wait(); err != nil { + return err + } else { + return filepath.SkipAll + } + } + } + } + return nil + }); err != nil { + s.Log.Error(err, "cannot walk /proc") + return err + } + return nil +} + +func (s Session) runSteps(initializeSteps bool, target formolv1alpha1.Target) error { + s.Log.V(0).Info("start to run the backup steps it any") + // For every container listed in the target, run the initialization steps + for _, container := range target.Containers { + // Runs the steps one after the other + for _, step := range container.Steps { + if (initializeSteps == true && step.Finalize != nil && *step.Finalize == true) || (initializeSteps == false && (step.Finalize == nil || step.Finalize != nil && *step.Finalize == false)) { + continue + } + return s.runFunction(step.Name) + } + } + return nil +} + +// Run the initializing steps in the INITIALIZING state of the controller +// before actualy doing the backup in the RUNNING state +func (s Session) runFinalizeSteps(target formolv1alpha1.Target) error { + return s.runSteps(false, target) +} + +// Run the finalizing steps in the FINALIZE state of the controller +// after the backup in the RUNNING state. +// The finalize happens whatever the result of the backup. +func (s Session) runInitializeSteps(target formolv1alpha1.Target) error { + return s.runSteps(true, target) +} From 0f8012c22e8d4ecd2c0d711c660da3e8a59e8c92 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Mon, 20 Mar 2023 22:14:41 +0100 Subject: [PATCH 15/37] prepared BackupSession and RestoreSession common code --- cmd/backupsession.go | 51 ------------------- cmd/root.go | 51 +++++++++++++++---- .../backupsession_controller_helpers.go | 43 ---------------- controllers/restoresession_controller.go | 37 ++++++++++++++ controllers/server.go | 10 ++++ controllers/session.go | 43 ++++++++++++++++ formol | 2 +- 7 files changed, 131 insertions(+), 106 deletions(-) delete mode 100644 cmd/backupsession.go create mode 100644 controllers/restoresession_controller.go diff --git a/cmd/backupsession.go b/cmd/backupsession.go deleted file mode 100644 index 38e7495..0000000 --- a/cmd/backupsession.go +++ /dev/null @@ -1,51 +0,0 @@ -/* -Copyright © 2023 NAME HERE -*/ -package cmd - -import ( - "fmt" - "github.com/desmo999r/formolcli/backupsession" - "github.com/desmo999r/formolcli/controllers" - "github.com/spf13/cobra" - corev1 "k8s.io/api/core/v1" -) - -var createBackupSessionCmd = &cobra.Command{ - Use: "create", - Short: "Create a backupsession", - Run: func(cmd *cobra.Command, args []string) { - name, _ := cmd.Flags().GetString("name") - namespace, _ := cmd.Flags().GetString("namespace") - fmt.Println("create backupsession called") - backupsession.CreateBackupSession(corev1.ObjectReference{ - Namespace: namespace, - Name: name, - }) - }, -} - -var startServerCmd = &cobra.Command{ - Use: "server", - Short: "Start a BackupSession controller", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("starts backupsession controller") - controllers.StartServer() - }, -} - -// backupsessionCmd represents the backupsession command -var backupSessionCmd = &cobra.Command{ - Use: "backupsession", - Short: "All the BackupSession related commands", -} - -func init() { - rootCmd.AddCommand(backupSessionCmd) - backupSessionCmd.AddCommand(createBackupSessionCmd) - backupSessionCmd.AddCommand(startServerCmd) - createBackupSessionCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") - createBackupSessionCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") - createBackupSessionCmd.MarkFlagRequired("namespace") - createBackupSessionCmd.MarkFlagRequired("name") -} diff --git a/cmd/root.go b/cmd/root.go index cd47515..b3ee38a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -4,11 +4,42 @@ Copyright © 2023 NAME HERE package cmd import ( - "os" - + "fmt" + "github.com/desmo999r/formolcli/backupsession" + "github.com/desmo999r/formolcli/controllers" "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + "os" ) +var createBackupSessionCmd = &cobra.Command{ + Use: "create", + Short: "Create a backupsession", + Run: func(cmd *cobra.Command, args []string) { + name, _ := cmd.Flags().GetString("name") + namespace, _ := cmd.Flags().GetString("namespace") + fmt.Println("create backupsession called") + backupsession.CreateBackupSession(corev1.ObjectReference{ + Namespace: namespace, + Name: name, + }) + }, +} + +var startServerCmd = &cobra.Command{ + Use: "server", + Short: "Start a BackupSession / RestoreSession controller", + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("starts backupsession controller") + controllers.StartServer() + }, +} + +var backupSessionCmd = &cobra.Command{ + Use: "backupsession", + Short: "All the BackupSession related commands", +} + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "formolcli", @@ -34,13 +65,11 @@ func Execute() { } func init() { - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - - // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.formolcli.yaml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.AddCommand(backupSessionCmd) + backupSessionCmd.AddCommand(createBackupSessionCmd) + rootCmd.AddCommand(startServerCmd) + createBackupSessionCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") + createBackupSessionCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") + createBackupSessionCmd.MarkFlagRequired("namespace") + createBackupSessionCmd.MarkFlagRequired("name") } diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 565a505..fe11ac3 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -12,49 +12,6 @@ import ( "strings" ) -const ( - RESTIC_EXEC = "/usr/bin/restic" -) - -func (r *BackupSessionReconciler) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { - repo := formolv1alpha1.Repo{} - if err := r.Get(r.Context, client.ObjectKey{ - Namespace: backupConf.Namespace, - Name: backupConf.Spec.Repository, - }, &repo); err != nil { - r.Log.Error(err, "unable to get repo") - return err - } - if repo.Spec.Backend.S3 != nil { - os.Setenv(formolv1alpha1.RESTIC_REPOSITORY, fmt.Sprintf("s3:http://%s/%s/%s-%s", - repo.Spec.Backend.S3.Server, - repo.Spec.Backend.S3.Bucket, - strings.ToUpper(backupConf.Namespace), - strings.ToLower(backupConf.Name))) - data := r.getSecretData(repo.Spec.RepositorySecrets) - os.Setenv(formolv1alpha1.AWS_SECRET_ACCESS_KEY, string(data[formolv1alpha1.AWS_SECRET_ACCESS_KEY])) - os.Setenv(formolv1alpha1.AWS_ACCESS_KEY_ID, string(data[formolv1alpha1.AWS_ACCESS_KEY_ID])) - os.Setenv(formolv1alpha1.RESTIC_PASSWORD, string(data[formolv1alpha1.RESTIC_PASSWORD])) - } - return nil -} - -func (r *BackupSessionReconciler) checkRepo() error { - r.Log.V(0).Info("Checking repo") - if err := exec.Command(RESTIC_EXEC, "unlock").Run(); err != nil { - r.Log.Error(err, "unable to unlock repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) - } - output, err := exec.Command(RESTIC_EXEC, "check").CombinedOutput() - if err != nil { - r.Log.V(0).Info("Initializing new repo") - output, err = exec.Command(RESTIC_EXEC, "init").CombinedOutput() - if err != nil { - r.Log.Error(err, "something went wrong during repo init", "output", output) - } - } - return err -} - type BackupResult struct { SnapshotId string Duration float64 diff --git a/controllers/restoresession_controller.go b/controllers/restoresession_controller.go new file mode 100644 index 0000000..6e5da3f --- /dev/null +++ b/controllers/restoresession_controller.go @@ -0,0 +1,37 @@ +package controllers + +import ( + "context" + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "k8s.io/apimachinery/pkg/api/errors" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/log" +) + +type RestoreSessionReconciler struct { + Session +} + +func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + r.Log = log.FromContext(ctx) + r.Context = ctx + + restoreSession := formolv1alpha1.RestoreSession{} + err := r.Get(r.Context, req.NamespacedName, &restoreSession) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + switch restoreSession.Status.SessionState { + case formolv1alpha1.New: + } + return ctrl.Result{}, nil +} + +func (r *RestoreSessionReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&formolv1alpha1.RestoreSession{}). + Complete(r) +} diff --git a/controllers/server.go b/controllers/server.go index 5eef1a5..aa2d70d 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -37,6 +37,16 @@ func StartServer() { setupLog.Error(err, "unable to create manager") os.Exit(1) } + if err = (&RestoreSessionReconciler{ + Session: Session{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }, + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "RestoreSession") + os.Exit(1) + } + if err = (&BackupSessionReconciler{ Session: Session{ Client: mgr.GetClient(), diff --git a/controllers/session.go b/controllers/session.go index e00c62c..6d4d7f5 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -27,6 +27,49 @@ type Session struct { Namespace string } +const ( + RESTIC_EXEC = "/usr/bin/restic" +) + +func (s Session) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { + repo := formolv1alpha1.Repo{} + if err := s.Get(r.Context, client.ObjectKey{ + Namespace: backupConf.Namespace, + Name: backupConf.Spec.Repository, + }, &repo); err != nil { + s.Log.Error(err, "unable to get repo") + return err + } + if repo.Spec.Backend.S3 != nil { + os.Setenv(formolv1alpha1.RESTIC_REPOSITORY, fmt.Sprintf("s3:http://%s/%s/%s-%s", + repo.Spec.Backend.S3.Server, + repo.Spec.Backend.S3.Bucket, + strings.ToUpper(backupConf.Namespace), + strings.ToLower(backupConf.Name))) + data := s.getSecretData(repo.Spec.RepositorySecrets) + os.Setenv(formolv1alpha1.AWS_SECRET_ACCESS_KEY, string(data[formolv1alpha1.AWS_SECRET_ACCESS_KEY])) + os.Setenv(formolv1alpha1.AWS_ACCESS_KEY_ID, string(data[formolv1alpha1.AWS_ACCESS_KEY_ID])) + os.Setenv(formolv1alpha1.RESTIC_PASSWORD, string(data[formolv1alpha1.RESTIC_PASSWORD])) + } + return nil +} + +func (s Session) checkRepo() error { + s.Log.V(0).Info("Checking repo") + if err := exec.Command(RESTIC_EXEC, "unlock").Run(); err != nil { + s.Log.Error(err, "unable to unlock repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) + } + output, err := exec.Command(RESTIC_EXEC, "check").CombinedOutput() + if err != nil { + s.Log.V(0).Info("Initializing new repo") + output, err = exec.Command(RESTIC_EXEC, "init").CombinedOutput() + if err != nil { + s.Log.Error(err, "something went wrong during repo init", "output", output) + } + } + return err +} + func (s Session) getSecretData(name string) map[string][]byte { secret := corev1.Secret{} namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) diff --git a/formol b/formol index 19d74cd..3486ad2 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit 19d74cda40b40eaea1d633f30400abd46d898f7a +Subproject commit 3486ad2efe7bcf40990212299a87aaa70f88965f From 994b792d97623db85a4b938da65aa06186208133 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Mon, 20 Mar 2023 22:15:13 +0100 Subject: [PATCH 16/37] multi arch Dockerfile --- Dockerfile.amd64 | 2 +- Dockerfile.arm64 | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 Dockerfile.arm64 diff --git a/Dockerfile.amd64 b/Dockerfile.amd64 index 1df3e77..7f0d6c6 100644 --- a/Dockerfile.amd64 +++ b/Dockerfile.amd64 @@ -1,6 +1,6 @@ # Build a small image FROM --platform=linux/amd64 alpine:3 -RUN apk add --no-cache su-exec restic postgresql-client +RUN apk add --no-cache su-exec restic COPY ./bin/formolcli /usr/local/bin # Command to run diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 new file mode 100644 index 0000000..083758d --- /dev/null +++ b/Dockerfile.arm64 @@ -0,0 +1,8 @@ +# Build a small image +FROM --platform=linux/arm64 alpine:3 +RUN apk add --no-cache su-exec restic postgresql-client +COPY ./bin/formolcli /usr/local/bin + +# Command to run +ENTRYPOINT ["/usr/local/bin/formolcli"] +CMD ["--help"] From 36cf68b74b1cc5b65e486537e34cecaa7463a811 Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Tue, 21 Mar 2023 10:32:35 +0100 Subject: [PATCH 17/37] Added README --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..0fa00c9 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +buildah bud --platform linux/arm64,linux/amd64 --manifest docker.io/desmo999r/formolcli:0.4.0 . From 9f40d2eb6c64d211b3329f6b53eb15a9a7f18de3 Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Tue, 21 Mar 2023 10:36:39 +0100 Subject: [PATCH 18/37] fixed imports --- controllers/backupsession_controller_helpers.go | 3 --- controllers/session.go | 4 +++- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index fe11ac3..516a1fa 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -3,13 +3,10 @@ package controllers import ( "bufio" "encoding/json" - "fmt" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "io" "os" "os/exec" - "sigs.k8s.io/controller-runtime/pkg/client" - "strings" ) type BackupResult struct { diff --git a/controllers/session.go b/controllers/session.go index 6d4d7f5..ca2b320 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "fmt" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "github.com/go-logr/logr" "io" @@ -17,6 +18,7 @@ import ( "regexp" "sigs.k8s.io/controller-runtime/pkg/client" "strconv" + "strings" ) type Session struct { @@ -33,7 +35,7 @@ const ( func (s Session) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { repo := formolv1alpha1.Repo{} - if err := s.Get(r.Context, client.ObjectKey{ + if err := s.Get(s.Context, client.ObjectKey{ Namespace: backupConf.Namespace, Name: backupConf.Spec.Repository, }, &repo); err != nil { From 322c712a370b564efa3e2ba99ec4c6ac77ceee4f Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Tue, 21 Mar 2023 13:34:33 +0100 Subject: [PATCH 19/37] Preparing RestoreSession init container for OnlineKind restores --- cmd/root.go | 4 +-- session/create.go | 41 ++++++++++++++++++++++ backupsession/create.go => session/init.go | 37 +------------------ 3 files changed, 44 insertions(+), 38 deletions(-) create mode 100644 session/create.go rename backupsession/create.go => session/init.go (51%) diff --git a/cmd/root.go b/cmd/root.go index b3ee38a..92644f7 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -5,8 +5,8 @@ package cmd import ( "fmt" - "github.com/desmo999r/formolcli/backupsession" "github.com/desmo999r/formolcli/controllers" + "github.com/desmo999r/formolcli/session" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "os" @@ -19,7 +19,7 @@ var createBackupSessionCmd = &cobra.Command{ name, _ := cmd.Flags().GetString("name") namespace, _ := cmd.Flags().GetString("namespace") fmt.Println("create backupsession called") - backupsession.CreateBackupSession(corev1.ObjectReference{ + session.CreateBackupSession(corev1.ObjectReference{ Namespace: namespace, Name: name, }) diff --git a/session/create.go b/session/create.go new file mode 100644 index 0000000..d76a59e --- /dev/null +++ b/session/create.go @@ -0,0 +1,41 @@ +package session + +import ( + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "os" + "strconv" + "strings" + "time" +) + +func CreateBackupSession(ref corev1.ObjectReference) { + log := logger.WithName("CreateBackupSession") + log.V(0).Info("CreateBackupSession called") + backupConf := formolv1alpha1.BackupConfiguration{} + if err := cl.Get(ctx, types.NamespacedName{ + Namespace: ref.Namespace, + Name: ref.Name, + }, &backupConf); err != nil { + log.Error(err, "unable to get backupconf") + os.Exit(1) + } + log.V(0).Info("got backupConf", "backupConf", backupConf) + + backupSession := &formolv1alpha1.BackupSession{ + ObjectMeta: metav1.ObjectMeta{ + Name: strings.Join([]string{"backupsession", ref.Name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), + Namespace: ref.Namespace, + }, + Spec: formolv1alpha1.BackupSessionSpec{ + Ref: ref, + }, + } + log.V(1).Info("create backupsession", "backupSession", backupSession) + if err := cl.Create(ctx, backupSession); err != nil { + log.Error(err, "unable to create backupsession") + os.Exit(1) + } +} diff --git a/backupsession/create.go b/session/init.go similarity index 51% rename from backupsession/create.go rename to session/init.go index 713e97e..9dbda44 100644 --- a/backupsession/create.go +++ b/session/init.go @@ -1,13 +1,10 @@ -package backupsession +package session import ( "context" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "github.com/go-logr/logr" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -16,9 +13,6 @@ import ( ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log/zap" - "strconv" - "strings" - "time" ) var ( @@ -51,32 +45,3 @@ func init() { os.Exit(1) } } - -func CreateBackupSession(ref corev1.ObjectReference) { - log := logger.WithName("CreateBackupSession") - log.V(0).Info("CreateBackupSession called") - backupConf := formolv1alpha1.BackupConfiguration{} - if err := cl.Get(ctx, types.NamespacedName{ - Namespace: ref.Namespace, - Name: ref.Name, - }, &backupConf); err != nil { - log.Error(err, "unable to get backupconf") - os.Exit(1) - } - log.V(0).Info("got backupConf", "backupConf", backupConf) - - backupSession := &formolv1alpha1.BackupSession{ - ObjectMeta: metav1.ObjectMeta{ - Name: strings.Join([]string{"backupsession", ref.Name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), - Namespace: ref.Namespace, - }, - Spec: formolv1alpha1.BackupSessionSpec{ - Ref: ref, - }, - } - log.V(1).Info("create backupsession", "backupSession", backupSession) - if err := cl.Create(ctx, backupSession); err != nil { - log.Error(err, "unable to create backupsession") - os.Exit(1) - } -} From 86417391d79cec0204f2becc8cbf961b055f90f5 Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Tue, 21 Mar 2023 17:57:49 +0100 Subject: [PATCH 20/37] Prepared RestoreSession for OnlineKind with initContainer --- controllers/restoresession_controller.go | 92 +++++++++++++++++++++++- formol | 2 +- 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/controllers/restoresession_controller.go b/controllers/restoresession_controller.go index 6e5da3f..640de0c 100644 --- a/controllers/restoresession_controller.go +++ b/controllers/restoresession_controller.go @@ -4,7 +4,9 @@ import ( "context" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "k8s.io/apimachinery/pkg/api/errors" + "os" ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" ) @@ -24,8 +26,96 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque } return ctrl.Result{}, err } - switch restoreSession.Status.SessionState { + if len(restoreSession.Status.Targets) == 0 { + r.Log.V(0).Info("RestoreSession still being initialized by the main controller. Wait for the next update...") + return ctrl.Result{}, nil + } + // We need the BackupConfiguration to get information about our restore target + backupSession := formolv1alpha1.BackupSession{ + Spec: restoreSession.Spec.BackupSessionRef.Spec, + Status: restoreSession.Spec.BackupSessionRef.Status, + } + backupConf := formolv1alpha1.BackupConfiguration{} + err = r.Get(r.Context, client.ObjectKey{ + Namespace: backupSession.Spec.Ref.Namespace, + Name: backupSession.Spec.Ref.Name, + }, &backupConf) + if err != nil { + if errors.IsNotFound(err) { + return ctrl.Result{}, nil + } + return ctrl.Result{}, err + } + r.Namespace = backupConf.Namespace + + // we don't want a copy because we will modify and update it. + var target formolv1alpha1.Target + var targetStatus *formolv1alpha1.TargetStatus + targetName := os.Getenv(formolv1alpha1.TARGET_NAME) + + for i, t := range backupConf.Spec.Targets { + if t.TargetName == targetName { + target = t + targetStatus = &(restoreSession.Status.Targets[i]) + break + } + } + + // Do preliminary checks with the repository + if err = r.setResticEnv(backupConf); err != nil { + r.Log.Error(err, "unable to set restic env") + return ctrl.Result{}, err + } + + var newSessionState formolv1alpha1.SessionState + switch targetStatus.SessionState { case formolv1alpha1.New: + // New session move to Initializing + r.Log.V(0).Info("New session. Move to Initializing state") + newSessionState = formolv1alpha1.Initializing + case formolv1alpha1.Initializing: + // Run the initializing Steps and then move to Initialized or Failure + r.Log.V(0).Info("Start to run the backup initializing steps is any") + // Runs the Steps functions in chroot env + if err := r.runInitializeSteps(target); err != nil { + r.Log.Error(err, "unable to run the initialization steps") + newSessionState = formolv1alpha1.Failure + } else { + r.Log.V(0).Info("Done with the initializing Steps. Move to Initialized state") + newSessionState = formolv1alpha1.Initialized + } + case formolv1alpha1.Running: + // Do the restore and move to Waiting once it is done. + // The restore is different if the Backup was an OnlineKind or a JobKind + switch target.BackupType { + case formolv1alpha1.JobKind: + case formolv1alpha1.OnlineKind: + // The restore has to be done by an initContainer since the data is mounted RO + // We create the initContainer here + // Once the the container has rebooted and the initContainer has done its job, it will change the targetStatus to Waiting. + targetObject, targetPodSpec := formolv1alpha1.GetTargetObjects(target.TargetKind) + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: backupConf.Namespace, + Name: target.TargetName, + }, targetObject); err != nil { + r.Log.Error(err, "unable to get target objects", "target", target.TargetName) + return ctrl.Result{}, err + } + initContainer := corev1.Container {} + targetPodSpec.InitContainers = append(targetPodSpec.InitContainers, initContainer) + if err := r.Update(r.Context, targetObject); err != nil { + r.Log.Error(err, "unable to add the restore init container", "targetObject", targetObject) + return ctrl.Result{}, err + } + } + } + if newSessionState != "" { + targetStatus.SessionState = newSessionState + err := r.Status().Update(ctx, &restoreSession) + if err != nil { + r.Log.Error(err, "unable to update RestoreSession status") + } + return ctrl.Result{}, err } return ctrl.Result{}, nil } diff --git a/formol b/formol index 3486ad2..7e007bf 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit 3486ad2efe7bcf40990212299a87aaa70f88965f +Subproject commit 7e007bfd44cc0041a74760c82b752aa2a93242fb From 8ea4e3bffe6ca0402010736cb31cebc054ebca29 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Thu, 23 Mar 2023 22:19:58 +0100 Subject: [PATCH 21/37] Restore OnlineKind --- cmd/root.go | 28 +++- .../backupsession_controller_helpers.go | 2 +- controllers/restoresession_controller.go | 29 ++--- .../restoresession_controller_helper.go | 56 ++++++++ controllers/session.go | 47 +++++-- formol | 2 +- session/create.go | 41 ------ session/init.go | 47 ------- standalone/root.go | 122 ++++++++++++++++++ 9 files changed, 250 insertions(+), 124 deletions(-) create mode 100644 controllers/restoresession_controller_helper.go delete mode 100644 session/create.go delete mode 100644 session/init.go create mode 100644 standalone/root.go diff --git a/cmd/root.go b/cmd/root.go index 92644f7..57427ba 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -6,7 +6,7 @@ package cmd import ( "fmt" "github.com/desmo999r/formolcli/controllers" - "github.com/desmo999r/formolcli/session" + "github.com/desmo999r/formolcli/standalone" "github.com/spf13/cobra" corev1 "k8s.io/api/core/v1" "os" @@ -19,13 +19,24 @@ var createBackupSessionCmd = &cobra.Command{ name, _ := cmd.Flags().GetString("name") namespace, _ := cmd.Flags().GetString("namespace") fmt.Println("create backupsession called") - session.CreateBackupSession(corev1.ObjectReference{ + standalone.CreateBackupSession(corev1.ObjectReference{ Namespace: namespace, Name: name, }) }, } +var startRestoreSessionCmd = &cobra.Command{ + Use: "start", + Short: "Restore a restic snapshot", + Run: func(cmd *cobra.Command, args []string) { + restoreSessionName, _ := cmd.Flags().GetString("name") + restoreSessionNamespace, _ := cmd.Flags().GetString("namespace") + targetName, _ := cmd.Flags().GetString("target-name") + standalone.StartRestore(restoreSessionName, restoreSessionNamespace, targetName) + }, +} + var startServerCmd = &cobra.Command{ Use: "server", Short: "Start a BackupSession / RestoreSession controller", @@ -35,6 +46,11 @@ var startServerCmd = &cobra.Command{ }, } +var restoreSessionCmd = &cobra.Command{ + Use: "restoresession", + Short: "All the RestoreSession related commands", +} + var backupSessionCmd = &cobra.Command{ Use: "backupsession", Short: "All the BackupSession related commands", @@ -66,10 +82,18 @@ func Execute() { func init() { rootCmd.AddCommand(backupSessionCmd) + rootCmd.AddCommand(restoreSessionCmd) backupSessionCmd.AddCommand(createBackupSessionCmd) + restoreSessionCmd.AddCommand(startRestoreSessionCmd) rootCmd.AddCommand(startServerCmd) createBackupSessionCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") createBackupSessionCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") createBackupSessionCmd.MarkFlagRequired("namespace") createBackupSessionCmd.MarkFlagRequired("name") + startRestoreSessionCmd.Flags().String("namespace", "", "The namespace of RestoreSession") + startRestoreSessionCmd.Flags().String("name", "", "The name of RestoreSession") + startRestoreSessionCmd.Flags().String("target-name", "", "The name of target being restored") + startRestoreSessionCmd.MarkFlagRequired("namespace") + startRestoreSessionCmd.MarkFlagRequired("name") + startRestoreSessionCmd.MarkFlagRequired("target-name") } diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 516a1fa..0e9d0e6 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -15,7 +15,7 @@ type BackupResult struct { } func (r *BackupSessionReconciler) backupPaths(tag string, paths []string) (result BackupResult, err error) { - if err = r.checkRepo(); err != nil { + if err = r.CheckRepo(); err != nil { r.Log.Error(err, "unable to setup repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) return } diff --git a/controllers/restoresession_controller.go b/controllers/restoresession_controller.go index 640de0c..09bf6cf 100644 --- a/controllers/restoresession_controller.go +++ b/controllers/restoresession_controller.go @@ -12,6 +12,8 @@ import ( type RestoreSessionReconciler struct { Session + backupConf formolv1alpha1.BackupConfiguration + restoreSession formolv1alpha1.RestoreSession } func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -30,6 +32,7 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque r.Log.V(0).Info("RestoreSession still being initialized by the main controller. Wait for the next update...") return ctrl.Result{}, nil } + r.restoreSession = restoreSession // We need the BackupConfiguration to get information about our restore target backupSession := formolv1alpha1.BackupSession{ Spec: restoreSession.Spec.BackupSessionRef.Spec, @@ -47,16 +50,17 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque return ctrl.Result{}, err } r.Namespace = backupConf.Namespace + r.backupConf = backupConf // we don't want a copy because we will modify and update it. var target formolv1alpha1.Target - var targetStatus *formolv1alpha1.TargetStatus + var restoreTargetStatus *formolv1alpha1.TargetStatus targetName := os.Getenv(formolv1alpha1.TARGET_NAME) for i, t := range backupConf.Spec.Targets { if t.TargetName == targetName { target = t - targetStatus = &(restoreSession.Status.Targets[i]) + restoreTargetStatus = &(restoreSession.Status.Targets[i]) break } } @@ -68,7 +72,7 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque } var newSessionState formolv1alpha1.SessionState - switch targetStatus.SessionState { + switch restoreTargetStatus.SessionState { case formolv1alpha1.New: // New session move to Initializing r.Log.V(0).Info("New session. Move to Initializing state") @@ -90,27 +94,14 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque switch target.BackupType { case formolv1alpha1.JobKind: case formolv1alpha1.OnlineKind: - // The restore has to be done by an initContainer since the data is mounted RO - // We create the initContainer here - // Once the the container has rebooted and the initContainer has done its job, it will change the targetStatus to Waiting. - targetObject, targetPodSpec := formolv1alpha1.GetTargetObjects(target.TargetKind) - if err := r.Get(r.Context, client.ObjectKey{ - Namespace: backupConf.Namespace, - Name: target.TargetName, - }, targetObject); err != nil { - r.Log.Error(err, "unable to get target objects", "target", target.TargetName) - return ctrl.Result{}, err - } - initContainer := corev1.Container {} - targetPodSpec.InitContainers = append(targetPodSpec.InitContainers, initContainer) - if err := r.Update(r.Context, targetObject); err != nil { - r.Log.Error(err, "unable to add the restore init container", "targetObject", targetObject) + if err := r.restoreInitContainer(target); err != nil { + r.Log.Error(err, "unable to create restore initContainer", "target", target) return ctrl.Result{}, err } } } if newSessionState != "" { - targetStatus.SessionState = newSessionState + restoreTargetStatus.SessionState = newSessionState err := r.Status().Update(ctx, &restoreSession) if err != nil { r.Log.Error(err, "unable to update RestoreSession status") diff --git a/controllers/restoresession_controller_helper.go b/controllers/restoresession_controller_helper.go new file mode 100644 index 0000000..32347ab --- /dev/null +++ b/controllers/restoresession_controller_helper.go @@ -0,0 +1,56 @@ +package controllers + +import ( + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + corev1 "k8s.io/api/core/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +func (r *RestoreSessionReconciler) restoreInitContainer(target formolv1alpha1.Target) error { + // The restore has to be done by an initContainer since the data is mounted RO + // We create the initContainer here + // Once the the container has rebooted and the initContainer has done its job, it will change the restoreTargetStatus to Waiting. + targetObject, targetPodSpec := formolv1alpha1.GetTargetObjects(target.TargetKind) + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: r.backupConf.Namespace, + Name: target.TargetName, + }, targetObject); err != nil { + r.Log.Error(err, "unable to get target objects", "target", target.TargetName) + return err + } + initContainer := corev1.Container{} + for _, c := range targetPodSpec.Containers { + if c.Name == formolv1alpha1.SIDECARCONTAINER_NAME { + // We copy the existing formol sidecar container to keep the VolumeMounts + // We just have to change the name + // Change the VolumeMounts to RW + // Change the command so the initContainer restores the snapshot + c.DeepCopyInto(&initContainer) + break + } + } + initContainer.Name = formolv1alpha1.RESTORECONTAINER_NAME + for i, _ := range initContainer.VolumeMounts { + initContainer.VolumeMounts[i].ReadOnly = false + } + if env, err := r.getResticEnv(r.backupConf); err != nil { + r.Log.Error(err, "unable to get restic env") + return err + } else { + initContainer.Env = append(initContainer.Env, env...) + } + initContainer.Args = []string{"restoresession", "start", + "--name", r.restoreSession.Name, + "--namespace", r.restoreSession.Namespace, + "--target-name", target.TargetName, + } + targetPodSpec.InitContainers = append(targetPodSpec.InitContainers, initContainer) + // This will kill this Pod and start a new one with the initContainer + // the initContainer will restore the snapshot + // If everything goes well the initContainer will change the restoreTargetStatus to Waiting + if err := r.Update(r.Context, targetObject); err != nil { + r.Log.Error(err, "unable to add the restore init container", "targetObject", targetObject) + return err + } + return nil +} diff --git a/controllers/session.go b/controllers/session.go index ca2b320..310ee5a 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -33,30 +33,51 @@ const ( RESTIC_EXEC = "/usr/bin/restic" ) -func (s Session) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { +func (s Session) getResticEnv(backupConf formolv1alpha1.BackupConfiguration) (envs []corev1.EnvVar, err error) { repo := formolv1alpha1.Repo{} - if err := s.Get(s.Context, client.ObjectKey{ + if err = s.Get(s.Context, client.ObjectKey{ Namespace: backupConf.Namespace, Name: backupConf.Spec.Repository, }, &repo); err != nil { s.Log.Error(err, "unable to get repo") - return err + return } if repo.Spec.Backend.S3 != nil { - os.Setenv(formolv1alpha1.RESTIC_REPOSITORY, fmt.Sprintf("s3:http://%s/%s/%s-%s", - repo.Spec.Backend.S3.Server, - repo.Spec.Backend.S3.Bucket, - strings.ToUpper(backupConf.Namespace), - strings.ToLower(backupConf.Name))) + envs = append(envs, corev1.EnvVar{ + Name: formolv1alpha1.RESTIC_REPOSITORY, + Value: fmt.Sprintf("s3:http://%s/%s/%s-%s", + repo.Spec.Backend.S3.Server, + repo.Spec.Backend.S3.Bucket, + strings.ToUpper(backupConf.Namespace), + strings.ToLower(backupConf.Name)), + }) + data := s.getSecretData(repo.Spec.RepositorySecrets) - os.Setenv(formolv1alpha1.AWS_SECRET_ACCESS_KEY, string(data[formolv1alpha1.AWS_SECRET_ACCESS_KEY])) - os.Setenv(formolv1alpha1.AWS_ACCESS_KEY_ID, string(data[formolv1alpha1.AWS_ACCESS_KEY_ID])) - os.Setenv(formolv1alpha1.RESTIC_PASSWORD, string(data[formolv1alpha1.RESTIC_PASSWORD])) + envs = append(envs, corev1.EnvVar{ + Name: formolv1alpha1.AWS_ACCESS_KEY_ID, + Value: string(data[formolv1alpha1.AWS_ACCESS_KEY_ID]), + }) + envs = append(envs, corev1.EnvVar{ + Name: formolv1alpha1.AWS_SECRET_ACCESS_KEY, + Value: string(data[formolv1alpha1.AWS_SECRET_ACCESS_KEY]), + }) + envs = append(envs, corev1.EnvVar{ + Name: formolv1alpha1.RESTIC_PASSWORD, + Value: string(data[formolv1alpha1.RESTIC_PASSWORD]), + }) } - return nil + return } -func (s Session) checkRepo() error { +func (s Session) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { + envs, err := s.getResticEnv(backupConf) + for _, env := range envs { + os.Setenv(env.Name, env.Value) + } + return err +} + +func (s Session) CheckRepo() error { s.Log.V(0).Info("Checking repo") if err := exec.Command(RESTIC_EXEC, "unlock").Run(); err != nil { s.Log.Error(err, "unable to unlock repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) diff --git a/formol b/formol index 7e007bf..b2d80d6 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit 7e007bfd44cc0041a74760c82b752aa2a93242fb +Subproject commit b2d80d66ae6bca9e1bc5d04737998806296f8a93 diff --git a/session/create.go b/session/create.go deleted file mode 100644 index d76a59e..0000000 --- a/session/create.go +++ /dev/null @@ -1,41 +0,0 @@ -package session - -import ( - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/types" - "os" - "strconv" - "strings" - "time" -) - -func CreateBackupSession(ref corev1.ObjectReference) { - log := logger.WithName("CreateBackupSession") - log.V(0).Info("CreateBackupSession called") - backupConf := formolv1alpha1.BackupConfiguration{} - if err := cl.Get(ctx, types.NamespacedName{ - Namespace: ref.Namespace, - Name: ref.Name, - }, &backupConf); err != nil { - log.Error(err, "unable to get backupconf") - os.Exit(1) - } - log.V(0).Info("got backupConf", "backupConf", backupConf) - - backupSession := &formolv1alpha1.BackupSession{ - ObjectMeta: metav1.ObjectMeta{ - Name: strings.Join([]string{"backupsession", ref.Name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), - Namespace: ref.Namespace, - }, - Spec: formolv1alpha1.BackupSessionSpec{ - Ref: ref, - }, - } - log.V(1).Info("create backupsession", "backupSession", backupSession) - if err := cl.Create(ctx, backupSession); err != nil { - log.Error(err, "unable to create backupsession") - os.Exit(1) - } -} diff --git a/session/init.go b/session/init.go deleted file mode 100644 index 9dbda44..0000000 --- a/session/init.go +++ /dev/null @@ -1,47 +0,0 @@ -package session - -import ( - "context" - formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" - "github.com/go-logr/logr" - "k8s.io/apimachinery/pkg/runtime" - clientgoscheme "k8s.io/client-go/kubernetes/scheme" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - "os" - "path/filepath" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/log/zap" -) - -var ( - config *rest.Config - scheme *runtime.Scheme - cl client.Client - logger logr.Logger - ctx context.Context -) - -func init() { - logger = zap.New(zap.UseDevMode(true)) - ctx = context.Background() - log := logger.WithName("InitBackupSession") - ctrl.SetLogger(logger) - config, err := rest.InClusterConfig() - if err != nil { - config, err = clientcmd.BuildConfigFromFlags("", filepath.Join(os.Getenv("HOME"), ".kube", "config")) - if err != nil { - log.Error(err, "unable to get config") - os.Exit(1) - } - } - scheme = runtime.NewScheme() - _ = formolv1alpha1.AddToScheme(scheme) - _ = clientgoscheme.AddToScheme(scheme) - cl, err = client.New(config, client.Options{Scheme: scheme}) - if err != nil { - log.Error(err, "unable to get client") - os.Exit(1) - } -} diff --git a/standalone/root.go b/standalone/root.go new file mode 100644 index 0000000..98c2c5e --- /dev/null +++ b/standalone/root.go @@ -0,0 +1,122 @@ +package standalone + +import ( + "context" + formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + "github.com/desmo999r/formolcli/controllers" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + "os" + "os/exec" + "path/filepath" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log/zap" + "strconv" + "strings" + "time" +) + +var ( + session controllers.Session +) + +func init() { + session.Log = zap.New(zap.UseDevMode(true)) + session.Context = context.Background() + log := session.Log.WithName("InitBackupSession") + ctrl.SetLogger(session.Log) + config, err := rest.InClusterConfig() + if err != nil { + config, err = clientcmd.BuildConfigFromFlags("", filepath.Join(os.Getenv("HOME"), ".kube", "config")) + if err != nil { + log.Error(err, "unable to get config") + os.Exit(1) + } + } + session.Scheme = runtime.NewScheme() + _ = formolv1alpha1.AddToScheme(session.Scheme) + _ = clientgoscheme.AddToScheme(session.Scheme) + session.Client, err = client.New(config, client.Options{Scheme: session.Scheme}) + if err != nil { + log.Error(err, "unable to get client") + os.Exit(1) + } +} + +func StartRestore( + restoreSessionName string, + restoreSessionNamespace string, + targetName string) { + log := session.Log.WithName("StartRestore") + if err := session.CheckRepo(); err != nil { + log.Error(err, "unable to check Repo") + return + } + restoreSession := formolv1alpha1.RestoreSession{} + if err := session.Get(session.Context, client.ObjectKey{ + Name: restoreSessionName, + Namespace: restoreSessionNamespace, + }, &restoreSession); err != nil { + log.Error(err, "unable to get restoresession", "name", restoreSessionName, "namespace", restoreSessionNamespace) + return + } + backupSession := formolv1alpha1.BackupSession{ + Spec: restoreSession.Spec.BackupSessionRef.Spec, + Status: restoreSession.Spec.BackupSessionRef.Status, + } + for i, target := range backupSession.Status.Targets { + if target.TargetName == targetName { + + log.V(0).Info("StartRestore called", "restoring snapshot", target.SnapshotId) + cmd := exec.Command(controllers.RESTIC_EXEC, "restore", target.SnapshotId, "--target", "/") + // the restic restore command does not support JSON output + if output, err := cmd.CombinedOutput(); err != nil { + log.Error(err, "unable to restore snapshot", "output", output) + restoreSession.Status.Targets[i].SessionState = formolv1alpha1.Failure + } else { + restoreSession.Status.Targets[i].SessionState = formolv1alpha1.Waiting + log.V(0).Info("restore was a success. Moving to waiting state", "target", target.TargetName) + } + if err := session.Status().Update(session.Context, &restoreSession); err != nil { + log.Error(err, "unable to update RestoreSession", "restoreSession", restoreSession) + return + } + break + } + } +} + +func CreateBackupSession(ref corev1.ObjectReference) { + log := session.Log.WithName("CreateBackupSession") + log.V(0).Info("CreateBackupSession called") + backupConf := formolv1alpha1.BackupConfiguration{} + if err := session.Get(session.Context, types.NamespacedName{ + Namespace: ref.Namespace, + Name: ref.Name, + }, &backupConf); err != nil { + log.Error(err, "unable to get backupconf") + os.Exit(1) + } + log.V(0).Info("got backupConf", "backupConf", backupConf) + + backupSession := &formolv1alpha1.BackupSession{ + ObjectMeta: metav1.ObjectMeta{ + Name: strings.Join([]string{"backupsession", ref.Name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), + Namespace: ref.Namespace, + }, + Spec: formolv1alpha1.BackupSessionSpec{ + Ref: ref, + }, + } + log.V(1).Info("create backupsession", "backupSession", backupSession) + if err := session.Create(session.Context, backupSession); err != nil { + log.Error(err, "unable to create backupsession") + os.Exit(1) + } +} From 5e5e4a9a7733f1b657f1536e5e1e1eed155546ea Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Fri, 24 Mar 2023 11:30:30 +0100 Subject: [PATCH 22/37] remove the initContainer once the restore is done --- controllers/backupsession_controller.go | 3 +++ standalone/root.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index b7e0767..b1177fe 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -54,6 +54,9 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques var targetStatus *formolv1alpha1.TargetStatus var result error targetName := os.Getenv(formolv1alpha1.TARGET_NAME) + if targetName == "" { + panic("targetName is empty. That should not happen") + } for i, t := range backupConf.Spec.Targets { if t.TargetName == targetName { diff --git a/standalone/root.go b/standalone/root.go index 98c2c5e..53ec3c3 100644 --- a/standalone/root.go +++ b/standalone/root.go @@ -87,6 +87,27 @@ func StartRestore( log.Error(err, "unable to update RestoreSession", "restoreSession", restoreSession) return } + log.V(0).Info("restore over. removing the initContainer") + targetObject, targetPodSpec := formolv1alpha1.GetTargetObjects(target.TargetKind) + if err := session.Get(session.Context, client.ObjectKey{ + Namespace: restoreSessionNamespace, + Name: target.TargetName, + }, targetObject); err != nil { + log.Error(err, "unable to get target objects", "target", target.TargetName) + return + } + initContainers := []corev1.Container{} + for _, c := range targetPodSpec.InitContainers { + if c.Name == formolv1alpha1.RESTORECONTAINER_NAME { + continue + } + initContainers = append(initContainers, c) + } + targetPodSpec.InitContainers = initContainers + if err := session.Update(session.Context, targetObject); err != nil { + log.Error(err, "unable to remove the restore initContainer", "targetObject", targetObject) + return + } break } } From 560271a2942bb16fa2ce8c45e518ab3093e5dcdc Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Fri, 24 Mar 2023 21:52:49 +0100 Subject: [PATCH 23/37] backup / restore of OnlineKind and JobKind work --- .../backupsession_controller_helpers.go | 2 +- controllers/restoresession_controller.go | 25 ++++++++++++++++++- .../restoresession_controller_helper.go | 19 ++++++++++++++ controllers/session.go | 4 ++- formol | 2 +- 5 files changed, 48 insertions(+), 4 deletions(-) diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 0e9d0e6..c32f86e 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -50,7 +50,7 @@ func (r *BackupSessionReconciler) backupJob(tag string, target formolv1alpha1.Ta paths := []string{} for _, container := range target.Containers { for _, job := range container.Job { - if err = r.runFunction(job.Name); err != nil { + if err = r.runFunction("backup-" + job.Name); err != nil { r.Log.Error(err, "unable to run job") return } diff --git a/controllers/restoresession_controller.go b/controllers/restoresession_controller.go index 09bf6cf..3da80ee 100644 --- a/controllers/restoresession_controller.go +++ b/controllers/restoresession_controller.go @@ -55,12 +55,14 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque // we don't want a copy because we will modify and update it. var target formolv1alpha1.Target var restoreTargetStatus *formolv1alpha1.TargetStatus + var backupTargetStatus formolv1alpha1.TargetStatus targetName := os.Getenv(formolv1alpha1.TARGET_NAME) for i, t := range backupConf.Spec.Targets { if t.TargetName == targetName { target = t restoreTargetStatus = &(restoreSession.Status.Targets[i]) + backupTargetStatus = backupSession.Status.Targets[i] break } } @@ -93,12 +95,33 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque // The restore is different if the Backup was an OnlineKind or a JobKind switch target.BackupType { case formolv1alpha1.JobKind: + r.Log.V(0).Info("restoring job backup", "target", target) + if err := r.restoreJob(target, backupTargetStatus); err != nil { + r.Log.Error(err, "unable to restore job", "target", target) + newSessionState = formolv1alpha1.Failure + } else { + r.Log.V(0).Info("job backup restore was a success", "target", target) + newSessionState = formolv1alpha1.Success + } case formolv1alpha1.OnlineKind: + // The initContainer will update the SessionState of the target + // once it is done with the restore + r.Log.V(0).Info("restoring online backup", "target", target) if err := r.restoreInitContainer(target); err != nil { r.Log.Error(err, "unable to create restore initContainer", "target", target) - return ctrl.Result{}, err + newSessionState = formolv1alpha1.Failure } } + case formolv1alpha1.Finalize: + r.Log.V(0).Info("We are done with the restore. Run the finalize steps") + // Runs the finalize Steps functions in chroot env + if err := r.runFinalizeSteps(target); err != nil { + r.Log.Error(err, "unable to run finalize steps") + newSessionState = formolv1alpha1.Failure + } else { + r.Log.V(0).Info("Ran the finalize steps. Restore was a success") + newSessionState = formolv1alpha1.Success + } } if newSessionState != "" { restoreTargetStatus.SessionState = newSessionState diff --git a/controllers/restoresession_controller_helper.go b/controllers/restoresession_controller_helper.go index 32347ab..68473e3 100644 --- a/controllers/restoresession_controller_helper.go +++ b/controllers/restoresession_controller_helper.go @@ -3,6 +3,7 @@ package controllers import ( formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" corev1 "k8s.io/api/core/v1" + "os/exec" "sigs.k8s.io/controller-runtime/pkg/client" ) @@ -54,3 +55,21 @@ func (r *RestoreSessionReconciler) restoreInitContainer(target formolv1alpha1.Ta } return nil } + +func (r *RestoreSessionReconciler) restoreJob(target formolv1alpha1.Target, targetStatus formolv1alpha1.TargetStatus) error { + cmd := exec.Command(RESTIC_EXEC, "restore", targetStatus.SnapshotId, "--target", "/") + // the restic restore command does not support JSON output + if output, err := cmd.CombinedOutput(); err != nil { + r.Log.Error(err, "unable to restore snapshot", "output", output) + return err + } + for _, container := range target.Containers { + for _, job := range container.Job { + if err := r.runFunction("restore-" + job.Name); err != nil { + r.Log.Error(err, "unable to run restore job") + return err + } + } + } + return nil +} diff --git a/controllers/session.go b/controllers/session.go index 310ee5a..276e3ed 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -271,7 +271,6 @@ func (s Session) runTargetContainerChroot(runCmd string, args ...string) error { } func (s Session) runSteps(initializeSteps bool, target formolv1alpha1.Target) error { - s.Log.V(0).Info("start to run the backup steps it any") // For every container listed in the target, run the initialization steps for _, container := range target.Containers { // Runs the steps one after the other @@ -282,12 +281,14 @@ func (s Session) runSteps(initializeSteps bool, target formolv1alpha1.Target) er return s.runFunction(step.Name) } } + s.Log.V(0).Info("Done running steps") return nil } // Run the initializing steps in the INITIALIZING state of the controller // before actualy doing the backup in the RUNNING state func (s Session) runFinalizeSteps(target formolv1alpha1.Target) error { + s.Log.V(0).Info("start to run the finalize steps it any") return s.runSteps(false, target) } @@ -295,5 +296,6 @@ func (s Session) runFinalizeSteps(target formolv1alpha1.Target) error { // after the backup in the RUNNING state. // The finalize happens whatever the result of the backup. func (s Session) runInitializeSteps(target formolv1alpha1.Target) error { + s.Log.V(0).Info("start to run the initialize steps it any") return s.runSteps(true, target) } diff --git a/formol b/formol index b2d80d6..b7747b6 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit b2d80d66ae6bca9e1bc5d04737998806296f8a93 +Subproject commit b7747b635d0253d39829d64ae96472da09c64297 From b91c767e82aaadf6f37163e916ae80daaad0eb84 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Sat, 25 Mar 2023 18:24:05 +0100 Subject: [PATCH 24/37] Reworked Steps --- .../backupsession_controller_helpers.go | 2 +- .../restoresession_controller_helper.go | 2 +- controllers/session.go | 23 ++++++++++++------- formol | 2 +- 4 files changed, 18 insertions(+), 11 deletions(-) diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index c32f86e..bb05727 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -50,7 +50,7 @@ func (r *BackupSessionReconciler) backupJob(tag string, target formolv1alpha1.Ta paths := []string{} for _, container := range target.Containers { for _, job := range container.Job { - if err = r.runFunction("backup-" + job.Name); err != nil { + if err = r.runFunction(*job.Backup); err != nil { r.Log.Error(err, "unable to run job") return } diff --git a/controllers/restoresession_controller_helper.go b/controllers/restoresession_controller_helper.go index 68473e3..dd0e05a 100644 --- a/controllers/restoresession_controller_helper.go +++ b/controllers/restoresession_controller_helper.go @@ -65,7 +65,7 @@ func (r *RestoreSessionReconciler) restoreJob(target formolv1alpha1.Target, targ } for _, container := range target.Containers { for _, job := range container.Job { - if err := r.runFunction("restore-" + job.Name); err != nil { + if err := r.runFunction(*job.Restore); err != nil { r.Log.Error(err, "unable to run restore job") return err } diff --git a/controllers/session.go b/controllers/session.go index 276e3ed..946d9cb 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -270,15 +270,18 @@ func (s Session) runTargetContainerChroot(runCmd string, args ...string) error { return nil } -func (s Session) runSteps(initializeSteps bool, target formolv1alpha1.Target) error { +type selectStep func(formolv1alpha1.Step) *string + +func (s Session) runSteps(target formolv1alpha1.Target, fn selectStep) error { // For every container listed in the target, run the initialization steps for _, container := range target.Containers { // Runs the steps one after the other for _, step := range container.Steps { - if (initializeSteps == true && step.Finalize != nil && *step.Finalize == true) || (initializeSteps == false && (step.Finalize == nil || step.Finalize != nil && *step.Finalize == false)) { - continue + if fn(step) != nil { + if err := s.runFunction(*fn(step)); err != nil { + return err + } } - return s.runFunction(step.Name) } } s.Log.V(0).Info("Done running steps") @@ -287,15 +290,19 @@ func (s Session) runSteps(initializeSteps bool, target formolv1alpha1.Target) er // Run the initializing steps in the INITIALIZING state of the controller // before actualy doing the backup in the RUNNING state -func (s Session) runFinalizeSteps(target formolv1alpha1.Target) error { +func (s Session) runInitializeSteps(target formolv1alpha1.Target) error { s.Log.V(0).Info("start to run the finalize steps it any") - return s.runSteps(false, target) + return s.runSteps(target, func(step formolv1alpha1.Step) *string { + return step.Initialize + }) } // Run the finalizing steps in the FINALIZE state of the controller // after the backup in the RUNNING state. // The finalize happens whatever the result of the backup. -func (s Session) runInitializeSteps(target formolv1alpha1.Target) error { +func (s Session) runFinalizeSteps(target formolv1alpha1.Target) error { s.Log.V(0).Info("start to run the initialize steps it any") - return s.runSteps(true, target) + return s.runSteps(target, func(step formolv1alpha1.Step) *string { + return step.Finalize + }) } diff --git a/formol b/formol index b7747b6..e73ef7c 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit b7747b635d0253d39829d64ae96472da09c64297 +Subproject commit e73ef7c3f24ee612421b62963f443ff1a1e790dc From 06b372765b50ce84c481df8cf2cf8381c0c31fb6 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Sat, 25 Mar 2023 21:23:11 +0100 Subject: [PATCH 25/37] backupsession housekeeping. delete the old backup and the corresponding restic snapshots --- cmd/root.go | 24 ++++++++++++++++++++++++ controllers/backupsession_controller.go | 2 +- controllers/restoresession_controller.go | 2 +- controllers/session.go | 5 ++--- formol | 2 +- standalone/root.go | 23 +++++++++++++++++++++++ 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index 57427ba..b0ec277 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -56,6 +56,22 @@ var backupSessionCmd = &cobra.Command{ Short: "All the BackupSession related commands", } +var snapshotCmd = &cobra.Command{ + Use: "snapshot", + Short: "All the snapshot related commands", +} + +var deleteSnapshotCmd = &cobra.Command{ + Use: "delete", + Short: "Delete a snapshot", + Run: func(cmd *cobra.Command, args []string) { + name, _ := cmd.Flags().GetString("name") + namespace, _ := cmd.Flags().GetString("namespace") + snapshotId, _ := cmd.Flags().GetString("snapshot-id") + standalone.DeleteSnapshot(namespace, name, snapshotId) + }, +} + // rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Use: "formolcli", @@ -83,8 +99,10 @@ func Execute() { func init() { rootCmd.AddCommand(backupSessionCmd) rootCmd.AddCommand(restoreSessionCmd) + rootCmd.AddCommand(snapshotCmd) backupSessionCmd.AddCommand(createBackupSessionCmd) restoreSessionCmd.AddCommand(startRestoreSessionCmd) + snapshotCmd.AddCommand(deleteSnapshotCmd) rootCmd.AddCommand(startServerCmd) createBackupSessionCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") createBackupSessionCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") @@ -96,4 +114,10 @@ func init() { startRestoreSessionCmd.MarkFlagRequired("namespace") startRestoreSessionCmd.MarkFlagRequired("name") startRestoreSessionCmd.MarkFlagRequired("target-name") + deleteSnapshotCmd.Flags().String("snapshot-id", "", "The snapshot id to delete") + deleteSnapshotCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") + deleteSnapshotCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") + deleteSnapshotCmd.MarkFlagRequired("snapshot-id") + deleteSnapshotCmd.MarkFlagRequired("namespace") + deleteSnapshotCmd.MarkFlagRequired("name") } diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index b1177fe..75a8e16 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -67,7 +67,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } // Do preliminary checks with the repository - if err = r.setResticEnv(backupConf); err != nil { + if err = r.SetResticEnv(backupConf); err != nil { r.Log.Error(err, "unable to set restic env") return ctrl.Result{}, err } diff --git a/controllers/restoresession_controller.go b/controllers/restoresession_controller.go index 3da80ee..8a53c10 100644 --- a/controllers/restoresession_controller.go +++ b/controllers/restoresession_controller.go @@ -68,7 +68,7 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque } // Do preliminary checks with the repository - if err = r.setResticEnv(backupConf); err != nil { + if err = r.SetResticEnv(backupConf); err != nil { r.Log.Error(err, "unable to set restic env") return ctrl.Result{}, err } diff --git a/controllers/session.go b/controllers/session.go index 946d9cb..9e28e95 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -69,7 +69,7 @@ func (s Session) getResticEnv(backupConf formolv1alpha1.BackupConfiguration) (en return } -func (s Session) setResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { +func (s Session) SetResticEnv(backupConf formolv1alpha1.BackupConfiguration) error { envs, err := s.getResticEnv(backupConf) for _, env := range envs { os.Setenv(env.Name, env.Value) @@ -95,9 +95,8 @@ func (s Session) CheckRepo() error { func (s Session) getSecretData(name string) map[string][]byte { secret := corev1.Secret{} - namespace := os.Getenv(formolv1alpha1.POD_NAMESPACE) if err := s.Get(s.Context, client.ObjectKey{ - Namespace: namespace, + Namespace: s.Namespace, Name: name, }, &secret); err != nil { s.Log.Error(err, "unable to get Secret", "Secret", name) diff --git a/formol b/formol index e73ef7c..f890962 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit e73ef7c3f24ee612421b62963f443ff1a1e790dc +Subproject commit f890962221bf05fb54c9d46d839957a59d644cc8 diff --git a/standalone/root.go b/standalone/root.go index 53ec3c3..f01fed5 100644 --- a/standalone/root.go +++ b/standalone/root.go @@ -141,3 +141,26 @@ func CreateBackupSession(ref corev1.ObjectReference) { os.Exit(1) } } + +func DeleteSnapshot(namespace string, name string, snapshotId string) { + log := session.Log.WithName("DeleteSnapshot") + session.Namespace = namespace + backupConf := formolv1alpha1.BackupConfiguration{} + if err := session.Get(session.Context, client.ObjectKey{ + Namespace: namespace, + Name: name, + }, &backupConf); err != nil { + log.Error(err, "unable to get the BackupConf") + return + } + if err := session.SetResticEnv(backupConf); err != nil { + log.Error(err, "unable to set the restic env") + return + } + log.V(0).Info("deleting restic snapshot", "snapshotId", snapshotId) + cmd := exec.Command(controllers.RESTIC_EXEC, "forget", "--prune", snapshotId) + _, err := cmd.CombinedOutput() + if err != nil { + log.Error(err, "unable to delete snapshot", "snapshoId", snapshotId) + } +} From 8b2aaf7211f4cce64f4ec294ab658ed12db37d85 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Tue, 11 Apr 2023 10:48:57 +0200 Subject: [PATCH 26/37] First volume snapshot created --- controllers/backupsession_controller.go | 15 ++ .../backupsession_controller_helpers.go | 143 ++++++++++++++++++ controllers/server.go | 4 + formol | 2 +- go.mod | 7 +- 5 files changed, 167 insertions(+), 4 deletions(-) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 75a8e16..05bd614 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -115,6 +115,21 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques targetStatus.SnapshotId = backupResult.SnapshotId targetStatus.Duration = &metav1.Duration{Duration: time.Now().Sub(targetStatus.StartTime.Time)} } + case formolv1alpha1.SnapshotKind: + if err := r.backupSnapshot(target); err != nil { + switch err.(type) { + case *NotReadyToUseError: + r.Log.V(0).Info("Volume snapshots are not ready. Requeueing") + return ctrl.Result{ + Requeue: true, + }, nil + default: + r.Log.Error(err, "unable to do snapshot backup") + // TODO: cleanup existing snapshots + r.deleteVolumeSnapshots(target) + newSessionState = formolv1alpha1.Failure + } + } } r.Log.V(0).Info("Backup is over and is a success. Move to Waiting state") case formolv1alpha1.Finalize: diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index bb05727..fcdd40e 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -4,9 +4,18 @@ import ( "bufio" "encoding/json" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" "io" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" "os/exec" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + SNAPSHOT_PREFIX = "formol-" ) type BackupResult struct { @@ -69,3 +78,137 @@ func (r *BackupSessionReconciler) backupJob(tag string, target formolv1alpha1.Ta result, err = r.backupPaths(tag, paths) return } + +func (r *BackupSessionReconciler) backupSnapshot(target formolv1alpha1.Target) error { + targetObject, targetPodSpec := formolv1alpha1.GetTargetObjects(target.TargetKind) + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: r.Namespace, + Name: target.TargetName, + }, targetObject); err != nil { + r.Log.Error(err, "cannot get target", "target", target.TargetName) + return err + } + for _, container := range targetPodSpec.Containers { + for _, targetContainer := range target.Containers { + if targetContainer.Name == container.Name { + // Now snapshot all the container PVC that support snapshots + // then create new volumes from the snapshots + // replace the volumes in the container struct with the snapshot volumes + // use formolv1alpha1.GetVolumeMounts to get the volume mounts for the Job + // sidecar := formolv1alpha1.GetSidecar(backupConf, target) + _, vms := formolv1alpha1.GetVolumeMounts(container, targetContainer) + if err := r.snapshotVolumes(vms, targetPodSpec); err != nil { + r.Log.Error(err, "volume snapshot not ready") + return err + } + + } + } + } + return nil +} + +type NotReadyToUseError struct{} + +func (e *NotReadyToUseError) Error() string { + return "Snapshot is not ready to use" +} + +func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) error { + r.Log.V(0).Info("Preparing snapshot", "volume", volume.Name) + if volume.VolumeSource.PersistentVolumeClaim != nil { + pvc := corev1.PersistentVolumeClaim{} + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: r.Namespace, + Name: volume.VolumeSource.PersistentVolumeClaim.ClaimName, + }, &pvc); err != nil { + r.Log.Error(err, "unable to get pvc", "volume", volume) + return err + } + pv := corev1.PersistentVolume{} + if err := r.Get(r.Context, client.ObjectKey{ + Name: pvc.Spec.VolumeName, + }, &pv); err != nil { + r.Log.Error(err, "unable to get pv", "volume", pvc.Spec.VolumeName) + return err + } + if pv.Spec.PersistentVolumeSource.CSI != nil { + // This volume is supported by a CSI driver. Let's see if we can snapshot it. + volumeSnapshotClassList := volumesnapshotv1.VolumeSnapshotClassList{} + if err := r.List(r.Context, &volumeSnapshotClassList); err != nil { + r.Log.Error(err, "unable to get VolumeSnapshotClass list") + return err + } + for _, volumeSnapshotClass := range volumeSnapshotClassList.Items { + if volumeSnapshotClass.Driver == pv.Spec.PersistentVolumeSource.CSI.Driver { + // Check if a snapshot exist + volumeSnapshot := volumesnapshotv1.VolumeSnapshot{} + if err := r.Get(r.Context, client.ObjectKey{ + Namespace: r.Namespace, + Name: SNAPSHOT_PREFIX + pv.Name, + }, &volumeSnapshot); errors.IsNotFound(err) { + // No snapshot found. Create a new one. + // We want to snapshot using this VolumeSnapshotClass + r.Log.V(0).Info("Create a volume snapshot", "pvc", pvc.Name) + volumeSnapshot = volumesnapshotv1.VolumeSnapshot{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.Namespace, + Name: SNAPSHOT_PREFIX + pv.Name, + }, + Spec: volumesnapshotv1.VolumeSnapshotSpec{ + VolumeSnapshotClassName: &volumeSnapshotClass.Name, + Source: volumesnapshotv1.VolumeSnapshotSource{ + PersistentVolumeClaimName: &pvc.Name, + }, + }, + } + if err := r.Create(r.Context, &volumeSnapshot); err != nil { + r.Log.Error(err, "unable to create the snapshot", "pvc", pvc.Name) + return err + } + // We just created the snapshot. We have to assume it's not yet ready and reschedule + return &NotReadyToUseError{} + } else { + if err != nil { + r.Log.Error(err, "Something went very wrong here") + return err + } + // The VolumeSnapshot exists. Is it ReadyToUse? + if volumeSnapshot.Status == nil || volumeSnapshot.Status.ReadyToUse == nil || *volumeSnapshot.Status.ReadyToUse == false { + r.Log.V(0).Info("Volume snapshot exists but it is not ready", "volume", volumeSnapshot.Name) + return &NotReadyToUseError{} + } + r.Log.V(0).Info("Volume snapshot is ready to use", "volume", volumeSnapshot.Name) + } + } + } + } + } + return nil +} + +func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podSpec *corev1.PodSpec) (err error) { + // We snapshot/check all the volumes. If at least one of the snapshot is not ready to use. We reschedule. + for _, vm := range vms { + for _, volume := range podSpec.Volumes { + if vm.Name == volume.Name { + err = r.snapshotVolume(volume) + if err != nil { + switch err.(type) { + case *NotReadyToUseError: + defer func() { + err = &NotReadyToUseError{} + }() + default: + return + } + } + } + } + } + return +} + +func (r *BackupSessionReconciler) deleteVolumeSnapshots(target formolv1alpha1.Target) error { + return nil +} diff --git a/controllers/server.go b/controllers/server.go index aa2d70d..782dfb9 100644 --- a/controllers/server.go +++ b/controllers/server.go @@ -1,6 +1,8 @@ package controllers import ( + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/runtime" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" @@ -20,6 +22,8 @@ var ( func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(formolv1alpha1.AddToScheme(scheme)) + utilruntime.Must(volumesnapshotv1.AddToScheme(scheme)) + utilruntime.Must(corev1.AddToScheme(scheme)) } func StartServer() { diff --git a/formol b/formol index f890962..d8b685c 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit f890962221bf05fb54c9d46d839957a59d644cc8 +Subproject commit d8b685c1ab88f0edf34b17850e6359b1eb632d25 diff --git a/go.mod b/go.mod index 3a950b6..b2794e1 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.19 require ( github.com/desmo999r/formol v0.8.0 github.com/go-logr/logr v1.2.3 + github.com/kubernetes-csi/external-snapshotter/client/v6 v6.2.0 github.com/spf13/cobra v1.6.1 k8s.io/api v0.26.1 k8s.io/apimachinery v0.26.1 @@ -22,11 +23,11 @@ require ( github.com/go-logr/zapr v1.2.3 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonreference v0.20.0 // indirect - github.com/go-openapi/swag v0.19.14 // indirect + github.com/go-openapi/swag v0.22.3 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.2 // indirect - github.com/google/gnostic v0.5.7-v3refs // indirect + github.com/google/gnostic v0.6.9 // indirect github.com/google/go-cmp v0.5.9 // indirect github.com/google/gofuzz v1.1.0 // indirect github.com/google/uuid v1.1.2 // indirect @@ -34,7 +35,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/mailru/easyjson v0.7.6 // indirect + github.com/mailru/easyjson v0.7.7 // indirect github.com/matttproud/golang_protobuf_extensions v1.0.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect From 7ca94b404878a3ef0763ce4de487e6697265ffa7 Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Tue, 11 Apr 2023 18:19:30 +0200 Subject: [PATCH 27/37] A step farther with create volume from snapshot --- .../backupsession_controller_helpers.go | 37 +++++++++++++------ formol | 2 +- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index fcdd40e..896076c 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -98,8 +98,13 @@ func (r *BackupSessionReconciler) backupSnapshot(target formolv1alpha1.Target) e // sidecar := formolv1alpha1.GetSidecar(backupConf, target) _, vms := formolv1alpha1.GetVolumeMounts(container, targetContainer) if err := r.snapshotVolumes(vms, targetPodSpec); err != nil { - r.Log.Error(err, "volume snapshot not ready") - return err + switch err.(type) { + case *NotReadyToUseError: + r.Log.V(0).Info("Some volumes are still not ready to use") + default: + r.Log.Error(err, "cannot snapshot the volumes") + return err + } } } @@ -114,7 +119,7 @@ func (e *NotReadyToUseError) Error() string { return "Snapshot is not ready to use" } -func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) error { +func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) (*volumesnapshotv1.VolumeSnapshot, error) { r.Log.V(0).Info("Preparing snapshot", "volume", volume.Name) if volume.VolumeSource.PersistentVolumeClaim != nil { pvc := corev1.PersistentVolumeClaim{} @@ -123,21 +128,21 @@ func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) error { Name: volume.VolumeSource.PersistentVolumeClaim.ClaimName, }, &pvc); err != nil { r.Log.Error(err, "unable to get pvc", "volume", volume) - return err + return nil, err } pv := corev1.PersistentVolume{} if err := r.Get(r.Context, client.ObjectKey{ Name: pvc.Spec.VolumeName, }, &pv); err != nil { r.Log.Error(err, "unable to get pv", "volume", pvc.Spec.VolumeName) - return err + return nil, err } if pv.Spec.PersistentVolumeSource.CSI != nil { // This volume is supported by a CSI driver. Let's see if we can snapshot it. volumeSnapshotClassList := volumesnapshotv1.VolumeSnapshotClassList{} if err := r.List(r.Context, &volumeSnapshotClassList); err != nil { r.Log.Error(err, "unable to get VolumeSnapshotClass list") - return err + return nil, err } for _, volumeSnapshotClass := range volumeSnapshotClassList.Items { if volumeSnapshotClass.Driver == pv.Spec.PersistentVolumeSource.CSI.Driver { @@ -164,27 +169,31 @@ func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) error { } if err := r.Create(r.Context, &volumeSnapshot); err != nil { r.Log.Error(err, "unable to create the snapshot", "pvc", pvc.Name) - return err + return nil, err } // We just created the snapshot. We have to assume it's not yet ready and reschedule - return &NotReadyToUseError{} + return nil, &NotReadyToUseError{} } else { if err != nil { r.Log.Error(err, "Something went very wrong here") - return err + return nil, err } // The VolumeSnapshot exists. Is it ReadyToUse? if volumeSnapshot.Status == nil || volumeSnapshot.Status.ReadyToUse == nil || *volumeSnapshot.Status.ReadyToUse == false { r.Log.V(0).Info("Volume snapshot exists but it is not ready", "volume", volumeSnapshot.Name) - return &NotReadyToUseError{} + return nil, &NotReadyToUseError{} } r.Log.V(0).Info("Volume snapshot is ready to use", "volume", volumeSnapshot.Name) + return &volumeSnapshot, nil } } } } } - return nil + return nil, nil +} + +func (r *BackupSessionReconciler) createVolumeFromSnapshot(vs *volumesnapshotv1.VolumeSnapshot) { } func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podSpec *corev1.PodSpec) (err error) { @@ -192,7 +201,8 @@ func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podS for _, vm := range vms { for _, volume := range podSpec.Volumes { if vm.Name == volume.Name { - err = r.snapshotVolume(volume) + var vs *volumesnapshotv1.VolumeSnapshot + vs, err = r.snapshotVolume(volume) if err != nil { switch err.(type) { case *NotReadyToUseError: @@ -203,6 +213,9 @@ func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podS return } } + if vs != nil { + r.createVolumeFromSnapshot(vs) + } } } } diff --git a/formol b/formol index d8b685c..61f45a7 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit d8b685c1ab88f0edf34b17850e6359b1eb632d25 +Subproject commit 61f45a79404e1f71d9f7661d295d6ac3cd07dd8c From 729505a216cce45c75210946bde8070628df1e59 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Fri, 14 Apr 2023 20:39:21 +0200 Subject: [PATCH 28/37] changed backupsession prefix to 'bs' --- standalone/root.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/standalone/root.go b/standalone/root.go index f01fed5..bf6bf21 100644 --- a/standalone/root.go +++ b/standalone/root.go @@ -22,6 +22,10 @@ import ( "time" ) +const ( + BACKUPSESSION_PREFIX = "bs" +) + var ( session controllers.Session ) @@ -128,7 +132,7 @@ func CreateBackupSession(ref corev1.ObjectReference) { backupSession := &formolv1alpha1.BackupSession{ ObjectMeta: metav1.ObjectMeta{ - Name: strings.Join([]string{"backupsession", ref.Name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), + Name: strings.Join([]string{BACKUPSESSION_PREFIX, ref.Name, strconv.FormatInt(time.Now().Unix(), 10)}, "-"), Namespace: ref.Namespace, }, Spec: formolv1alpha1.BackupSessionSpec{ From e7bb4b114936898270ae7dd223540274fe28d0af Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Fri, 14 Apr 2023 20:43:31 +0200 Subject: [PATCH 29/37] Added Name alongside to Namespace to Session --- controllers/backupsession_controller.go | 7 ++++--- controllers/backupsession_controller_helpers.go | 8 ++++---- controllers/restoresession_controller.go | 3 ++- controllers/session.go | 1 + 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 05bd614..6d0ef1f 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -21,6 +21,8 @@ type BackupSessionReconciler struct { func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { r.Log = log.FromContext(ctx) r.Context = ctx + r.Namespace = req.NamespacedName.Namespace + r.Name = req.NamespacedName.Name backupSession := formolv1alpha1.BackupSession{} err := r.Get(ctx, req.NamespacedName, &backupSession) @@ -47,7 +49,6 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } return ctrl.Result{}, err } - r.Namespace = backupConf.Namespace // we don't want a copy because we will modify and update it. var target formolv1alpha1.Target @@ -96,7 +97,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques newSessionState = formolv1alpha1.Waiting switch target.BackupType { case formolv1alpha1.JobKind: - if backupResult, err := r.backupJob(backupSession.Name, target); err != nil { + if backupResult, err := r.backupJob(target); err != nil { r.Log.Error(err, "unable to run backup job", "target", targetName) newSessionState = formolv1alpha1.Failure } else { @@ -106,7 +107,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } case formolv1alpha1.OnlineKind: backupPaths := strings.Split(os.Getenv(formolv1alpha1.BACKUP_PATHS), string(os.PathListSeparator)) - if backupResult, result := r.backupPaths(backupSession.Name, backupPaths); result != nil { + if backupResult, result := r.backupPaths(backupPaths); result != nil { r.Log.Error(result, "unable to backup paths", "target name", targetName, "paths", backupPaths) newSessionState = formolv1alpha1.Failure } else { diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 896076c..a27aa88 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -23,13 +23,13 @@ type BackupResult struct { Duration float64 } -func (r *BackupSessionReconciler) backupPaths(tag string, paths []string) (result BackupResult, err error) { +func (r *BackupSessionReconciler) backupPaths(paths []string) (result BackupResult, err error) { if err = r.CheckRepo(); err != nil { r.Log.Error(err, "unable to setup repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) return } r.Log.V(0).Info("backing up paths", "paths", paths) - cmd := exec.Command(RESTIC_EXEC, append([]string{"backup", "--json", "--tag", tag}, paths...)...) + cmd := exec.Command(RESTIC_EXEC, append([]string{"backup", "--json", "--tag", r.Name}, paths...)...) stdout, _ := cmd.StdoutPipe() stderr, _ := cmd.StderrPipe() _ = cmd.Start() @@ -55,7 +55,7 @@ func (r *BackupSessionReconciler) backupPaths(tag string, paths []string) (resul return } -func (r *BackupSessionReconciler) backupJob(tag string, target formolv1alpha1.Target) (result BackupResult, err error) { +func (r *BackupSessionReconciler) backupJob(target formolv1alpha1.Target) (result BackupResult, err error) { paths := []string{} for _, container := range target.Containers { for _, job := range container.Job { @@ -75,7 +75,7 @@ func (r *BackupSessionReconciler) backupJob(tag string, target formolv1alpha1.Ta paths = append(paths, container.SharePath) } } - result, err = r.backupPaths(tag, paths) + result, err = r.backupPaths(paths) return } diff --git a/controllers/restoresession_controller.go b/controllers/restoresession_controller.go index 8a53c10..8ebdcc0 100644 --- a/controllers/restoresession_controller.go +++ b/controllers/restoresession_controller.go @@ -19,6 +19,8 @@ type RestoreSessionReconciler struct { func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { r.Log = log.FromContext(ctx) r.Context = ctx + r.Namespace = req.NamespacedName.Namespace + r.Name = req.NamespacedName.Name restoreSession := formolv1alpha1.RestoreSession{} err := r.Get(r.Context, req.NamespacedName, &restoreSession) @@ -49,7 +51,6 @@ func (r *RestoreSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reque } return ctrl.Result{}, err } - r.Namespace = backupConf.Namespace r.backupConf = backupConf // we don't want a copy because we will modify and update it. diff --git a/controllers/session.go b/controllers/session.go index 9e28e95..309df79 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -27,6 +27,7 @@ type Session struct { Scheme *runtime.Scheme context.Context Namespace string + Name string } const ( From 9a49ac96c4b4a8f5181e9dc860ff279d1e3407db Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Fri, 14 Apr 2023 20:45:49 +0200 Subject: [PATCH 30/37] Create PVC from VolumeSnapshot --- .../backupsession_controller_helpers.go | 90 +++++++++++++++---- 1 file changed, 73 insertions(+), 17 deletions(-) diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index a27aa88..971cd8a 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -12,10 +12,7 @@ import ( "os" "os/exec" "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - SNAPSHOT_PREFIX = "formol-" + "strings" ) type BackupResult struct { @@ -119,6 +116,15 @@ func (e *NotReadyToUseError) Error() string { return "Snapshot is not ready to use" } +func IsNotReadyToUse(err error) bool { + switch err.(type) { + case *NotReadyToUseError: + return true + default: + return false + } +} + func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) (*volumesnapshotv1.VolumeSnapshot, error) { r.Log.V(0).Info("Preparing snapshot", "volume", volume.Name) if volume.VolumeSource.PersistentVolumeClaim != nil { @@ -148,9 +154,11 @@ func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) (*volumes if volumeSnapshotClass.Driver == pv.Spec.PersistentVolumeSource.CSI.Driver { // Check if a snapshot exist volumeSnapshot := volumesnapshotv1.VolumeSnapshot{} + volumeSnapshotName := strings.Join([]string{"vs", r.Name, pv.Name}, "-") + if err := r.Get(r.Context, client.ObjectKey{ Namespace: r.Namespace, - Name: SNAPSHOT_PREFIX + pv.Name, + Name: volumeSnapshotName, }, &volumeSnapshot); errors.IsNotFound(err) { // No snapshot found. Create a new one. // We want to snapshot using this VolumeSnapshotClass @@ -158,7 +166,7 @@ func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) (*volumes volumeSnapshot = volumesnapshotv1.VolumeSnapshot{ ObjectMeta: metav1.ObjectMeta{ Namespace: r.Namespace, - Name: SNAPSHOT_PREFIX + pv.Name, + Name: volumeSnapshotName, }, Spec: volumesnapshotv1.VolumeSnapshotSpec{ VolumeSnapshotClassName: &volumeSnapshotClass.Name, @@ -193,28 +201,76 @@ func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) (*volumes return nil, nil } -func (r *BackupSessionReconciler) createVolumeFromSnapshot(vs *volumesnapshotv1.VolumeSnapshot) { +func (r *BackupSessionReconciler) createVolumeFromSnapshot(vs *volumesnapshotv1.VolumeSnapshot) (backupPVCName string, err error) { + backupPVCName = strings.Replace(vs.Name, "vs", "bak", 1) + backupPVC := corev1.PersistentVolumeClaim{} + if err = r.Get(r.Context, client.ObjectKey{ + Namespace: r.Namespace, + Name: backupPVCName, + }, &backupPVC); errors.IsNotFound(err) { + // The Volume does not exist. Create it. + pv := corev1.PersistentVolume{} + pvName, _ := strings.CutPrefix(vs.Name, strings.Join([]string{"vs", r.Name}, "-")) + if err = r.Get(r.Context, client.ObjectKey{ + Name: pvName, + }, &pv); err != nil { + r.Log.Error(err, "unable to find pv", "pv", pvName) + return + } + backupPVC = corev1.PersistentVolumeClaim{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.Namespace, + Name: backupPVCName, + }, + Spec: corev1.PersistentVolumeClaimSpec{ + StorageClassName: &pv.Spec.StorageClassName, + //AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany}, + AccessModes: pv.Spec.AccessModes, + DataSource: &corev1.TypedLocalObjectReference{ + APIGroup: func() *string { s := "snapshot.storage.k8s.io"; return &s }(), + Kind: "VolumeSnapshot", + Name: vs.Name, + }, + }, + } + if err = r.Create(r.Context, &backupPVC); err != nil { + r.Log.Error(err, "unable to create backup PVC", "backupPVC", backupPVC) + return + } + } + if err != nil { + r.Log.Error(err, "something went very wrong here") + } + return } func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podSpec *corev1.PodSpec) (err error) { // We snapshot/check all the volumes. If at least one of the snapshot is not ready to use. We reschedule. for _, vm := range vms { - for _, volume := range podSpec.Volumes { + for i, volume := range podSpec.Volumes { if vm.Name == volume.Name { var vs *volumesnapshotv1.VolumeSnapshot vs, err = r.snapshotVolume(volume) + if IsNotReadyToUse(err) { + defer func() { + err = &NotReadyToUseError{} + }() + continue + } if err != nil { - switch err.(type) { - case *NotReadyToUseError: - defer func() { - err = &NotReadyToUseError{} - }() - default: - return - } + return } if vs != nil { - r.createVolumeFromSnapshot(vs) + backupPVCName, err := r.createVolumeFromSnapshot(vs) + if err != nil { + r.Log.Error(err, "unable to create volume from snapshot", "vs", vs) + return + } + podSpec.Volumes[i].VolumeSource.PersistentVolumeClaim = &corev1.PersistentVolumeClaimVolumeSource{ + ClaimName: backupPVCName, + ReadOnly: true, + } + // The snapshot and the volume will be deleted by the Job when the backup is over } } } From 1de6a31e253e77bb169788328c065cc5700d2dfb Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Fri, 14 Apr 2023 20:46:25 +0200 Subject: [PATCH 31/37] VolumeSnapshot will be deleted by the Job --- controllers/backupsession_controller.go | 2 -- controllers/backupsession_controller_helpers.go | 15 +++++---------- formol | 2 +- 3 files changed, 6 insertions(+), 13 deletions(-) diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 6d0ef1f..145ece3 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -127,8 +127,6 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques default: r.Log.Error(err, "unable to do snapshot backup") // TODO: cleanup existing snapshots - r.deleteVolumeSnapshots(target) - newSessionState = formolv1alpha1.Failure } } } diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 971cd8a..4efd47b 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -95,10 +95,9 @@ func (r *BackupSessionReconciler) backupSnapshot(target formolv1alpha1.Target) e // sidecar := formolv1alpha1.GetSidecar(backupConf, target) _, vms := formolv1alpha1.GetVolumeMounts(container, targetContainer) if err := r.snapshotVolumes(vms, targetPodSpec); err != nil { - switch err.(type) { - case *NotReadyToUseError: + if IsNotReadyToUse(err) { r.Log.V(0).Info("Some volumes are still not ready to use") - default: + } else { r.Log.Error(err, "cannot snapshot the volumes") return err } @@ -225,7 +224,7 @@ func (r *BackupSessionReconciler) createVolumeFromSnapshot(vs *volumesnapshotv1. Spec: corev1.PersistentVolumeClaimSpec{ StorageClassName: &pv.Spec.StorageClassName, //AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany}, - AccessModes: pv.Spec.AccessModes, + AccessModes: pv.Spec.AccessModes, DataSource: &corev1.TypedLocalObjectReference{ APIGroup: func() *string { s := "snapshot.storage.k8s.io"; return &s }(), Kind: "VolumeSnapshot", @@ -264,11 +263,11 @@ func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podS backupPVCName, err := r.createVolumeFromSnapshot(vs) if err != nil { r.Log.Error(err, "unable to create volume from snapshot", "vs", vs) - return + return err } podSpec.Volumes[i].VolumeSource.PersistentVolumeClaim = &corev1.PersistentVolumeClaimVolumeSource{ ClaimName: backupPVCName, - ReadOnly: true, + ReadOnly: true, } // The snapshot and the volume will be deleted by the Job when the backup is over } @@ -277,7 +276,3 @@ func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podS } return } - -func (r *BackupSessionReconciler) deleteVolumeSnapshots(target formolv1alpha1.Target) error { - return nil -} diff --git a/formol b/formol index 61f45a7..8975f77 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit 61f45a79404e1f71d9f7661d295d6ac3cd07dd8c +Subproject commit 8975f77e5858ee167508ef0359c3b9d6cbaba6ee From 1f2baef062e1865de9e5da189a7d8d04b257c615 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Fri, 14 Apr 2023 20:54:26 +0200 Subject: [PATCH 32/37] reworked the multiplatform Makefile --- Dockerfile.arm64 | 2 +- Makefile | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 083758d..5e39629 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -1,6 +1,6 @@ # Build a small image FROM --platform=linux/arm64 alpine:3 -RUN apk add --no-cache su-exec restic postgresql-client +RUN apk add --no-cache su-exec restic COPY ./bin/formolcli /usr/local/bin # Command to run diff --git a/Makefile b/Makefile index aeece64..700240b 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,18 @@ GOARCH ?= amd64 GOOS ?= linux -IMG ?= docker.io/desmo999r/formolcli:latest +VERSION ?= latest +IMG ?= docker.io/desmo999r/formolcli:$(VERSION) +MANIFEST = formol-multiarch BINDIR = ./bin .PHONY: formolcli formolcli: fmt vet GO111MODULE=on CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(BINDIR)/formolcli main.go +#.PHONY: formolcli-arm64 +#formolcli-arm64: GOARCH = arm64 +#formolcli-arm64: formolcli + .PHONY: fmt fmt: go fmt ./... @@ -17,11 +23,15 @@ vet: .PHONY: docker-build docker-build: formolcli - buildah bud --tag $(IMG) Dockerfile.$(GOARCH) + buildah bud --tag $(IMG) --manifest $(MANIFEST) --arch $(GOARCH) Dockerfile.$(GOARCH) + +.PHONY: docker-build-arm64 +docker-build-arm64: GOARCH = arm64 +docker-build-arm64: docker-build .PHONY: docker-push -docker-push: docker-build - buildah push $(IMG) +docker-push: + buildah manifest push --all --rm $(MANIFEST) "docker://$(IMG)" .PHONY: all all: formolcli docker-build From fac6d9b62032acd71ecdcee3e005e7b6609ca4dd Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Mon, 17 Apr 2023 01:10:56 +0200 Subject: [PATCH 33/37] backup snapshot finalized --- cmd/root.go | 18 ++++ controllers/backupsession_controller.go | 24 +++-- .../backupsession_controller_helpers.go | 101 ++++++++++-------- controllers/session.go | 44 +++++++- formol | 2 +- standalone/root.go | 67 +++++++++++- 6 files changed, 198 insertions(+), 58 deletions(-) diff --git a/cmd/root.go b/cmd/root.go index b0ec277..9bca20a 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -26,6 +26,17 @@ var createBackupSessionCmd = &cobra.Command{ }, } +var backupCmd = &cobra.Command{ + Use: "backup", + Short: "Backup paths", + Run: func(cmd *cobra.Command, args []string) { + backupSessionName, _ := cmd.Flags().GetString("name") + backupSessionNamespace, _ := cmd.Flags().GetString("namespace") + targetName, _ := cmd.Flags().GetString("target-name") + standalone.BackupPaths(backupSessionName, backupSessionNamespace, targetName, args...) + }, +} + var startRestoreSessionCmd = &cobra.Command{ Use: "start", Short: "Restore a restic snapshot", @@ -101,6 +112,7 @@ func init() { rootCmd.AddCommand(restoreSessionCmd) rootCmd.AddCommand(snapshotCmd) backupSessionCmd.AddCommand(createBackupSessionCmd) + backupSessionCmd.AddCommand(backupCmd) restoreSessionCmd.AddCommand(startRestoreSessionCmd) snapshotCmd.AddCommand(deleteSnapshotCmd) rootCmd.AddCommand(startServerCmd) @@ -108,6 +120,12 @@ func init() { createBackupSessionCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") createBackupSessionCmd.MarkFlagRequired("namespace") createBackupSessionCmd.MarkFlagRequired("name") + backupCmd.Flags().String("target-name", "", "The name of target being restored") + backupCmd.Flags().String("namespace", "", "The namespace of the BackupConfiguration containing the information about the backup.") + backupCmd.Flags().String("name", "", "The name of the BackupConfiguration containing the information about the backup.") + backupCmd.MarkFlagRequired("namespace") + backupCmd.MarkFlagRequired("name") + backupCmd.MarkFlagRequired("target-name") startRestoreSessionCmd.Flags().String("namespace", "", "The namespace of RestoreSession") startRestoreSessionCmd.Flags().String("name", "", "The name of RestoreSession") startRestoreSessionCmd.Flags().String("target-name", "", "The name of target being restored") diff --git a/controllers/backupsession_controller.go b/controllers/backupsession_controller.go index 145ece3..1c487e0 100644 --- a/controllers/backupsession_controller.go +++ b/controllers/backupsession_controller.go @@ -16,6 +16,8 @@ import ( type BackupSessionReconciler struct { Session + backupSession formolv1alpha1.BackupSession + backupConf formolv1alpha1.BackupConfiguration } func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { @@ -32,6 +34,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } return ctrl.Result{}, err } + r.backupSession = backupSession if len(backupSession.Status.Targets) == 0 { // The main BackupSession controller hasn't assigned a backup task yet // Wait a bit @@ -49,6 +52,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } return ctrl.Result{}, err } + r.backupConf = backupConf // we don't want a copy because we will modify and update it. var target formolv1alpha1.Target @@ -107,7 +111,7 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } case formolv1alpha1.OnlineKind: backupPaths := strings.Split(os.Getenv(formolv1alpha1.BACKUP_PATHS), string(os.PathListSeparator)) - if backupResult, result := r.backupPaths(backupPaths); result != nil { + if backupResult, result := r.BackupPaths(backupPaths); result != nil { r.Log.Error(result, "unable to backup paths", "target name", targetName, "paths", backupPaths) newSessionState = formolv1alpha1.Failure } else { @@ -118,15 +122,14 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques } case formolv1alpha1.SnapshotKind: if err := r.backupSnapshot(target); err != nil { - switch err.(type) { - case *NotReadyToUseError: + if IsNotReadyToUse(err) { r.Log.V(0).Info("Volume snapshots are not ready. Requeueing") return ctrl.Result{ Requeue: true, }, nil - default: + } else { r.Log.Error(err, "unable to do snapshot backup") - // TODO: cleanup existing snapshots + return ctrl.Result{}, err } } } @@ -138,10 +141,15 @@ func (r *BackupSessionReconciler) Reconcile(ctx context.Context, req ctrl.Reques if result = r.runFinalizeSteps(target); result != nil { r.Log.Error(err, "unable to run finalize steps") } - if targetStatus.SnapshotId == "" { - newSessionState = formolv1alpha1.Failure + if target.BackupType == formolv1alpha1.SnapshotKind { + // SnapshotKind special state where we wait for the backup Job to finish + newSessionState = formolv1alpha1.WaitingForJob } else { - newSessionState = formolv1alpha1.Success + if targetStatus.SnapshotId == "" { + newSessionState = formolv1alpha1.Failure + } else { + newSessionState = formolv1alpha1.Success + } } case formolv1alpha1.Success: // Target backup is a success diff --git a/controllers/backupsession_controller_helpers.go b/controllers/backupsession_controller_helpers.go index 4efd47b..d94154d 100644 --- a/controllers/backupsession_controller_helpers.go +++ b/controllers/backupsession_controller_helpers.go @@ -1,56 +1,20 @@ package controllers import ( - "bufio" - "encoding/json" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" - "io" + batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "os" - "os/exec" "sigs.k8s.io/controller-runtime/pkg/client" "strings" ) -type BackupResult struct { - SnapshotId string - Duration float64 -} - -func (r *BackupSessionReconciler) backupPaths(paths []string) (result BackupResult, err error) { - if err = r.CheckRepo(); err != nil { - r.Log.Error(err, "unable to setup repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) - return - } - r.Log.V(0).Info("backing up paths", "paths", paths) - cmd := exec.Command(RESTIC_EXEC, append([]string{"backup", "--json", "--tag", r.Name}, paths...)...) - stdout, _ := cmd.StdoutPipe() - stderr, _ := cmd.StderrPipe() - _ = cmd.Start() - - scanner := bufio.NewScanner(io.MultiReader(stdout, stderr)) - scanner.Split(bufio.ScanLines) - var data map[string]interface{} - for scanner.Scan() { - if err := json.Unmarshal(scanner.Bytes(), &data); err != nil { - r.Log.Error(err, "unable to unmarshal json", "data", scanner.Text()) - continue - } - switch data["message_type"].(string) { - case "summary": - result.SnapshotId = data["snapshot_id"].(string) - result.Duration = data["total_duration"].(float64) - case "status": - r.Log.V(0).Info("backup running", "percent done", data["percent_done"].(float64)) - } - } - - err = cmd.Wait() - return -} +const ( + JOBTTL int32 = 7200 +) func (r *BackupSessionReconciler) backupJob(target formolv1alpha1.Target) (result BackupResult, err error) { paths := []string{} @@ -72,11 +36,11 @@ func (r *BackupSessionReconciler) backupJob(target formolv1alpha1.Target) (resul paths = append(paths, container.SharePath) } } - result, err = r.backupPaths(paths) + result, err = r.BackupPaths(paths) return } -func (r *BackupSessionReconciler) backupSnapshot(target formolv1alpha1.Target) error { +func (r *BackupSessionReconciler) backupSnapshot(target formolv1alpha1.Target) (e error) { targetObject, targetPodSpec := formolv1alpha1.GetTargetObjects(target.TargetKind) if err := r.Get(r.Context, client.ObjectKey{ Namespace: r.Namespace, @@ -93,16 +57,54 @@ func (r *BackupSessionReconciler) backupSnapshot(target formolv1alpha1.Target) e // replace the volumes in the container struct with the snapshot volumes // use formolv1alpha1.GetVolumeMounts to get the volume mounts for the Job // sidecar := formolv1alpha1.GetSidecar(backupConf, target) - _, vms := formolv1alpha1.GetVolumeMounts(container, targetContainer) + paths, vms := formolv1alpha1.GetVolumeMounts(container, targetContainer) if err := r.snapshotVolumes(vms, targetPodSpec); err != nil { if IsNotReadyToUse(err) { r.Log.V(0).Info("Some volumes are still not ready to use") + defer func() { e = &NotReadyToUseError{} }() } else { r.Log.Error(err, "cannot snapshot the volumes") return err } + } else { + r.Log.V(1).Info("Creating a Job to backup the Snapshot volumes") + sidecar := formolv1alpha1.GetSidecar(r.backupConf, target) + sidecar.Args = append([]string{"backupsession", "backup", "--namespace", r.Namespace, "--name", r.Name, "--target-name", target.TargetName}, paths...) + sidecar.VolumeMounts = vms + if env, err := r.getResticEnv(r.backupConf); err != nil { + r.Log.Error(err, "unable to get restic env") + return err + } else { + sidecar.Env = append(sidecar.Env, env...) + } + sidecar.Env = append(sidecar.Env, corev1.EnvVar{ + Name: formolv1alpha1.BACKUP_PATHS, + Value: strings.Join(paths, string(os.PathListSeparator)), + }) + job := batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: r.Namespace, + Name: "backupsnapshot-" + r.Name, + }, + Spec: batchv1.JobSpec{ + TTLSecondsAfterFinished: func() *int32 { ttl := JOBTTL; return &ttl }(), + Template: corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Volumes: targetPodSpec.Volumes, + Containers: []corev1.Container{ + sidecar, + }, + RestartPolicy: corev1.RestartPolicyNever, + }, + }, + }, + } + if err := r.Create(r.Context, &job); err != nil { + r.Log.Error(err, "unable to create the snapshot volumes backup job", "job", job, "container", sidecar) + return err + } + r.Log.V(1).Info("snapshot volumes backup job created", "job", job.Name) } - } } } @@ -166,6 +168,9 @@ func (r *BackupSessionReconciler) snapshotVolume(volume corev1.Volume) (*volumes ObjectMeta: metav1.ObjectMeta{ Namespace: r.Namespace, Name: volumeSnapshotName, + Labels: map[string]string{ + "backupsession": r.Name, + }, }, Spec: volumesnapshotv1.VolumeSnapshotSpec{ VolumeSnapshotClassName: &volumeSnapshotClass.Name, @@ -210,6 +215,7 @@ func (r *BackupSessionReconciler) createVolumeFromSnapshot(vs *volumesnapshotv1. // The Volume does not exist. Create it. pv := corev1.PersistentVolume{} pvName, _ := strings.CutPrefix(vs.Name, strings.Join([]string{"vs", r.Name}, "-")) + pvName = pvName[1:] if err = r.Get(r.Context, client.ObjectKey{ Name: pvName, }, &pv); err != nil { @@ -220,11 +226,17 @@ func (r *BackupSessionReconciler) createVolumeFromSnapshot(vs *volumesnapshotv1. ObjectMeta: metav1.ObjectMeta{ Namespace: r.Namespace, Name: backupPVCName, + Labels: map[string]string{ + "backupsession": r.Name, + }, }, Spec: corev1.PersistentVolumeClaimSpec{ StorageClassName: &pv.Spec.StorageClassName, //AccessModes: []corev1.PersistentVolumeAccessMode{corev1.ReadOnlyMany}, AccessModes: pv.Spec.AccessModes, + Resources: corev1.ResourceRequirements{ + Requests: pv.Spec.Capacity, + }, DataSource: &corev1.TypedLocalObjectReference{ APIGroup: func() *string { s := "snapshot.storage.k8s.io"; return &s }(), Kind: "VolumeSnapshot", @@ -260,6 +272,7 @@ func (r *BackupSessionReconciler) snapshotVolumes(vms []corev1.VolumeMount, podS return } if vs != nil { + // The snapshot is ready. We create a PVC from it. backupPVCName, err := r.createVolumeFromSnapshot(vs) if err != nil { r.Log.Error(err, "unable to create volume from snapshot", "vs", vs) diff --git a/controllers/session.go b/controllers/session.go index 309df79..07c3be2 100644 --- a/controllers/session.go +++ b/controllers/session.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "context" + "encoding/json" "fmt" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "github.com/go-logr/logr" @@ -23,13 +24,18 @@ import ( type Session struct { client.Client - Log logr.Logger - Scheme *runtime.Scheme context.Context + Log logr.Logger + Scheme *runtime.Scheme Namespace string Name string } +type BackupResult struct { + SnapshotId string + Duration float64 +} + const ( RESTIC_EXEC = "/usr/bin/restic" ) @@ -40,7 +46,7 @@ func (s Session) getResticEnv(backupConf formolv1alpha1.BackupConfiguration) (en Namespace: backupConf.Namespace, Name: backupConf.Spec.Repository, }, &repo); err != nil { - s.Log.Error(err, "unable to get repo") + s.Log.Error(err, "unable to get repo", "backupconf", backupConf) return } if repo.Spec.Backend.S3 != nil { @@ -94,6 +100,38 @@ func (s Session) CheckRepo() error { return err } +func (s Session) BackupPaths(paths []string) (result BackupResult, err error) { + if err = s.CheckRepo(); err != nil { + s.Log.Error(err, "unable to setup repo", "repo", os.Getenv(formolv1alpha1.RESTIC_REPOSITORY)) + return + } + s.Log.V(0).Info("backing up paths", "paths", paths) + cmd := exec.Command(RESTIC_EXEC, append([]string{"backup", "--json", "--tag", s.Name}, paths...)...) + stdout, _ := cmd.StdoutPipe() + stderr, _ := cmd.StderrPipe() + _ = cmd.Start() + + scanner := bufio.NewScanner(io.MultiReader(stdout, stderr)) + scanner.Split(bufio.ScanLines) + var data map[string]interface{} + for scanner.Scan() { + if err := json.Unmarshal(scanner.Bytes(), &data); err != nil { + s.Log.Error(err, "unable to unmarshal json", "data", scanner.Text()) + continue + } + switch data["message_type"].(string) { + case "summary": + result.SnapshotId = data["snapshot_id"].(string) + result.Duration = data["total_duration"].(float64) + case "status": + s.Log.V(0).Info("backup running", "percent done", data["percent_done"].(float64)) + } + } + + err = cmd.Wait() + return +} + func (s Session) getSecretData(name string) map[string][]byte { secret := corev1.Secret{} if err := s.Get(s.Context, client.ObjectKey{ diff --git a/formol b/formol index 8975f77..ea1c1bd 160000 --- a/formol +++ b/formol @@ -1 +1 @@ -Subproject commit 8975f77e5858ee167508ef0359c3b9d6cbaba6ee +Subproject commit ea1c1bd2e31cc6f67621ed71659e738ca5f5d8c8 diff --git a/standalone/root.go b/standalone/root.go index bf6bf21..f8599ff 100644 --- a/standalone/root.go +++ b/standalone/root.go @@ -4,10 +4,12 @@ import ( "context" formolv1alpha1 "github.com/desmo999r/formol/api/v1alpha1" "github.com/desmo999r/formolcli/controllers" + volumesnapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" "k8s.io/client-go/tools/clientcmd" @@ -44,8 +46,9 @@ func init() { } } session.Scheme = runtime.NewScheme() - _ = formolv1alpha1.AddToScheme(session.Scheme) - _ = clientgoscheme.AddToScheme(session.Scheme) + utilruntime.Must(formolv1alpha1.AddToScheme(session.Scheme)) + utilruntime.Must(volumesnapshotv1.AddToScheme(session.Scheme)) + utilruntime.Must(clientgoscheme.AddToScheme(session.Scheme)) session.Client, err = client.New(config, client.Options{Scheme: session.Scheme}) if err != nil { log.Error(err, "unable to get client") @@ -53,6 +56,66 @@ func init() { } } +func BackupPaths( + backupSessionName string, + backupSessionNamespace string, + targetName string, + paths ...string) error { + log := session.Log.WithName("BackupPaths") + backupResult, err := session.BackupPaths(paths) + log.V(0).Info("Backup Job is over", "target", targetName, "snapshotID", backupResult.SnapshotId, "duration", backupResult.Duration) + if err != nil { + log.Error(err, "unable to backup paths", "paths", paths) + return err + } + backupSession := formolv1alpha1.BackupSession{} + if err := session.Get(session.Context, client.ObjectKey{ + Name: backupSessionName, + Namespace: backupSessionNamespace, + }, &backupSession); err != nil { + log.Error(err, "unable to get backupsession", "name", backupSessionName, "namespace", backupSessionNamespace) + return err + } + for i, target := range backupSession.Status.Targets { + if target.TargetName == targetName { + backupSession.Status.Targets[i].SessionState = formolv1alpha1.Success + backupSession.Status.Targets[i].SnapshotId = backupResult.SnapshotId + backupSession.Status.Targets[i].Duration = &metav1.Duration{Duration: time.Now().Sub(backupSession.Status.Targets[i].StartTime.Time)} + if err := session.Status().Update(session.Context, &backupSession); err != nil { + log.Error(err, "unable to update backupSession status") + return err + } + } + } + // Now find the PVC, VolumeSnapshots with the right label backupsession + // and delete them + vss := volumesnapshotv1.VolumeSnapshotList{} + if err := session.List(session.Context, &vss, client.InNamespace(backupSessionNamespace), client.MatchingLabels{"backupsession": backupSessionName}); err != nil { + log.Error(err, "unable to list the volumesnapshots", "backupsession", backupSessionName) + return err + } + for _, vs := range vss.Items { + if err := session.Delete(session.Context, &vs); err != nil { + log.Error(err, "unable to delete volumesnapshot", "vs", vs.Name) + return err + } + log.V(0).Info("volumesnapshot deleted", "vs", vs.Name) + } + pvcs := corev1.PersistentVolumeClaimList{} + if err := session.List(session.Context, &pvcs, client.InNamespace(backupSessionNamespace), client.MatchingLabels{"backupsession": backupSessionName}); err != nil { + log.Error(err, "unable to list the PVCs", "backupsession", backupSessionName) + return err + } + for _, pvc := range pvcs.Items { + if err := session.Delete(session.Context, &pvc); err != nil { + log.Error(err, "unable to delete PVC", "pvc", pvc.Name) + return err + } + log.V(0).Info("PVC deleted", "pvc", pvc.Name) + } + return nil +} + func StartRestore( restoreSessionName string, restoreSessionNamespace string, From 162e82b531c60deb57c86ca566819660d09bb0cb Mon Sep 17 00:00:00 2001 From: Jean-Marc Andre Date: Tue, 18 Apr 2023 17:21:23 +0200 Subject: [PATCH 34/37] makefile --- Dockerfile | 3 ++- Makefile | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 868bc0e..010abfa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,9 @@ # Build a small image -FROM golang:alpine3.17 AS builder +FROM --platform=$BUILDPLATFORM golang:alpine3.17 AS builder WORKDIR /go/src COPY . . +ARG TARGETOS TARGETARCH RUN GO111MODULE=on CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o bin/formolcli main.go FROM alpine:3.17 diff --git a/Makefile b/Makefile index 700240b..2a5b0a3 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ GOARCH ?= amd64 GOOS ?= linux VERSION ?= latest IMG ?= docker.io/desmo999r/formolcli:$(VERSION) -MANIFEST = formol-multiarch +MANIFEST = formolcli-multiarch BINDIR = ./bin .PHONY: formolcli @@ -33,5 +33,9 @@ docker-build-arm64: docker-build docker-push: buildah manifest push --all --rm $(MANIFEST) "docker://$(IMG)" +.PHONY: docker-build-multiarch +docker-build-multiarch: + buildah bud --manifest $(MANIFEST) --platform linux/amd64,linux/arm64/v8 . + .PHONY: all all: formolcli docker-build From 89b91f66da98fdfb8b28b9c68a68cee215f89ff4 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Wed, 19 Apr 2023 13:27:34 +0200 Subject: [PATCH 35/37] multi arch Dockerfile and Makefile --- Dockerfile | 21 +++++++++++++++------ Dockerfile.amd64 | 8 -------- Dockerfile.arm64 | 8 -------- Makefile | 15 ++++----------- 4 files changed, 19 insertions(+), 33 deletions(-) delete mode 100644 Dockerfile.amd64 delete mode 100644 Dockerfile.arm64 diff --git a/Dockerfile b/Dockerfile index 010abfa..de1ce64 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,22 @@ # Build a small image -FROM --platform=$BUILDPLATFORM golang:alpine3.17 AS builder +FROM --platform=${BUILDPLATFORM} golang:alpine3 AS builder +ARG TARGETOS +ARG TARGETARCH +ARG TARGETPLATFORM WORKDIR /go/src -COPY . . -ARG TARGETOS TARGETARCH -RUN GO111MODULE=on CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o bin/formolcli main.go +COPY go.mod go.mod +COPY go.sum go.sum +COPY formol/ formol/ +RUN go mod download +COPY main.go main.go +COPY cmd/ cmd/ +COPY standalone/ standalone/ +COPY controllers/ controllers/ +RUN GO111MODULE=on CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -o bin/formolcli main.go -FROM alpine:3.17 -RUN apk add --no-cache su-exec restic postgresql-client +FROM --platform=${TARGETPLATFORM} alpine:3 +RUN apk add --no-cache su-exec restic COPY --from=builder /go/src/bin/formolcli /usr/local/bin # Command to run diff --git a/Dockerfile.amd64 b/Dockerfile.amd64 deleted file mode 100644 index 7f0d6c6..0000000 --- a/Dockerfile.amd64 +++ /dev/null @@ -1,8 +0,0 @@ -# Build a small image -FROM --platform=linux/amd64 alpine:3 -RUN apk add --no-cache su-exec restic -COPY ./bin/formolcli /usr/local/bin - -# Command to run -ENTRYPOINT ["/usr/local/bin/formolcli"] -CMD ["--help"] diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 deleted file mode 100644 index 5e39629..0000000 --- a/Dockerfile.arm64 +++ /dev/null @@ -1,8 +0,0 @@ -# Build a small image -FROM --platform=linux/arm64 alpine:3 -RUN apk add --no-cache su-exec restic -COPY ./bin/formolcli /usr/local/bin - -# Command to run -ENTRYPOINT ["/usr/local/bin/formolcli"] -CMD ["--help"] diff --git a/Makefile b/Makefile index 2a5b0a3..6fbfadb 100644 --- a/Makefile +++ b/Makefile @@ -4,15 +4,12 @@ VERSION ?= latest IMG ?= docker.io/desmo999r/formolcli:$(VERSION) MANIFEST = formolcli-multiarch BINDIR = ./bin +PLATFORMS ?= linux/arm64,linux/amd64 .PHONY: formolcli formolcli: fmt vet GO111MODULE=on CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -o $(BINDIR)/formolcli main.go -#.PHONY: formolcli-arm64 -#formolcli-arm64: GOARCH = arm64 -#formolcli-arm64: formolcli - .PHONY: fmt fmt: go fmt ./... @@ -21,13 +18,9 @@ fmt: vet: go vet ./... -.PHONY: docker-build -docker-build: formolcli - buildah bud --tag $(IMG) --manifest $(MANIFEST) --arch $(GOARCH) Dockerfile.$(GOARCH) - -.PHONY: docker-build-arm64 -docker-build-arm64: GOARCH = arm64 -docker-build-arm64: docker-build +.PHONY: docker-build-multiarch +docker-build-multiarch: + buildah bud --manifest $(MANIFEST) --platform=$(PLATFORMS) . .PHONY: docker-push docker-push: From adf2743b191afe65e94daf908de6fe0886a39789 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Thu, 20 Apr 2023 08:33:27 +0200 Subject: [PATCH 36/37] dead code --- standalone/root.go | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/standalone/root.go b/standalone/root.go index f8599ff..e4cd051 100644 --- a/standalone/root.go +++ b/standalone/root.go @@ -8,7 +8,6 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/types" utilruntime "k8s.io/apimachinery/pkg/util/runtime" clientgoscheme "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" @@ -183,15 +182,6 @@ func StartRestore( func CreateBackupSession(ref corev1.ObjectReference) { log := session.Log.WithName("CreateBackupSession") log.V(0).Info("CreateBackupSession called") - backupConf := formolv1alpha1.BackupConfiguration{} - if err := session.Get(session.Context, types.NamespacedName{ - Namespace: ref.Namespace, - Name: ref.Name, - }, &backupConf); err != nil { - log.Error(err, "unable to get backupconf") - os.Exit(1) - } - log.V(0).Info("got backupConf", "backupConf", backupConf) backupSession := &formolv1alpha1.BackupSession{ ObjectMeta: metav1.ObjectMeta{ From 1aa9d9efb51f08f70272e38143c2555711d24804 Mon Sep 17 00:00:00 2001 From: Jean-Marc ANDRE Date: Thu, 20 Apr 2023 08:34:04 +0200 Subject: [PATCH 37/37] Makefile/Dockerfile cleanup --- Dockerfile | 2 +- Makefile | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index de1ce64..314c5bc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,5 @@ # Build a small image -FROM --platform=${BUILDPLATFORM} golang:alpine3 AS builder +FROM --platform=${BUILDPLATFORM} golang:alpine3.17 AS builder ARG TARGETOS ARG TARGETARCH ARG TARGETPLATFORM diff --git a/Makefile b/Makefile index 6fbfadb..f1364a9 100644 --- a/Makefile +++ b/Makefile @@ -20,15 +20,11 @@ vet: .PHONY: docker-build-multiarch docker-build-multiarch: - buildah bud --manifest $(MANIFEST) --platform=$(PLATFORMS) . + buildah bud --manifest $(MANIFEST) --platform=$(PLATFORMS) --layers . .PHONY: docker-push docker-push: buildah manifest push --all --rm $(MANIFEST) "docker://$(IMG)" -.PHONY: docker-build-multiarch -docker-build-multiarch: - buildah bud --manifest $(MANIFEST) --platform linux/amd64,linux/arm64/v8 . - .PHONY: all all: formolcli docker-build