diff --git a/Makefile b/Makefile index b56edbd02b..b77a3a25df 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ BIN ?= velero PKG := github.com/vmware-tanzu/velero # Where to push the docker image. -REGISTRY ?= velero +REGISTRY ?= catalogicsoftware GCR_REGISTRY ?= gcr.io/velero-gcp # Image name @@ -62,7 +62,7 @@ HUGO_IMAGE := hugo-builder local : ARCH ?= $(shell go env GOOS)-$(shell go env GOARCH) ARCH ?= linux-amd64 -VERSION ?= main +VERSION ?= v1.11.1.1 TAG_LATEST ?= false diff --git a/pkg/backup/backup.go b/pkg/backup/backup.go index 1ae35fbd16..739f987b20 100644 --- a/pkg/backup/backup.go +++ b/pkg/backup/backup.go @@ -34,6 +34,8 @@ import ( "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime/schema" kubeerrs "k8s.io/apimachinery/pkg/util/errors" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" "github.com/vmware-tanzu/velero/internal/hook" velerov1api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1" @@ -86,6 +88,17 @@ type kubernetesBackupper struct { uploaderType string } +type veleroConfig struct { + ExcludedPvcs map[string]bool +} + +const ( + CloudCasaNamespace = "cloudcasa-io" + + // VeleroConfigMapName is the name of the configmap used to store configuration parameters + VeleroConfigMapName = "cloudcasa-io-velero-config" +) + func (i *itemKey) String() string { return fmt.Sprintf("resource=%s,namespace=%s,name=%s", i.resource, i.namespace, i.name) } @@ -290,6 +303,11 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger, } backupRequest.Status.Progress = &velerov1api.BackupProgress{TotalItems: len(items)} + veleroConfig, err := GetVeleroConfig(log) + if err != nil { + return errors.Wrap(err, "Failed to get velero configuration") + } + itemBackupper := &itemBackupper{ backupRequest: backupRequest, tarWriter: tw, @@ -302,6 +320,7 @@ func (kb *kubernetesBackupper) BackupWithResolvers(log logrus.FieldLogger, itemHookHandler: &hook.DefaultItemHookHandler{ PodCommandExecutor: kb.podCommandExecutor, }, + excludedPvcs: veleroConfig.ExcludedPvcs, } // helper struct to send current progress between the main @@ -715,3 +734,50 @@ type tarWriter interface { Write([]byte) (int, error) WriteHeader(*tar.Header) error } + +// GetVeleroConfig reads the configmap that contains Velero config parameters +func GetVeleroConfig(log logrus.FieldLogger) (*veleroConfig, error) { + clientset, err := GetClientset(log) + if err != nil { + return nil, err + } + + configMap, err := clientset.CoreV1().ConfigMaps(CloudCasaNamespace).Get(context.TODO(), VeleroConfigMapName, metav1.GetOptions{}) + if err != nil { + if apierrors.IsNotFound(err) { + log.Infof("Velero configuration configmap %q not found", VeleroConfigMapName) + return &veleroConfig{}, nil + } + log.Error(errors.Wrapf(err, "Failed to get %q configmap in %q namespace", VeleroConfigMapName, CloudCasaNamespace)) + return nil, err + } + + var excludedPvcs map[string]bool + if excludedPvcsJson, found := configMap.BinaryData["excludedPvcs"]; found { + if err := json.Unmarshal(excludedPvcsJson, &excludedPvcs); err != nil { + log.Error(errors.Wrapf(err, "Failed to parse excludedPvcs value %q from %q", + string(excludedPvcsJson), VeleroConfigMapName)) + return nil, err + } + } + + return &veleroConfig{ + ExcludedPvcs: excludedPvcs, + }, nil +} + +func GetClientset(log logrus.FieldLogger) (*kubernetes.Clientset, error) { + // creates the in-cluster config + config, err := rest.InClusterConfig() + if err != nil { + log.Error(errors.Wrap(err, "Failed to create in-cluster config")) + return nil, err + } + // creates the clientset + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + log.Error(errors.Wrap(err, "Failed to create in-cluster clientset")) + return nil, err + } + return clientset, nil +} diff --git a/pkg/backup/item_backupper.go b/pkg/backup/item_backupper.go index 2e1378f0d1..ca72129b28 100644 --- a/pkg/backup/item_backupper.go +++ b/pkg/backup/item_backupper.go @@ -73,6 +73,8 @@ type itemBackupper struct { itemHookHandler hook.ItemHookHandler snapshotLocationVolumeSnapshotters map[string]vsv1.VolumeSnapshotter + + excludedPvcs map[string]bool } type FileForArchive struct { @@ -158,6 +160,30 @@ func (ib *itemBackupper) backupItemInternal(logger logrus.FieldLogger, obj runti return false, itemFiles, nil } + if ib.excludedPvcs != nil { + switch groupResource { + case kuberesource.PersistentVolumes: + pv := new(corev1api.PersistentVolume) + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(obj.UnstructuredContent(), pv); err != nil { + log.Errorf("Failed to convert unstructured PV %s to PersistentVolume structure: %v", metadata.GetName(), err) + return false, itemFiles, err + } + + pvcPath := pv.Spec.ClaimRef.Namespace + "/" + pv.Spec.ClaimRef.Name + if _, isExcluded := ib.excludedPvcs[pvcPath]; isExcluded { + log.Infof("Excluding PV %s for PVC %s because it is excluded from backup", metadata.GetName(), pvcPath) + return false, itemFiles, nil + } + + case kuberesource.PersistentVolumeClaims: + pvcPath := metadata.GetNamespace() + "/" + metadata.GetName() + if _, isExcluded := ib.excludedPvcs[pvcPath]; isExcluded { + log.Infof("Excluding PVC %s because it is excluded from backup", pvcPath) + return false, itemFiles, nil + } + } + } + key := itemKey{ resource: resourceKey(obj), namespace: namespace,