2018-03-14 18:17:27 +00:00
/ *
2019-03-20 19:32:48 +00:00
Copyright 2018 the Velero contributors .
2018-03-14 18:17:27 +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 controller
import (
2018-06-22 19:07:23 +00:00
"context"
2018-03-14 18:17:27 +00:00
"encoding/json"
2018-04-04 15:27:33 +00:00
"time"
2018-03-14 18:17:27 +00:00
jsonpatch "github.com/evanphx/json-patch"
2018-10-23 14:36:11 +00:00
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/clock"
kubeerrs "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/tools/cache"
2019-01-25 03:33:07 +00:00
v1 "github.com/heptio/velero/pkg/apis/velero/v1"
pkgbackup "github.com/heptio/velero/pkg/backup"
velerov1client "github.com/heptio/velero/pkg/generated/clientset/versioned/typed/velero/v1"
informers "github.com/heptio/velero/pkg/generated/informers/externalversions/velero/v1"
listers "github.com/heptio/velero/pkg/generated/listers/velero/v1"
"github.com/heptio/velero/pkg/persistence"
2019-03-15 18:32:11 +00:00
"github.com/heptio/velero/pkg/plugin/clientmgmt"
2019-03-14 20:35:06 +00:00
"github.com/heptio/velero/pkg/plugin/velero"
2019-01-25 03:33:07 +00:00
"github.com/heptio/velero/pkg/restic"
"github.com/heptio/velero/pkg/util/kube"
2018-03-14 18:17:27 +00:00
)
2018-06-22 19:07:23 +00:00
const resticTimeout = time . Minute
2018-03-14 18:17:27 +00:00
type backupDeletionController struct {
* genericController
2019-01-25 03:33:07 +00:00
deleteBackupRequestClient velerov1client . DeleteBackupRequestsGetter
2018-03-14 18:17:27 +00:00
deleteBackupRequestLister listers . DeleteBackupRequestLister
2019-01-25 03:33:07 +00:00
backupClient velerov1client . BackupsGetter
2018-03-14 18:17:27 +00:00
restoreLister listers . RestoreLister
2019-01-25 03:33:07 +00:00
restoreClient velerov1client . RestoresGetter
2018-04-06 17:08:39 +00:00
backupTracker BackupTracker
2018-02-28 01:35:35 +00:00
resticMgr restic . RepositoryManager
podvolumeBackupLister listers . PodVolumeBackupLister
2018-08-15 23:27:27 +00:00
backupLocationLister listers . BackupStorageLocationLister
2018-10-16 21:49:37 +00:00
snapshotLocationLister listers . VolumeSnapshotLocationLister
2018-08-15 23:27:27 +00:00
processRequestFunc func ( * v1 . DeleteBackupRequest ) error
clock clock . Clock
2019-03-15 18:32:11 +00:00
newPluginManager func ( logrus . FieldLogger ) clientmgmt . Manager
2018-08-20 23:29:54 +00:00
newBackupStore func ( * v1 . BackupStorageLocation , persistence . ObjectStoreGetter , logrus . FieldLogger ) ( persistence . BackupStore , error )
2018-03-14 18:17:27 +00:00
}
// NewBackupDeletionController creates a new backup deletion controller.
func NewBackupDeletionController (
logger logrus . FieldLogger ,
deleteBackupRequestInformer informers . DeleteBackupRequestInformer ,
2019-01-25 03:33:07 +00:00
deleteBackupRequestClient velerov1client . DeleteBackupRequestsGetter ,
backupClient velerov1client . BackupsGetter ,
2018-03-14 18:17:27 +00:00
restoreInformer informers . RestoreInformer ,
2019-01-25 03:33:07 +00:00
restoreClient velerov1client . RestoresGetter ,
2018-04-06 17:08:39 +00:00
backupTracker BackupTracker ,
2018-02-28 01:35:35 +00:00
resticMgr restic . RepositoryManager ,
podvolumeBackupInformer informers . PodVolumeBackupInformer ,
2018-08-15 23:27:27 +00:00
backupLocationInformer informers . BackupStorageLocationInformer ,
2018-10-16 21:49:37 +00:00
snapshotLocationInformer informers . VolumeSnapshotLocationInformer ,
2019-03-15 18:32:11 +00:00
newPluginManager func ( logrus . FieldLogger ) clientmgmt . Manager ,
2018-03-14 18:17:27 +00:00
) Interface {
c := & backupDeletionController {
genericController : newGenericController ( "backup-deletion" , logger ) ,
deleteBackupRequestClient : deleteBackupRequestClient ,
deleteBackupRequestLister : deleteBackupRequestInformer . Lister ( ) ,
backupClient : backupClient ,
restoreLister : restoreInformer . Lister ( ) ,
restoreClient : restoreClient ,
2018-04-06 17:08:39 +00:00
backupTracker : backupTracker ,
2018-02-28 01:35:35 +00:00
resticMgr : resticMgr ,
2018-08-15 23:27:27 +00:00
podvolumeBackupLister : podvolumeBackupInformer . Lister ( ) ,
backupLocationLister : backupLocationInformer . Lister ( ) ,
2018-10-16 21:49:37 +00:00
snapshotLocationLister : snapshotLocationInformer . Lister ( ) ,
2018-08-15 23:27:27 +00:00
// use variables to refer to these functions so they can be
// replaced with fakes for testing.
2018-08-25 19:53:56 +00:00
newPluginManager : newPluginManager ,
2018-08-20 23:29:54 +00:00
newBackupStore : persistence . NewObjectBackupStore ,
2018-05-13 13:28:09 +00:00
2018-08-15 23:27:27 +00:00
clock : & clock . RealClock { } ,
2018-03-14 18:17:27 +00:00
}
c . syncHandler = c . processQueueItem
2018-02-28 01:35:35 +00:00
c . cacheSyncWaiters = append (
c . cacheSyncWaiters ,
deleteBackupRequestInformer . Informer ( ) . HasSynced ,
restoreInformer . Informer ( ) . HasSynced ,
podvolumeBackupInformer . Informer ( ) . HasSynced ,
2018-08-15 23:27:27 +00:00
backupLocationInformer . Informer ( ) . HasSynced ,
2018-10-16 21:49:37 +00:00
snapshotLocationInformer . Informer ( ) . HasSynced ,
2018-02-28 01:35:35 +00:00
)
2018-03-14 18:17:27 +00:00
c . processRequestFunc = c . processRequest
deleteBackupRequestInformer . Informer ( ) . AddEventHandler (
cache . ResourceEventHandlerFuncs {
2018-07-19 23:06:12 +00:00
AddFunc : c . enqueue ,
2018-03-14 18:17:27 +00:00
} ,
)
2018-04-04 15:27:33 +00:00
c . resyncPeriod = time . Hour
c . resyncFunc = c . deleteExpiredRequests
2018-03-14 18:17:27 +00:00
return c
}
func ( c * backupDeletionController ) processQueueItem ( key string ) error {
log := c . logger . WithField ( "key" , key )
log . Debug ( "Running processItem" )
ns , name , err := cache . SplitMetaNamespaceKey ( key )
if err != nil {
return errors . Wrap ( err , "error splitting queue key" )
}
req , err := c . deleteBackupRequestLister . DeleteBackupRequests ( ns ) . Get ( name )
if apierrors . IsNotFound ( err ) {
log . Debug ( "Unable to find DeleteBackupRequest" )
return nil
}
if err != nil {
return errors . Wrap ( err , "error getting DeleteBackupRequest" )
}
switch req . Status . Phase {
case v1 . DeleteBackupRequestPhaseProcessed :
// Don't do anything because it's already been processed
default :
// Don't mutate the shared cache
reqCopy := req . DeepCopy ( )
return c . processRequestFunc ( reqCopy )
}
return nil
}
func ( c * backupDeletionController ) processRequest ( req * v1 . DeleteBackupRequest ) error {
log := c . logger . WithFields ( logrus . Fields {
"namespace" : req . Namespace ,
"name" : req . Name ,
"backup" : req . Spec . BackupName ,
} )
2018-04-06 17:08:39 +00:00
var err error
2018-04-05 19:38:44 +00:00
// Make sure we have the backup name
2018-04-05 18:53:10 +00:00
if req . Spec . BackupName == "" {
2018-04-06 17:08:39 +00:00
_ , err = c . patchDeleteBackupRequest ( req , func ( r * v1 . DeleteBackupRequest ) {
2018-04-05 18:53:10 +00:00
r . Status . Phase = v1 . DeleteBackupRequestPhaseProcessed
r . Status . Errors = [ ] string { "spec.backupName is required" }
} )
return err
}
2018-07-19 23:06:12 +00:00
// Remove any existing deletion requests for this backup so we only have
// one at a time
if errs := c . deleteExistingDeletionRequests ( req , log ) ; errs != nil {
return kubeerrs . NewAggregate ( errs )
}
2018-04-06 17:08:39 +00:00
// Don't allow deleting an in-progress backup
if c . backupTracker . Contains ( req . Namespace , req . Spec . BackupName ) {
_ , err = c . patchDeleteBackupRequest ( req , func ( r * v1 . DeleteBackupRequest ) {
r . Status . Phase = v1 . DeleteBackupRequestPhaseProcessed
r . Status . Errors = [ ] string { "backup is still in progress" }
} )
return err
}
2018-03-14 18:17:27 +00:00
2018-04-05 19:38:44 +00:00
// Update status to InProgress and set backup-name label if needed
2018-03-14 18:17:27 +00:00
req , err = c . patchDeleteBackupRequest ( req , func ( r * v1 . DeleteBackupRequest ) {
r . Status . Phase = v1 . DeleteBackupRequestPhaseInProgress
2018-04-05 19:38:44 +00:00
if req . Labels [ v1 . BackupNameLabel ] == "" {
req . Labels [ v1 . BackupNameLabel ] = req . Spec . BackupName
}
2018-03-14 18:17:27 +00:00
} )
if err != nil {
return err
}
// Get the backup we're trying to delete
backup , err := c . backupClient . Backups ( req . Namespace ) . Get ( req . Spec . BackupName , metav1 . GetOptions { } )
if apierrors . IsNotFound ( err ) {
// Couldn't find backup - update status to Processed and record the not-found error
req , err = c . patchDeleteBackupRequest ( req , func ( r * v1 . DeleteBackupRequest ) {
r . Status . Phase = v1 . DeleteBackupRequestPhaseProcessed
r . Status . Errors = [ ] string { "backup not found" }
} )
return err
}
if err != nil {
return errors . Wrap ( err , "error getting Backup" )
}
2018-04-05 19:38:44 +00:00
// Set backup-uid label if needed
if req . Labels [ v1 . BackupUIDLabel ] == "" {
req , err = c . patchDeleteBackupRequest ( req , func ( r * v1 . DeleteBackupRequest ) {
req . Labels [ v1 . BackupUIDLabel ] = string ( backup . UID )
} )
if err != nil {
return err
}
}
2018-03-14 18:17:27 +00:00
// Set backup status to Deleting
backup , err = c . patchBackup ( backup , func ( b * v1 . Backup ) {
b . Status . Phase = v1 . BackupPhaseDeleting
} )
if err != nil {
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error setting backup phase to deleting" )
2018-05-17 15:37:08 +00:00
return err
2018-03-14 18:17:27 +00:00
}
var errs [ ] string
2018-10-16 21:49:37 +00:00
pluginManager := c . newPluginManager ( log )
defer pluginManager . CleanupClients ( )
backupStore , backupStoreErr := c . backupStoreForBackup ( backup , pluginManager , log )
if backupStoreErr != nil {
errs = append ( errs , backupStoreErr . Error ( ) )
}
2018-10-16 21:54:05 +00:00
if backupStore != nil {
log . Info ( "Removing PV snapshots" )
2018-10-19 18:54:24 +00:00
if len ( backup . Status . VolumeBackups ) > 0 {
// pre-v0.10 backup
locations , err := c . snapshotLocationLister . VolumeSnapshotLocations ( backup . Namespace ) . List ( labels . Everything ( ) )
if err != nil {
errs = append ( errs , errors . Wrap ( err , "error listing volume snapshot locations" ) . Error ( ) )
} else if len ( locations ) != 1 {
errs = append ( errs , errors . Errorf ( "unable to delete pre-v0.10 volume snapshots because exactly one volume snapshot location must exist, got %d" , len ( locations ) ) . Error ( ) )
} else {
blockStore , err := blockStoreForSnapshotLocation ( backup . Namespace , locations [ 0 ] . Name , c . snapshotLocationLister , pluginManager )
if err != nil {
errs = append ( errs , err . Error ( ) )
} else {
for _ , snapshot := range backup . Status . VolumeBackups {
if err := blockStore . DeleteSnapshot ( snapshot . SnapshotID ) ; err != nil {
errs = append ( errs , errors . Wrapf ( err , "error deleting snapshot %s" , snapshot . SnapshotID ) . Error ( ) )
}
2018-10-16 21:54:05 +00:00
}
2018-10-16 21:49:37 +00:00
}
2018-10-19 18:54:24 +00:00
}
} else {
// v0.10+ backup
if snapshots , err := backupStore . GetBackupVolumeSnapshots ( backup . Name ) ; err != nil {
errs = append ( errs , errors . Wrap ( err , "error getting backup's volume snapshots" ) . Error ( ) )
} else {
2019-03-14 20:35:06 +00:00
blockStores := make ( map [ string ] velero . BlockStore )
2018-10-19 18:54:24 +00:00
for _ , snapshot := range snapshots {
log . WithField ( "providerSnapshotID" , snapshot . Status . ProviderSnapshotID ) . Info ( "Removing snapshot associated with backup" )
blockStore , ok := blockStores [ snapshot . Spec . Location ]
if ! ok {
if blockStore , err = blockStoreForSnapshotLocation ( backup . Namespace , snapshot . Spec . Location , c . snapshotLocationLister , pluginManager ) ; err != nil {
errs = append ( errs , err . Error ( ) )
continue
}
blockStores [ snapshot . Spec . Location ] = blockStore
}
2018-10-16 21:49:37 +00:00
2018-10-19 18:54:24 +00:00
if err := blockStore . DeleteSnapshot ( snapshot . Status . ProviderSnapshotID ) ; err != nil {
errs = append ( errs , errors . Wrapf ( err , "error deleting snapshot %s" , snapshot . Status . ProviderSnapshotID ) . Error ( ) )
}
2018-10-16 21:54:05 +00:00
}
2018-10-16 21:49:37 +00:00
}
2018-03-14 18:17:27 +00:00
}
}
2018-02-28 01:35:35 +00:00
log . Info ( "Removing restic snapshots" )
2018-06-22 19:07:23 +00:00
if deleteErrs := c . deleteResticSnapshots ( backup ) ; len ( deleteErrs ) > 0 {
for _ , err := range deleteErrs {
errs = append ( errs , err . Error ( ) )
2018-02-28 01:35:35 +00:00
}
}
2018-10-16 21:54:05 +00:00
if backupStore != nil {
log . Info ( "Removing backup from backup storage" )
if err := backupStore . DeleteBackup ( backup . Name ) ; err != nil {
errs = append ( errs , err . Error ( ) )
}
2018-03-14 18:17:27 +00:00
}
log . Info ( "Removing restores" )
if restores , err := c . restoreLister . Restores ( backup . Namespace ) . List ( labels . Everything ( ) ) ; err != nil {
log . WithError ( errors . WithStack ( err ) ) . Error ( "Error listing restore API objects" )
} else {
for _ , restore := range restores {
if restore . Spec . BackupName != backup . Name {
continue
}
restoreLog := log . WithField ( "restore" , kube . NamespaceAndName ( restore ) )
2018-09-18 15:56:45 +00:00
restoreLog . Info ( "Deleting restore log/results from backup storage" )
if err := backupStore . DeleteRestore ( restore . Name ) ; err != nil {
errs = append ( errs , err . Error ( ) )
// if we couldn't delete the restore files, don't delete the API object
continue
}
2018-03-14 18:17:27 +00:00
restoreLog . Info ( "Deleting restore referencing backup" )
if err := c . restoreClient . Restores ( restore . Namespace ) . Delete ( restore . Name , & metav1 . DeleteOptions { } ) ; err != nil {
errs = append ( errs , errors . Wrapf ( err , "error deleting restore %s" , kube . NamespaceAndName ( restore ) ) . Error ( ) )
}
}
}
if len ( errs ) == 0 {
// Only try to delete the backup object from kube if everything preceding went smoothly
err = c . backupClient . Backups ( backup . Namespace ) . Delete ( backup . Name , nil )
if err != nil {
errs = append ( errs , errors . Wrapf ( err , "error deleting backup %s" , kube . NamespaceAndName ( backup ) ) . Error ( ) )
}
}
// Update status to processed and record errors
req , err = c . patchDeleteBackupRequest ( req , func ( r * v1 . DeleteBackupRequest ) {
r . Status . Phase = v1 . DeleteBackupRequestPhaseProcessed
r . Status . Errors = errs
} )
if err != nil {
return err
}
// Everything deleted correctly, so we can delete all DeleteBackupRequests for this backup
if len ( errs ) == 0 {
2018-04-05 18:21:45 +00:00
listOptions := pkgbackup . NewDeleteBackupRequestListOptions ( backup . Name , string ( backup . UID ) )
2018-03-14 18:17:27 +00:00
err = c . deleteBackupRequestClient . DeleteBackupRequests ( req . Namespace ) . DeleteCollection ( nil , listOptions )
if err != nil {
// If this errors, all we can do is log it.
c . logger . WithField ( "backup" , kube . NamespaceAndName ( backup ) ) . Error ( "error deleting all associated DeleteBackupRequests after successfully deleting the backup" )
}
}
return nil
}
2018-10-16 21:49:37 +00:00
func blockStoreForSnapshotLocation (
namespace , snapshotLocationName string ,
snapshotLocationLister listers . VolumeSnapshotLocationLister ,
2019-03-15 18:32:11 +00:00
pluginManager clientmgmt . Manager ,
2019-03-14 20:35:06 +00:00
) ( velero . BlockStore , error ) {
2018-10-16 21:49:37 +00:00
snapshotLocation , err := snapshotLocationLister . VolumeSnapshotLocations ( namespace ) . Get ( snapshotLocationName )
if err != nil {
return nil , errors . Wrapf ( err , "error getting volume snapshot location %s" , snapshotLocationName )
}
blockStore , err := pluginManager . GetBlockStore ( snapshotLocation . Spec . Provider )
if err != nil {
return nil , errors . Wrapf ( err , "error getting block store for provider %s" , snapshotLocation . Spec . Provider )
}
if err = blockStore . Init ( snapshotLocation . Spec . Config ) ; err != nil {
return nil , errors . Wrapf ( err , "error initializing block store for volume snapshot location %s" , snapshotLocationName )
}
return blockStore , nil
}
2019-03-15 18:32:11 +00:00
func ( c * backupDeletionController ) backupStoreForBackup ( backup * v1 . Backup , pluginManager clientmgmt . Manager , log logrus . FieldLogger ) ( persistence . BackupStore , error ) {
2018-08-15 23:27:27 +00:00
backupLocation , err := c . backupLocationLister . BackupStorageLocations ( backup . Namespace ) . Get ( backup . Spec . StorageLocation )
if err != nil {
2018-09-18 15:56:45 +00:00
return nil , errors . WithStack ( err )
2018-08-15 23:27:27 +00:00
}
2018-08-20 23:29:54 +00:00
backupStore , err := c . newBackupStore ( backupLocation , pluginManager , log )
2018-08-15 23:27:27 +00:00
if err != nil {
2018-09-18 15:56:45 +00:00
return nil , err
2018-08-15 23:27:27 +00:00
}
2018-09-18 15:56:45 +00:00
return backupStore , nil
2018-08-15 23:27:27 +00:00
}
2018-07-19 23:06:12 +00:00
func ( c * backupDeletionController ) deleteExistingDeletionRequests ( req * v1 . DeleteBackupRequest , log logrus . FieldLogger ) [ ] error {
log . Info ( "Removing existing deletion requests for backup" )
selector := labels . SelectorFromSet ( labels . Set ( map [ string ] string {
v1 . BackupNameLabel : req . Spec . BackupName ,
} ) )
dbrs , err := c . deleteBackupRequestLister . DeleteBackupRequests ( req . Namespace ) . List ( selector )
if err != nil {
return [ ] error { errors . Wrap ( err , "error listing existing DeleteBackupRequests for backup" ) }
}
var errs [ ] error
for _ , dbr := range dbrs {
if dbr . Name == req . Name {
continue
}
if err := c . deleteBackupRequestClient . DeleteBackupRequests ( req . Namespace ) . Delete ( dbr . Name , nil ) ; err != nil {
errs = append ( errs , errors . WithStack ( err ) )
}
}
return errs
}
2018-06-22 19:07:23 +00:00
func ( c * backupDeletionController ) deleteResticSnapshots ( backup * v1 . Backup ) [ ] error {
if c . resticMgr == nil {
return nil
}
snapshots , err := restic . GetSnapshotsInBackup ( backup , c . podvolumeBackupLister )
if err != nil {
return [ ] error { err }
}
ctx , cancelFunc := context . WithTimeout ( context . Background ( ) , resticTimeout )
defer cancelFunc ( )
var errs [ ] error
for _ , snapshot := range snapshots {
if err := c . resticMgr . Forget ( ctx , snapshot ) ; err != nil {
errs = append ( errs , err )
}
}
return errs
}
2018-04-04 15:27:33 +00:00
const deleteBackupRequestMaxAge = 24 * time . Hour
func ( c * backupDeletionController ) deleteExpiredRequests ( ) {
c . logger . Info ( "Checking for expired DeleteBackupRequests" )
defer c . logger . Info ( "Done checking for expired DeleteBackupRequests" )
// Our shared informer factory filters on a single namespace, so asking for all is ok here.
requests , err := c . deleteBackupRequestLister . List ( labels . Everything ( ) )
if err != nil {
c . logger . WithError ( err ) . Error ( "unable to check for expired DeleteBackupRequests" )
return
}
now := c . clock . Now ( )
for _ , req := range requests {
if req . Status . Phase != v1 . DeleteBackupRequestPhaseProcessed {
continue
}
age := now . Sub ( req . CreationTimestamp . Time )
if age >= deleteBackupRequestMaxAge {
reqLog := c . logger . WithFields ( logrus . Fields { "namespace" : req . Namespace , "name" : req . Name } )
reqLog . Info ( "Deleting expired DeleteBackupRequest" )
err = c . deleteBackupRequestClient . DeleteBackupRequests ( req . Namespace ) . Delete ( req . Name , nil )
if err != nil {
reqLog . WithError ( err ) . Error ( "Error deleting DeleteBackupRequest" )
}
}
}
}
2018-03-14 18:17:27 +00:00
func ( c * backupDeletionController ) patchDeleteBackupRequest ( req * v1 . DeleteBackupRequest , mutate func ( * v1 . DeleteBackupRequest ) ) ( * v1 . DeleteBackupRequest , error ) {
// Record original json
oldData , err := json . Marshal ( req )
if err != nil {
return nil , errors . Wrap ( err , "error marshalling original DeleteBackupRequest" )
}
// Mutate
mutate ( req )
// Record new json
newData , err := json . Marshal ( req )
if err != nil {
return nil , errors . Wrap ( err , "error marshalling updated DeleteBackupRequest" )
}
patchBytes , err := jsonpatch . CreateMergePatch ( oldData , newData )
if err != nil {
return nil , errors . Wrap ( err , "error creating json merge patch for DeleteBackupRequest" )
}
req , err = c . deleteBackupRequestClient . DeleteBackupRequests ( req . Namespace ) . Patch ( req . Name , types . MergePatchType , patchBytes )
if err != nil {
return nil , errors . Wrap ( err , "error patching DeleteBackupRequest" )
}
return req , nil
}
func ( c * backupDeletionController ) patchBackup ( backup * v1 . Backup , mutate func ( * v1 . Backup ) ) ( * v1 . Backup , error ) {
// Record original json
oldData , err := json . Marshal ( backup )
if err != nil {
return nil , errors . Wrap ( err , "error marshalling original Backup" )
}
// Mutate
mutate ( backup )
// Record new json
newData , err := json . Marshal ( backup )
if err != nil {
return nil , errors . Wrap ( err , "error marshalling updated Backup" )
}
patchBytes , err := jsonpatch . CreateMergePatch ( oldData , newData )
if err != nil {
return nil , errors . Wrap ( err , "error creating json merge patch for Backup" )
}
backup , err = c . backupClient . Backups ( backup . Namespace ) . Patch ( backup . Name , types . MergePatchType , patchBytes )
if err != nil {
return nil , errors . Wrap ( err , "error patching Backup" )
}
return backup , nil
}