Initial commit
This commit is contained in:
parent
84347314fd
commit
28658aef39
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
*~
|
||||
bin/
|
||||
go.sum
|
||||
bin
|
||||
|
||||
@ -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"]
|
||||
@ -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"]
|
||||
202
LICENSE
202
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.
|
||||
23
Makefile
23
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
|
||||
|
||||
82
backupsession/create.go
Normal file
82
backupsession/create.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
40
cmd/backupsession.go
Normal file
40
cmd/backupsession.go
Normal file
@ -0,0 +1,40 @@
|
||||
/*
|
||||
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
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")
|
||||
}
|
||||
46
cmd/root.go
Normal file
46
cmd/root.go
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
Copyright © 2023 NAME HERE <EMAIL ADDRESS>
|
||||
*/
|
||||
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")
|
||||
}
|
||||
82
go.mod
82
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
|
||||
|
||||
16
main.go
16
main.go
@ -1,21 +1,9 @@
|
||||
/*
|
||||
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
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 <EMAIL ADDRESS>
|
||||
*/
|
||||
package main
|
||||
|
||||
import "github.com/desmo999r/formolcli/pkg/cmd"
|
||||
import "github.com/desmo999r/formolcli/cmd"
|
||||
|
||||
func main() {
|
||||
cmd.Execute()
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -1,67 +0,0 @@
|
||||
/*
|
||||
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
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")
|
||||
}
|
||||
@ -1,106 +0,0 @@
|
||||
/*
|
||||
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
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")
|
||||
}
|
||||
@ -1,91 +0,0 @@
|
||||
/*
|
||||
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
/* Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
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")
|
||||
}
|
||||
@ -1,44 +0,0 @@
|
||||
/*
|
||||
Copyright © 2021 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
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)
|
||||
}
|
||||
@ -1,70 +0,0 @@
|
||||
/*
|
||||
Copyright © 2020 NAME HERE <EMAIL ADDRESS>
|
||||
|
||||
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")
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user