2018-02-28 01:35:35 +00:00
|
|
|
/*
|
2019-03-20 19:32:48 +00:00
|
|
|
Copyright 2018 the Velero contributors.
|
2018-02-28 01:35:35 +00:00
|
|
|
|
|
|
|
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"
|
2018-09-25 21:46:29 +00:00
|
|
|
"os"
|
2018-02-28 01:35:35 +00:00
|
|
|
"strings"
|
2018-06-06 21:32:28 +00:00
|
|
|
"time"
|
2018-02-28 01:35:35 +00:00
|
|
|
|
|
|
|
"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"
|
|
|
|
|
2019-09-30 21:26:56 +00:00
|
|
|
velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
|
|
|
|
velerov1listers "github.com/vmware-tanzu/velero/pkg/generated/listers/velero/v1"
|
|
|
|
"github.com/vmware-tanzu/velero/pkg/label"
|
|
|
|
"github.com/vmware-tanzu/velero/pkg/util/filesystem"
|
2018-02-28 01:35:35 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
2019-08-06 20:40:35 +00:00
|
|
|
// DaemonSet is the name of the Velero restic daemonset.
|
|
|
|
DaemonSet = "restic"
|
|
|
|
|
|
|
|
// InitContainer is the name of the init container added
|
|
|
|
// to workload pods to help with restores.
|
|
|
|
InitContainer = "restic-wait"
|
|
|
|
|
|
|
|
// DefaultMaintenanceFrequency is the default time interval
|
2019-09-10 18:58:42 +00:00
|
|
|
// at which restic prune is run.
|
|
|
|
DefaultMaintenanceFrequency = 7 * 24 * time.Hour
|
2018-02-28 01:35:35 +00:00
|
|
|
|
2019-08-06 20:40:35 +00:00
|
|
|
// PVCNameAnnotation is the key for the annotation added to
|
|
|
|
// pod volume backups when they're for a PVC.
|
|
|
|
PVCNameAnnotation = "velero.io/pvc-name"
|
|
|
|
|
2019-07-24 19:51:20 +00:00
|
|
|
// Deprecated.
|
2019-08-06 20:40:35 +00:00
|
|
|
//
|
|
|
|
// TODO(2.0): remove
|
2019-07-24 19:51:20 +00:00
|
|
|
podAnnotationPrefix = "snapshot.velero.io/"
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
volumesToBackupAnnotation = "backup.velero.io/backup-volumes"
|
2018-02-28 01:35:35 +00:00
|
|
|
)
|
|
|
|
|
2019-08-06 20:17:36 +00:00
|
|
|
// getPodSnapshotAnnotations returns a map, of volume name -> snapshot id,
|
2018-02-28 01:35:35 +00:00
|
|
|
// of all restic snapshots for this pod.
|
2019-08-06 20:17:36 +00:00
|
|
|
// TODO(2.0) to remove
|
|
|
|
// Deprecated: we will stop using pod annotations to record restic snapshot IDs after they're taken,
|
|
|
|
// therefore we won't need to check if these annotations exist.
|
|
|
|
func getPodSnapshotAnnotations(obj metav1.Object) map[string]string {
|
2018-02-28 01:35:35 +00:00
|
|
|
var res map[string]string
|
|
|
|
|
2019-01-25 03:33:07 +00:00
|
|
|
insertSafe := func(k, v string) {
|
|
|
|
if res == nil {
|
|
|
|
res = make(map[string]string)
|
|
|
|
}
|
|
|
|
res[k] = v
|
|
|
|
}
|
|
|
|
|
2018-02-28 01:35:35 +00:00
|
|
|
for k, v := range obj.GetAnnotations() {
|
|
|
|
if strings.HasPrefix(k, podAnnotationPrefix) {
|
2019-01-25 03:33:07 +00:00
|
|
|
insertSafe(k[len(podAnnotationPrefix):], v)
|
|
|
|
}
|
2018-02-28 01:35:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return res
|
|
|
|
}
|
|
|
|
|
2019-08-06 20:17:36 +00:00
|
|
|
// GetVolumeBackupsForPod returns a map, of volume name -> snapshot id,
|
|
|
|
// of the PodVolumeBackups that exist for the provided pod.
|
|
|
|
func GetVolumeBackupsForPod(podVolumeBackups []*velerov1api.PodVolumeBackup, pod metav1.Object) map[string]string {
|
|
|
|
volumes := make(map[string]string)
|
|
|
|
|
|
|
|
for _, pvb := range podVolumeBackups {
|
2019-11-04 23:18:08 +00:00
|
|
|
if pod.GetName() != pvb.Spec.Pod.Name {
|
|
|
|
continue
|
2019-08-06 20:17:36 +00:00
|
|
|
}
|
2019-11-04 23:18:08 +00:00
|
|
|
|
|
|
|
// skip PVBs without a snapshot ID since there's nothing
|
|
|
|
// to restore (they could be failed, or for empty volumes).
|
|
|
|
if pvb.Status.SnapshotID == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
volumes[pvb.Spec.Volume] = pvb.Status.SnapshotID
|
2019-08-06 20:17:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if len(volumes) > 0 {
|
|
|
|
return volumes
|
|
|
|
}
|
|
|
|
|
|
|
|
return getPodSnapshotAnnotations(pod)
|
|
|
|
}
|
|
|
|
|
2018-02-28 01:35:35 +00:00
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
2019-03-27 23:07:53 +00:00
|
|
|
backupsValue := annotations[volumesToBackupAnnotation]
|
2018-02-28 01:35:35 +00:00
|
|
|
if backupsValue == "" {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Split(backupsValue, ",")
|
|
|
|
}
|
|
|
|
|
|
|
|
// SnapshotIdentifier uniquely identifies a restic snapshot
|
2019-01-25 03:33:07 +00:00
|
|
|
// taken by Velero.
|
2018-02-28 01:35:35 +00:00
|
|
|
type SnapshotIdentifier struct {
|
2018-09-25 20:20:58 +00:00
|
|
|
// VolumeNamespace is the namespace of the pod/volume that
|
|
|
|
// the restic snapshot is for.
|
|
|
|
VolumeNamespace string
|
2018-02-28 01:35:35 +00:00
|
|
|
|
2018-09-25 20:20:58 +00:00
|
|
|
// BackupStorageLocation is the backup's storage location
|
|
|
|
// name.
|
|
|
|
BackupStorageLocation string
|
|
|
|
|
|
|
|
// SnapshotID is the short ID of the restic snapshot.
|
2018-02-28 01:35:35 +00:00
|
|
|
SnapshotID string
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetSnapshotsInBackup returns a list of all restic snapshot ids associated with
|
2019-01-25 03:33:07 +00:00
|
|
|
// a given Velero backup.
|
|
|
|
func GetSnapshotsInBackup(backup *velerov1api.Backup, podVolumeBackupLister velerov1listers.PodVolumeBackupLister) ([]SnapshotIdentifier, error) {
|
2018-09-19 18:51:30 +00:00
|
|
|
selector := labels.Set(map[string]string{
|
2019-04-23 23:58:59 +00:00
|
|
|
velerov1api.BackupNameLabel: label.GetValidName(backup.Name),
|
2018-09-19 18:51:30 +00:00
|
|
|
}).AsSelector()
|
2018-02-28 01:35:35 +00:00
|
|
|
|
|
|
|
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{
|
2018-09-25 20:20:58 +00:00
|
|
|
VolumeNamespace: item.Spec.Pod.Namespace,
|
|
|
|
BackupStorageLocation: backup.Spec.StorageLocation,
|
|
|
|
SnapshotID: item.Status.SnapshotID,
|
2018-02-28 01:35:35 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
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.
|
2019-01-25 03:33:07 +00:00
|
|
|
func TempCredentialsFile(secretLister corev1listers.SecretLister, veleroNamespace, repoName string, fs filesystem.Interface) (string, error) {
|
2018-02-28 01:35:35 +00:00
|
|
|
secretGetter := NewListerSecretGetter(secretLister)
|
2018-06-22 19:07:23 +00:00
|
|
|
|
|
|
|
// 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
|
2019-01-25 03:33:07 +00:00
|
|
|
// (all within the Velero server's namespace) so GetRepositoryKey will need to take the repo
|
2018-06-22 19:07:23 +00:00
|
|
|
// name as an argument as well.
|
2019-01-25 03:33:07 +00:00
|
|
|
repoKey, err := GetRepositoryKey(secretGetter, veleroNamespace)
|
2018-02-28 01:35:35 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2018-06-18 17:54:07 +00:00
|
|
|
file, err := fs.TempFile("", fmt.Sprintf("%s-%s", CredentialsSecretName, repoName))
|
2018-02-28 01:35:35 +00:00
|
|
|
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
|
|
|
|
}
|
2018-06-13 23:40:18 +00:00
|
|
|
|
|
|
|
// NewPodVolumeBackupListOptions creates a ListOptions with a label selector configured to
|
2019-01-25 03:33:07 +00:00
|
|
|
// find PodVolumeBackups for the backup identified by name.
|
|
|
|
func NewPodVolumeBackupListOptions(name string) metav1.ListOptions {
|
2018-06-13 23:40:18 +00:00
|
|
|
return metav1.ListOptions{
|
2019-04-23 23:58:59 +00:00
|
|
|
LabelSelector: fmt.Sprintf("%s=%s", velerov1api.BackupNameLabel, label.GetValidName(name)),
|
2018-06-13 23:40:18 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewPodVolumeRestoreListOptions creates a ListOptions with a label selector configured to
|
2019-01-25 03:33:07 +00:00
|
|
|
// find PodVolumeRestores for the restore identified by name.
|
|
|
|
func NewPodVolumeRestoreListOptions(name string) metav1.ListOptions {
|
2018-06-13 23:40:18 +00:00
|
|
|
return metav1.ListOptions{
|
2019-04-23 23:58:59 +00:00
|
|
|
LabelSelector: fmt.Sprintf("%s=%s", velerov1api.RestoreNameLabel, label.GetValidName(name)),
|
2018-06-13 23:40:18 +00:00
|
|
|
}
|
|
|
|
}
|
2018-09-25 21:46:29 +00:00
|
|
|
|
|
|
|
// AzureCmdEnv returns a list of environment variables (in the format var=val) that
|
|
|
|
// should be used when running a restic command for an Azure backend. This list is
|
|
|
|
// the current environment, plus the Azure-specific variables restic needs, namely
|
|
|
|
// a storage account name and key.
|
2019-01-25 03:33:07 +00:00
|
|
|
func AzureCmdEnv(backupLocationLister velerov1listers.BackupStorageLocationLister, namespace, backupLocation string) ([]string, error) {
|
2018-09-25 21:46:29 +00:00
|
|
|
loc, err := backupLocationLister.BackupStorageLocations(namespace).Get(backupLocation)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "error getting backup storage location")
|
|
|
|
}
|
|
|
|
|
2019-10-22 22:31:27 +00:00
|
|
|
azureVars, err := getResticEnvVars(loc.Spec.Config)
|
2018-09-25 21:46:29 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "error getting azure restic env vars")
|
|
|
|
}
|
|
|
|
|
|
|
|
env := os.Environ()
|
|
|
|
for k, v := range azureVars {
|
|
|
|
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
|
|
|
}
|
|
|
|
|
|
|
|
return env, nil
|
|
|
|
}
|