add restic integration for doing pod volume backups/restores

Signed-off-by: Steve Kriss <steve@heptio.com>
This commit is contained in:
Steve Kriss
2018-06-06 09:48:10 -07:00
parent c2c5b9040c
commit 50d4084fac
86 changed files with 5421 additions and 485 deletions
+32 -3
View File
@@ -21,10 +21,11 @@ import (
"github.com/pkg/errors"
"k8s.io/api/core/v1"
corev1api "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
corev1listers "k8s.io/client-go/listers/core/v1"
)
// NamespaceAndName returns a string in the format <namespace>/<name>
@@ -39,7 +40,7 @@ func NamespaceAndName(objMeta metav1.Object) string {
// a bool indicating whether or not the namespace was created, and an error if the create failed
// for a reason other than that the namespace already exists. Note that in the case where the
// namespace already exists, this function will return (false, nil).
func EnsureNamespaceExists(namespace *v1.Namespace, client corev1.NamespaceInterface) (bool, error) {
func EnsureNamespaceExists(namespace *corev1api.Namespace, client corev1client.NamespaceInterface) (bool, error) {
if _, err := client.Create(namespace); err == nil {
return true, nil
} else if apierrors.IsAlreadyExists(err) {
@@ -48,3 +49,31 @@ func EnsureNamespaceExists(namespace *v1.Namespace, client corev1.NamespaceInter
return false, errors.Wrapf(err, "error creating namespace %s", namespace.Name)
}
}
// GetVolumeDirectory gets the name of the directory on the host, under /var/lib/kubelet/pods/<podUID>/volumes/,
// where the specified volume lives.
func GetVolumeDirectory(pod *corev1api.Pod, volumeName string, pvcLister corev1listers.PersistentVolumeClaimLister) (string, error) {
var volume *corev1api.Volume
for _, item := range pod.Spec.Volumes {
if item.Name == volumeName {
volume = &item
break
}
}
if volume == nil {
return "", errors.New("volume not found in pod")
}
if volume.VolumeSource.PersistentVolumeClaim == nil {
return volume.Name, nil
}
pvc, err := pvcLister.PersistentVolumeClaims(pod.Namespace).Get(volume.VolumeSource.PersistentVolumeClaim.ClaimName)
if err != nil {
return "", errors.WithStack(err)
}
return pvc.Spec.VolumeName, nil
}
+27
View File
@@ -0,0 +1,27 @@
package logging
import (
"github.com/sirupsen/logrus"
)
// DefaultHooks returns a slice of the default
// logrus hooks to be used by a logger.
func DefaultHooks() []logrus.Hook {
return []logrus.Hook{
&LogLocationHook{},
&ErrorLocationHook{},
}
}
// DefaultLogger returns a Logger with the default properties
// and hooks.
func DefaultLogger(level logrus.Level) *logrus.Logger {
logger := logrus.New()
logger.Level = level
for _, hook := range DefaultHooks() {
logger.Hooks.Add(hook)
}
return logger
}
+60
View File
@@ -0,0 +1,60 @@
package logging
import (
"sort"
"strings"
"github.com/sirupsen/logrus"
"github.com/heptio/ark/pkg/cmd/util/flag"
)
var sortedLogLevels = sortLogLevels()
// LevelFlag is a command-line flag for setting the logrus
// log level.
type LevelFlag struct {
*flag.Enum
defaultValue logrus.Level
}
// LogLevelFlag constructs a new log level flag.
func LogLevelFlag(defaultValue logrus.Level) *LevelFlag {
return &LevelFlag{
Enum: flag.NewEnum(defaultValue.String(), sortedLogLevels...),
defaultValue: defaultValue,
}
}
// Parse returns the flag's value as a logrus.Level.
func (f *LevelFlag) Parse() logrus.Level {
if parsed, err := logrus.ParseLevel(f.String()); err == nil {
return parsed
}
// This should theoretically never happen assuming the enum flag
// is constructed correctly because the enum flag will not allow
// an invalid value to be set.
logrus.Errorf("log-level flag has invalid value %s", strings.ToUpper(f.String()))
return f.defaultValue
}
// sortLogLevels returns a string slice containing all of the valid logrus
// log levels (based on logrus.AllLevels), sorted in ascending order of severity.
func sortLogLevels() []string {
var (
sortedLogLevels = make([]logrus.Level, len(logrus.AllLevels))
logLevelsStrings []string
)
copy(sortedLogLevels, logrus.AllLevels)
// logrus.Panic has the lowest value, so the compare function uses ">"
sort.Slice(sortedLogLevels, func(i, j int) bool { return sortedLogLevels[i] > sortedLogLevels[j] })
for _, level := range sortedLogLevels {
logLevelsStrings = append(logLevelsStrings, level.String())
}
return logLevelsStrings
}
+71
View File
@@ -0,0 +1,71 @@
/*
Copyright 2018 the Heptio Ark contributors.
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 sync
import "sync"
// An ErrorGroup waits for a collection of goroutines that return errors to finish.
// The main goroutine calls Go one or more times to execute a function that returns
// an error in a goroutine. Then it calls Wait to wait for all goroutines to finish
// and collect the results of each.
type ErrorGroup struct {
wg sync.WaitGroup
errChan chan error
}
// Go runs the specified function in a goroutine.
func (eg *ErrorGroup) Go(action func() error) {
if eg.errChan == nil {
eg.errChan = make(chan error)
}
eg.wg.Add(1)
go func() {
eg.errChan <- action()
eg.wg.Done()
}()
}
// GoErrorSlice runs a function that returns a slice of errors
// in a goroutine.
func (eg *ErrorGroup) GoErrorSlice(action func() []error) {
if eg.errChan == nil {
eg.errChan = make(chan error)
}
eg.wg.Add(1)
go func() {
for _, err := range action() {
eg.errChan <- err
}
eg.wg.Done()
}()
}
// Wait waits for all functions run via Go to finish,
// and returns all of their errors.
func (eg *ErrorGroup) Wait() []error {
var errs []error
go func() {
for {
errs = append(errs, <-eg.errChan)
}
}()
eg.wg.Wait()
return errs
}
+21
View File
@@ -0,0 +1,21 @@
package test
import (
"encoding/json"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func UnstructuredOrDie(data string) *unstructured.Unstructured {
o, _, err := unstructured.UnstructuredJSONScheme.Decode([]byte(data), nil, nil)
if err != nil {
panic(err)
}
return o.(*unstructured.Unstructured)
}
func GetAsMap(j string) (map[string]interface{}, error) {
m := make(map[string]interface{})
err := json.Unmarshal([]byte(j), &m)
return m, err
}
@@ -0,0 +1,17 @@
package test
import (
"github.com/sirupsen/logrus"
"github.com/stretchr/testify/mock"
"github.com/heptio/ark/pkg/apis/ark/v1"
)
type MockPodCommandExecutor struct {
mock.Mock
}
func (e *MockPodCommandExecutor) ExecutePodCommand(log logrus.FieldLogger, item map[string]interface{}, namespace, name, hookName string, hook *v1.ExecHook) error {
args := e.Called(log, item, namespace, name, hookName, hook)
return args.Error(0)
}