179 lines
5.0 KiB
Go
179 lines
5.0 KiB
Go
/*
|
|
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 restic
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/labels"
|
|
corev1listers "k8s.io/client-go/listers/core/v1"
|
|
|
|
arkv1api "github.com/heptio/ark/pkg/apis/ark/v1"
|
|
arkv1listers "github.com/heptio/ark/pkg/generated/listers/ark/v1"
|
|
)
|
|
|
|
const (
|
|
DaemonSet = "restic"
|
|
InitContainer = "restic-wait"
|
|
DefaultMaintenanceFrequency = 24 * time.Hour
|
|
|
|
podAnnotationPrefix = "snapshot.ark.heptio.com/"
|
|
volumesToBackupAnnotation = "backup.ark.heptio.com/backup-volumes"
|
|
)
|
|
|
|
// PodHasSnapshotAnnotation returns true if the object has an annotation
|
|
// indicating that there is a restic snapshot for a volume in this pod,
|
|
// or false otherwise.
|
|
func PodHasSnapshotAnnotation(obj metav1.Object) bool {
|
|
for key := range obj.GetAnnotations() {
|
|
if strings.HasPrefix(key, podAnnotationPrefix) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetPodSnapshotAnnotations returns a map, of volume name -> snapshot id,
|
|
// of all restic snapshots for this pod.
|
|
func GetPodSnapshotAnnotations(obj metav1.Object) map[string]string {
|
|
var res map[string]string
|
|
|
|
for k, v := range obj.GetAnnotations() {
|
|
if strings.HasPrefix(k, podAnnotationPrefix) {
|
|
if res == nil {
|
|
res = make(map[string]string)
|
|
}
|
|
|
|
res[k[len(podAnnotationPrefix):]] = v
|
|
}
|
|
}
|
|
|
|
return res
|
|
}
|
|
|
|
// SetPodSnapshotAnnotation adds an annotation to a pod to indicate that
|
|
// the specified volume has a restic snapshot with the provided id.
|
|
func SetPodSnapshotAnnotation(obj metav1.Object, volumeName, snapshotID string) {
|
|
annotations := obj.GetAnnotations()
|
|
|
|
if annotations == nil {
|
|
annotations = make(map[string]string)
|
|
}
|
|
|
|
annotations[podAnnotationPrefix+volumeName] = snapshotID
|
|
|
|
obj.SetAnnotations(annotations)
|
|
}
|
|
|
|
// GetVolumesToBackup returns a list of volume names to backup for
|
|
// the provided pod.
|
|
func GetVolumesToBackup(obj metav1.Object) []string {
|
|
annotations := obj.GetAnnotations()
|
|
if annotations == nil {
|
|
return nil
|
|
}
|
|
|
|
backupsValue := annotations[volumesToBackupAnnotation]
|
|
if backupsValue == "" {
|
|
return nil
|
|
}
|
|
|
|
return strings.Split(backupsValue, ",")
|
|
}
|
|
|
|
// SnapshotIdentifier uniquely identifies a restic snapshot
|
|
// taken by Ark.
|
|
type SnapshotIdentifier struct {
|
|
// Repo is the name of the restic repository where the
|
|
// snapshot is located
|
|
Repo string
|
|
|
|
// SnapshotID is the short ID of the restic snapshot
|
|
SnapshotID string
|
|
}
|
|
|
|
// GetSnapshotsInBackup returns a list of all restic snapshot ids associated with
|
|
// a given Ark backup.
|
|
func GetSnapshotsInBackup(backup *arkv1api.Backup, podVolumeBackupLister arkv1listers.PodVolumeBackupLister) ([]SnapshotIdentifier, error) {
|
|
selector, err := labels.Parse(fmt.Sprintf("%s=%s", arkv1api.BackupNameLabel, backup.Name))
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
podVolumeBackups, err := podVolumeBackupLister.List(selector)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
|
|
var res []SnapshotIdentifier
|
|
for _, item := range podVolumeBackups {
|
|
if item.Status.SnapshotID == "" {
|
|
continue
|
|
}
|
|
res = append(res, SnapshotIdentifier{
|
|
Repo: item.Spec.Pod.Namespace,
|
|
SnapshotID: item.Status.SnapshotID,
|
|
})
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// TempCredentialsFile creates a temp file containing a restic
|
|
// encryption key for the given repo and returns its path. The
|
|
// caller should generally call os.Remove() to remove the file
|
|
// when done with it.
|
|
func TempCredentialsFile(secretLister corev1listers.SecretLister, arkNamespace, repoName string) (string, error) {
|
|
secretGetter := NewListerSecretGetter(secretLister)
|
|
|
|
// For now, all restic repos share the same key so we don't need the repoName to fetch it.
|
|
// When we move to full-backup encryption, we'll likely have a separate key per restic repo
|
|
// (all within the Ark server's namespace) so GetRepositoryKey will need to take the repo
|
|
// name as an argument as well.
|
|
repoKey, err := GetRepositoryKey(secretGetter, arkNamespace)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
file, err := ioutil.TempFile("", fmt.Sprintf("%s-%s", CredentialsSecretName, repoName))
|
|
if err != nil {
|
|
return "", errors.WithStack(err)
|
|
}
|
|
|
|
if _, err := file.Write(repoKey); err != nil {
|
|
// nothing we can do about an error closing the file here, and we're
|
|
// already returning an error about the write failing.
|
|
file.Close()
|
|
return "", errors.WithStack(err)
|
|
}
|
|
|
|
name := file.Name()
|
|
|
|
if err := file.Close(); err != nil {
|
|
return "", errors.WithStack(err)
|
|
}
|
|
|
|
return name, nil
|
|
}
|