2017-08-02 17:27:17 +00:00
/ *
2019-03-20 19:32:48 +00:00
Copyright 2017 the Velero contributors .
2017-08-02 17:27:17 +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 restore
import (
"fmt"
2019-10-24 21:20:57 +00:00
"sort"
2017-08-02 17:27:17 +00:00
"time"
2017-09-14 21:27:31 +00:00
"github.com/pkg/errors"
2017-08-02 17:27:17 +00:00
"github.com/spf13/cobra"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2018-08-01 00:20:03 +00:00
"k8s.io/client-go/tools/cache"
2017-08-02 17:27:17 +00:00
2019-09-30 21:26:56 +00:00
api "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
"github.com/vmware-tanzu/velero/pkg/client"
"github.com/vmware-tanzu/velero/pkg/cmd"
"github.com/vmware-tanzu/velero/pkg/cmd/util/flag"
"github.com/vmware-tanzu/velero/pkg/cmd/util/output"
veleroclient "github.com/vmware-tanzu/velero/pkg/generated/clientset/versioned"
v1 "github.com/vmware-tanzu/velero/pkg/generated/informers/externalversions/velero/v1"
2019-10-24 21:20:57 +00:00
"github.com/vmware-tanzu/velero/pkg/util/boolptr"
2017-08-02 17:27:17 +00:00
)
2017-09-12 21:36:57 +00:00
func NewCreateCommand ( f client . Factory , use string ) * cobra . Command {
2017-08-02 17:27:17 +00:00
o := NewCreateOptions ( )
c := & cobra . Command {
2018-04-20 18:02:59 +00:00
Use : use + " [RESTORE_NAME] [--from-backup BACKUP_NAME | --from-schedule SCHEDULE_NAME]" ,
2017-08-02 17:27:17 +00:00
Short : "Create a restore" ,
2018-03-01 00:29:25 +00:00
Example : ` # create a restore named "restore-1" from backup "backup-1"
2019-01-25 03:33:07 +00:00
velero restore create restore - 1 -- from - backup backup - 1
2018-03-01 00:29:25 +00:00
# create a restore with a default name ( "backup-1-<timestamp>" ) from backup "backup-1"
2019-01-25 03:33:07 +00:00
velero restore create -- from - backup backup - 1
2018-04-20 18:02:59 +00:00
# create a restore from the latest successful backup triggered by schedule "schedule-1"
2019-01-25 03:33:07 +00:00
velero restore create -- from - schedule schedule - 1
2019-04-12 15:21:00 +00:00
2019-10-24 21:20:57 +00:00
# create a restore from the latest successful OR partially - failed backup triggered by schedule "schedule-1"
velero restore create -- from - schedule schedule - 1 -- allow - partially - failed
2019-04-12 15:21:00 +00:00
# create a restore for only persistentvolumeclaims and persistentvolumes within a backup
velero restore create -- from - backup backup - 2 -- include - resources persistentvolumeclaims , persistentvolumes
2018-04-20 18:02:59 +00:00
` ,
2018-04-03 16:34:19 +00:00
Args : cobra . MaximumNArgs ( 1 ) ,
2017-08-02 17:27:17 +00:00
Run : func ( c * cobra . Command , args [ ] string ) {
2018-04-04 03:43:42 +00:00
cmd . CheckError ( o . Complete ( args , f ) )
2018-04-03 16:34:19 +00:00
cmd . CheckError ( o . Validate ( c , args , f ) )
2017-08-02 17:27:17 +00:00
cmd . CheckError ( o . Run ( c , f ) )
} ,
}
o . BindFlags ( c . Flags ( ) )
output . BindFlags ( c . Flags ( ) )
output . ClearOutputFlagDefault ( c )
return c
}
type CreateOptions struct {
2017-10-20 19:51:54 +00:00
BackupName string
2018-04-20 18:02:59 +00:00
ScheduleName string
2018-03-01 00:29:25 +00:00
RestoreName string
2017-10-20 19:51:54 +00:00
RestoreVolumes flag . OptionalBool
Labels flag . Map
IncludeNamespaces flag . StringArray
ExcludeNamespaces flag . StringArray
IncludeResources flag . StringArray
ExcludeResources flag . StringArray
NamespaceMappings flag . Map
Selector flag . LabelSelector
IncludeClusterResources flag . OptionalBool
2018-08-01 00:20:03 +00:00
Wait bool
2019-10-24 21:20:57 +00:00
AllowPartiallyFailed flag . OptionalBool
2018-02-05 18:29:48 +00:00
2019-01-25 03:33:07 +00:00
client veleroclient . Interface
2017-08-02 17:27:17 +00:00
}
func NewCreateOptions ( ) * CreateOptions {
return & CreateOptions {
2017-10-20 19:51:54 +00:00
Labels : flag . NewMap ( ) ,
IncludeNamespaces : flag . NewStringArray ( "*" ) ,
NamespaceMappings : flag . NewMap ( ) . WithEntryDelimiter ( "," ) . WithKeyValueDelimiter ( ":" ) ,
RestoreVolumes : flag . NewOptionalBool ( nil ) ,
IncludeClusterResources : flag . NewOptionalBool ( nil ) ,
2017-08-02 17:27:17 +00:00
}
}
func ( o * CreateOptions ) BindFlags ( flags * pflag . FlagSet ) {
2018-03-01 00:29:25 +00:00
flags . StringVar ( & o . BackupName , "from-backup" , "" , "backup to restore from" )
2018-04-20 18:02:59 +00:00
flags . StringVar ( & o . ScheduleName , "from-schedule" , "" , "schedule to restore from" )
2017-08-30 02:14:21 +00:00
flags . Var ( & o . IncludeNamespaces , "include-namespaces" , "namespaces to include in the restore (use '*' for all namespaces)" )
flags . Var ( & o . ExcludeNamespaces , "exclude-namespaces" , "namespaces to exclude from the restore" )
2017-08-02 17:27:17 +00:00
flags . Var ( & o . NamespaceMappings , "namespace-mappings" , "namespace mappings from name in the backup to desired restored name in the form src1:dst1,src2:dst2,..." )
2017-08-27 16:42:10 +00:00
flags . Var ( & o . Labels , "labels" , "labels to apply to the restore" )
2017-09-01 21:39:30 +00:00
flags . Var ( & o . IncludeResources , "include-resources" , "resources to include in the restore, formatted as resource.group, such as storageclasses.storage.k8s.io (use '*' for all resources)" )
flags . Var ( & o . ExcludeResources , "exclude-resources" , "resources to exclude from the restore, formatted as resource.group, such as storageclasses.storage.k8s.io" )
2017-08-02 17:27:17 +00:00
flags . VarP ( & o . Selector , "selector" , "l" , "only restore resources matching this label selector" )
2017-08-18 22:11:42 +00:00
f := flags . VarPF ( & o . RestoreVolumes , "restore-volumes" , "" , "whether to restore volumes from snapshots" )
// this allows the user to just specify "--restore-volumes" as shorthand for "--restore-volumes=true"
// like a normal bool flag
f . NoOptDefVal = "true"
2017-10-20 19:51:54 +00:00
f = flags . VarPF ( & o . IncludeClusterResources , "include-cluster-resources" , "" , "include cluster-scoped resources in the restore" )
f . NoOptDefVal = "true"
2018-08-01 00:20:03 +00:00
2019-10-24 21:20:57 +00:00
f = flags . VarPF ( & o . AllowPartiallyFailed , "allow-partially-failed" , "" , "if using --from-schedule, whether to consider PartiallyFailed backups when looking for the most recent one. This flag has no effect if not using --from-schedule." )
f . NoOptDefVal = "true"
2018-08-01 00:20:03 +00:00
flags . BoolVarP ( & o . Wait , "wait" , "w" , o . Wait , "wait for the operation to complete" )
2017-08-02 17:27:17 +00:00
}
2018-04-20 18:02:59 +00:00
func ( o * CreateOptions ) Complete ( args [ ] string , f client . Factory ) error {
if len ( args ) == 1 {
o . RestoreName = args [ 0 ]
} else {
sourceName := o . BackupName
if o . ScheduleName != "" {
sourceName = o . ScheduleName
}
2017-08-02 17:27:17 +00:00
2018-04-20 18:02:59 +00:00
o . RestoreName = fmt . Sprintf ( "%s-%s" , sourceName , time . Now ( ) . Format ( "20060102150405" ) )
2018-02-05 18:29:48 +00:00
}
2018-04-20 18:02:59 +00:00
client , err := f . Client ( )
if err != nil {
2018-02-05 18:29:48 +00:00
return err
}
2018-04-20 18:02:59 +00:00
o . client = client
2018-02-05 18:29:48 +00:00
2017-08-02 17:27:17 +00:00
return nil
}
2018-04-20 18:02:59 +00:00
func ( o * CreateOptions ) Validate ( c * cobra . Command , args [ ] string , f client . Factory ) error {
if o . BackupName != "" && o . ScheduleName != "" {
return errors . New ( "either a backup or schedule must be specified, but not both" )
2018-03-01 00:29:25 +00:00
}
2018-04-20 18:02:59 +00:00
if o . BackupName == "" && o . ScheduleName == "" {
return errors . New ( "either a backup or schedule must be specified, but not both" )
}
if err := output . ValidateFlags ( c ) ; err != nil {
2018-04-04 03:43:42 +00:00
return err
}
2018-04-20 18:02:59 +00:00
if o . client == nil {
// This should never happen
2019-01-25 03:33:07 +00:00
return errors . New ( "Velero client is not set; unable to proceed" )
2018-04-20 18:02:59 +00:00
}
2020-01-24 08:16:13 +00:00
if o . BackupName != "" {
2019-01-25 03:33:07 +00:00
if _ , err := o . client . VeleroV1 ( ) . Backups ( f . Namespace ( ) ) . Get ( o . BackupName , metav1 . GetOptions { } ) ; err != nil {
2018-04-20 18:02:59 +00:00
return err
}
}
2018-04-04 03:43:42 +00:00
2017-08-02 17:27:17 +00:00
return nil
}
2019-10-24 21:20:57 +00:00
// mostRecentBackup returns the backup with the most recent start timestamp that has a phase that's
// in the provided list of allowed phases.
func mostRecentBackup ( backups [ ] api . Backup , allowedPhases ... api . BackupPhase ) * api . Backup {
// sort the backups in descending order of start time (i.e. most recent to least recent)
sort . Slice ( backups , func ( i , j int ) bool {
// Use .After() because we want descending sort.
var iStartTime , jStartTime time . Time
if backups [ i ] . Status . StartTimestamp != nil {
iStartTime = backups [ i ] . Status . StartTimestamp . Time
}
if backups [ j ] . Status . StartTimestamp != nil {
jStartTime = backups [ j ] . Status . StartTimestamp . Time
}
return iStartTime . After ( jStartTime )
} )
// create a map of the allowed phases for easy lookup below
phases := map [ api . BackupPhase ] struct { } { }
for _ , phase := range allowedPhases {
phases [ phase ] = struct { } { }
}
var res * api . Backup
for i , backup := range backups {
// if the backup's phase is one of the allowable ones, record
// the backup and break the loop so we can return it
if _ , ok := phases [ backup . Status . Phase ] ; ok {
res = & backups [ i ]
break
}
}
return res
}
2017-08-02 17:27:17 +00:00
func ( o * CreateOptions ) Run ( c * cobra . Command , f client . Factory ) error {
2018-02-05 18:29:48 +00:00
if o . client == nil {
// This should never happen
2019-01-25 03:33:07 +00:00
return errors . New ( "Velero client is not set; unable to proceed" )
2017-08-02 17:27:17 +00:00
}
2019-10-24 21:20:57 +00:00
// if --allow-partially-failed was specified, look up the most recent Completed or
// PartiallyFailed backup for the provided schedule, and use that specific backup
// to restore from.
if o . ScheduleName != "" && boolptr . IsSetToTrue ( o . AllowPartiallyFailed . Value ) {
backups , err := o . client . VeleroV1 ( ) . Backups ( f . Namespace ( ) ) . List ( metav1 . ListOptions { LabelSelector : fmt . Sprintf ( "%s=%s" , api . ScheduleNameLabel , o . ScheduleName ) } )
if err != nil {
return err
}
// if we find a Completed or PartiallyFailed backup for the schedule, restore specifically from that backup. If we don't
// find one, proceed as-is -- the Velero server will handle validation.
if backup := mostRecentBackup ( backups . Items , api . BackupPhaseCompleted , api . BackupPhasePartiallyFailed ) ; backup != nil {
// TODO(sk): this is kind of a hack -- we should revisit this and probably
// move this logic to the server side or otherwise solve this problem.
o . BackupName = backup . Name
o . ScheduleName = ""
}
}
2017-08-02 17:27:17 +00:00
restore := & api . Restore {
ObjectMeta : metav1 . ObjectMeta {
2017-12-22 14:43:44 +00:00
Namespace : f . Namespace ( ) ,
2018-03-01 00:29:25 +00:00
Name : o . RestoreName ,
2017-08-02 17:27:17 +00:00
Labels : o . Labels . Data ( ) ,
} ,
Spec : api . RestoreSpec {
2017-10-20 19:51:54 +00:00
BackupName : o . BackupName ,
2018-04-20 18:02:59 +00:00
ScheduleName : o . ScheduleName ,
2017-10-20 19:51:54 +00:00
IncludedNamespaces : o . IncludeNamespaces ,
ExcludedNamespaces : o . ExcludeNamespaces ,
IncludedResources : o . IncludeResources ,
ExcludedResources : o . ExcludeResources ,
NamespaceMapping : o . NamespaceMappings . Data ( ) ,
LabelSelector : o . Selector . LabelSelector ,
RestorePVs : o . RestoreVolumes . Value ,
IncludeClusterResources : o . IncludeClusterResources . Value ,
2017-08-02 17:27:17 +00:00
} ,
}
if printed , err := output . PrintWithFormat ( c , restore ) ; printed || err != nil {
return err
}
2018-08-01 00:20:03 +00:00
var restoreInformer cache . SharedIndexInformer
var updates chan * api . Restore
if o . Wait {
stop := make ( chan struct { } )
defer close ( stop )
updates = make ( chan * api . Restore )
restoreInformer = v1 . NewRestoreInformer ( o . client , f . Namespace ( ) , 0 , nil )
restoreInformer . AddEventHandler (
cache . FilteringResourceEventHandler {
FilterFunc : func ( obj interface { } ) bool {
restore , ok := obj . ( * api . Restore )
if ! ok {
return false
}
return restore . Name == o . RestoreName
} ,
Handler : cache . ResourceEventHandlerFuncs {
UpdateFunc : func ( _ , obj interface { } ) {
restore , ok := obj . ( * api . Restore )
if ! ok {
return
}
updates <- restore
} ,
DeleteFunc : func ( obj interface { } ) {
restore , ok := obj . ( * api . Restore )
if ! ok {
return
}
updates <- restore
} ,
} ,
} ,
)
go restoreInformer . Run ( stop )
}
2019-01-25 03:33:07 +00:00
restore , err := o . client . VeleroV1 ( ) . Restores ( restore . Namespace ) . Create ( restore )
2017-08-02 17:27:17 +00:00
if err != nil {
return err
}
2018-01-04 16:51:22 +00:00
fmt . Printf ( "Restore request %q submitted successfully.\n" , restore . Name )
2018-08-01 00:20:03 +00:00
if o . Wait {
fmt . Println ( "Waiting for restore to complete. You may safely press ctrl-c to stop waiting - your restore will continue in the background." )
ticker := time . NewTicker ( time . Second )
defer ticker . Stop ( )
for {
select {
case <- ticker . C :
fmt . Print ( "." )
case restore , ok := <- updates :
if ! ok {
fmt . Println ( "\nError waiting: unable to watch restores." )
return nil
}
if restore . Status . Phase != api . RestorePhaseNew && restore . Status . Phase != api . RestorePhaseInProgress {
2019-01-25 03:33:07 +00:00
fmt . Printf ( "\nRestore completed with status: %s. You may check for more information using the commands `velero restore describe %s` and `velero restore logs %s`.\n" , restore . Status . Phase , restore . Name , restore . Name )
2018-08-01 00:20:03 +00:00
return nil
}
}
}
}
// Not waiting
2019-01-25 03:33:07 +00:00
fmt . Printf ( "Run `velero restore describe %s` or `velero restore logs %s` for more details.\n" , restore . Name , restore . Name )
2018-08-01 00:20:03 +00:00
2017-08-02 17:27:17 +00:00
return nil
}