github.com/1aal/kubeblocks@v0.0.0-20231107070852-e1c03e598921/pkg/cli/cmd/cluster/dataprotection.go (about)

     1  /*
     2  Copyright (C) 2022-2023 ApeCloud Co., Ltd
     3  
     4  This file is part of KubeBlocks project
     5  
     6  This program is free software: you can redistribute it and/or modify
     7  it under the terms of the GNU Affero General Public License as published by
     8  the Free Software Foundation, either version 3 of the License, or
     9  (at your option) any later version.
    10  
    11  This program is distributed in the hope that it will be useful
    12  but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  GNU Affero General Public License for more details.
    15  
    16  You should have received a copy of the GNU Affero General Public License
    17  along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18  */
    19  
    20  package cluster
    21  
    22  import (
    23  	"bytes"
    24  	"context"
    25  	"fmt"
    26  	"reflect"
    27  	"sort"
    28  	"strconv"
    29  	"strings"
    30  	"time"
    31  
    32  	"github.com/pkg/errors"
    33  	"github.com/spf13/cobra"
    34  	"golang.org/x/exp/maps"
    35  	batchv1 "k8s.io/api/batch/v1"
    36  	corev1 "k8s.io/api/core/v1"
    37  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    38  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    39  	"k8s.io/apimachinery/pkg/runtime"
    40  	"k8s.io/apimachinery/pkg/runtime/schema"
    41  	"k8s.io/apimachinery/pkg/util/duration"
    42  	"k8s.io/apimachinery/pkg/util/json"
    43  	"k8s.io/cli-runtime/pkg/genericiooptions"
    44  	"k8s.io/client-go/dynamic"
    45  	clientset "k8s.io/client-go/kubernetes"
    46  	"k8s.io/client-go/kubernetes/scheme"
    47  	"k8s.io/client-go/util/jsonpath"
    48  	"k8s.io/kubectl/pkg/cmd/get"
    49  	cmdutil "k8s.io/kubectl/pkg/cmd/util"
    50  	"k8s.io/kubectl/pkg/cmd/util/editor"
    51  	"k8s.io/kubectl/pkg/util/templates"
    52  	"sigs.k8s.io/controller-runtime/pkg/client"
    53  
    54  	appsv1alpha1 "github.com/1aal/kubeblocks/apis/apps/v1alpha1"
    55  	dpv1alpha1 "github.com/1aal/kubeblocks/apis/dataprotection/v1alpha1"
    56  	"github.com/1aal/kubeblocks/pkg/cli/cluster"
    57  	"github.com/1aal/kubeblocks/pkg/cli/create"
    58  	"github.com/1aal/kubeblocks/pkg/cli/delete"
    59  	"github.com/1aal/kubeblocks/pkg/cli/list"
    60  	"github.com/1aal/kubeblocks/pkg/cli/printer"
    61  	"github.com/1aal/kubeblocks/pkg/cli/types"
    62  	"github.com/1aal/kubeblocks/pkg/cli/util"
    63  	"github.com/1aal/kubeblocks/pkg/constant"
    64  	dptypes "github.com/1aal/kubeblocks/pkg/dataprotection/types"
    65  )
    66  
    67  var (
    68  	listBackupPolicyExample = templates.Examples(`
    69  		# list all backup policies
    70  		kbcli cluster list-backup-policy
    71  
    72  		# using short cmd to list backup policy of the specified cluster
    73          kbcli cluster list-bp mycluster
    74  	`)
    75  	editExample = templates.Examples(`
    76  		# edit backup policy
    77  		kbcli cluster edit-backup-policy <backup-policy-name>
    78  
    79          # update backup Repo
    80  		kbcli cluster edit-backup-policy <backup-policy-name> --set backupRepoName=<backup-repo-name>
    81  
    82  	    # using short cmd to edit backup policy
    83          kbcli cluster edit-bp <backup-policy-name>
    84  	`)
    85  	createBackupExample = templates.Examples(`
    86  		# Create a backup for the cluster, use the default backup policy and volume snapshot backup method
    87  		kbcli cluster backup mycluster
    88  
    89  		# create a backup with a specified method, run "kbcli cluster desc-backup-policy mycluster" to show supported backup methods
    90  		kbcli cluster backup mycluster --method volume-snapshot
    91  
    92  		# create a backup with specified backup policy, run "kbcli cluster list-backup-policy mycluster" to show the cluster supported backup policies
    93  		kbcli cluster backup mycluster --method volume-snapshot --policy <backup-policy-name>
    94  
    95  		# create a backup from a parent backup
    96  		kbcli cluster backup mycluster --parent-backup parent-backup-name
    97  	`)
    98  	listBackupExample = templates.Examples(`
    99  		# list all backups
   100  		kbcli cluster list-backups
   101  	`)
   102  	deleteBackupExample = templates.Examples(`
   103  		# delete a backup named backup-name
   104  		kbcli cluster delete-backup cluster-name --name backup-name
   105  	`)
   106  	createRestoreExample = templates.Examples(`
   107  		# restore a new cluster from a backup
   108  		kbcli cluster restore new-cluster-name --backup backup-name
   109  	`)
   110  	describeBackupExample = templates.Examples(`
   111  		# describe a backup
   112  		kbcli cluster describe-backup backup-default-mycluster-20230616190023
   113  	`)
   114  	describeBackupPolicyExample = templates.Examples(`
   115  		# describe the default backup policy of the cluster
   116  		kbcli cluster describe-backup-policy cluster-name
   117  
   118  		# describe the backup policy of the cluster with specified name
   119  		kbcli cluster describe-backup-policy cluster-name --name backup-policy-name
   120  	`)
   121  )
   122  
   123  const annotationTrueValue = "true"
   124  
   125  type CreateBackupOptions struct {
   126  	BackupMethod     string `json:"backupMethod"`
   127  	BackupName       string `json:"backupName"`
   128  	BackupPolicy     string `json:"backupPolicy"`
   129  	DeletionPolicy   string `json:"deletionPolicy"`
   130  	RetentionPeriod  string `json:"retentionPeriod"`
   131  	ParentBackupName string `json:"parentBackupName"`
   132  
   133  	create.CreateOptions `json:"-"`
   134  }
   135  
   136  type ListBackupOptions struct {
   137  	*list.ListOptions
   138  	BackupName string
   139  }
   140  
   141  type DescribeBackupOptions struct {
   142  	Factory   cmdutil.Factory
   143  	client    clientset.Interface
   144  	dynamic   dynamic.Interface
   145  	namespace string
   146  
   147  	// resource type and names
   148  	Gvr   schema.GroupVersionResource
   149  	names []string
   150  
   151  	genericiooptions.IOStreams
   152  }
   153  
   154  func (o *CreateBackupOptions) CompleteBackup() error {
   155  	if err := o.Complete(); err != nil {
   156  		return err
   157  	}
   158  	// generate backupName
   159  	if len(o.BackupName) == 0 {
   160  		o.BackupName = strings.Join([]string{"backup", o.Namespace, o.Name, time.Now().Format("20060102150405")}, "-")
   161  	}
   162  
   163  	return o.CreateOptions.Complete()
   164  }
   165  
   166  func (o *CreateBackupOptions) Validate() error {
   167  	if o.Name == "" {
   168  		return fmt.Errorf("missing cluster name")
   169  	}
   170  
   171  	// if backup policy is not specified, use the default backup policy
   172  	if o.BackupPolicy == "" {
   173  		if err := o.completeDefaultBackupPolicy(); err != nil {
   174  			return err
   175  		}
   176  	}
   177  
   178  	// check if backup policy exists
   179  	backupPolicyObj, err := o.Dynamic.Resource(types.BackupPolicyGVR()).Namespace(o.Namespace).Get(context.TODO(), o.BackupPolicy, metav1.GetOptions{})
   180  	if err != nil {
   181  		return err
   182  	}
   183  	backupPolicy := &dpv1alpha1.BackupPolicy{}
   184  	if err := runtime.DefaultUnstructuredConverter.FromUnstructured(backupPolicyObj.Object, backupPolicy); err != nil {
   185  		return err
   186  	}
   187  
   188  	if o.BackupMethod == "" {
   189  		return fmt.Errorf("backup method can not be empty, you can specify it by --method")
   190  	}
   191  	// TODO: check if pvc exists
   192  
   193  	// valid retention period
   194  	if o.RetentionPeriod != "" {
   195  		_, err := dpv1alpha1.RetentionPeriod(o.RetentionPeriod).ToDuration()
   196  		if err != nil {
   197  			return fmt.Errorf("invalid retention period, please refer to examples [1y, 1m, 1d, 1h, 1m] or combine them [1y1m1d1h1m]")
   198  		}
   199  	}
   200  
   201  	// check if parent backup exists
   202  	if o.ParentBackupName != "" {
   203  		parentBackupObj, err := o.Dynamic.Resource(types.BackupGVR()).Namespace(o.Namespace).Get(context.TODO(), o.ParentBackupName, metav1.GetOptions{})
   204  		if err != nil {
   205  			return err
   206  		}
   207  		parentBackup := &dpv1alpha1.Backup{}
   208  		if err := runtime.DefaultUnstructuredConverter.FromUnstructured(parentBackupObj.Object, parentBackup); err != nil {
   209  			return err
   210  		}
   211  		if parentBackup.Status.Phase != dpv1alpha1.BackupPhaseCompleted {
   212  			return fmt.Errorf("parent backup %s is not completed", o.ParentBackupName)
   213  		}
   214  		if parentBackup.Labels[constant.AppInstanceLabelKey] != o.Name {
   215  			return fmt.Errorf("parent backup %s is not belong to cluster %s", o.ParentBackupName, o.Name)
   216  		}
   217  	}
   218  	return nil
   219  }
   220  
   221  // completeDefaultBackupPolicy completes the default backup policy.
   222  func (o *CreateBackupOptions) completeDefaultBackupPolicy() error {
   223  	defaultBackupPolicyName, err := o.getDefaultBackupPolicy()
   224  	if err != nil {
   225  		return err
   226  	}
   227  	o.BackupPolicy = defaultBackupPolicyName
   228  	return nil
   229  }
   230  
   231  func (o *CreateBackupOptions) getDefaultBackupPolicy() (string, error) {
   232  	clusterObj, err := o.Dynamic.Resource(types.ClusterGVR()).Namespace(o.Namespace).Get(context.TODO(), o.Name, metav1.GetOptions{})
   233  	if err != nil {
   234  		return "", err
   235  	}
   236  
   237  	// TODO: support multiple components backup, add --componentDef flag
   238  	opts := metav1.ListOptions{
   239  		LabelSelector: fmt.Sprintf("%s=%s",
   240  			constant.AppInstanceLabelKey, clusterObj.GetName()),
   241  	}
   242  	objs, err := o.Dynamic.
   243  		Resource(types.BackupPolicyGVR()).Namespace(o.Namespace).
   244  		List(context.TODO(), opts)
   245  	if err != nil {
   246  		return "", err
   247  	}
   248  	if len(objs.Items) == 0 {
   249  		return "", fmt.Errorf(`not found any backup policy for cluster "%s"`, o.Name)
   250  	}
   251  	var defaultBackupPolicies []unstructured.Unstructured
   252  	for _, obj := range objs.Items {
   253  		if obj.GetAnnotations()[dptypes.DefaultBackupPolicyAnnotationKey] == annotationTrueValue {
   254  			defaultBackupPolicies = append(defaultBackupPolicies, obj)
   255  		}
   256  	}
   257  	if len(defaultBackupPolicies) == 0 {
   258  		return "", fmt.Errorf(`not found any default backup policy for cluster "%s"`, o.Name)
   259  	}
   260  	if len(defaultBackupPolicies) > 1 {
   261  		return "", fmt.Errorf(`cluster "%s" has multiple default backup policies`, o.Name)
   262  	}
   263  	return defaultBackupPolicies[0].GetName(), nil
   264  }
   265  
   266  func NewCreateBackupCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   267  	customOutPut := func(opt *create.CreateOptions) {
   268  		output := fmt.Sprintf("Backup %s created successfully, you can view the progress:", opt.Name)
   269  		printer.PrintLine(output)
   270  		nextLine := fmt.Sprintf("\tkbcli cluster list-backups --name=%s -n %s", opt.Name, opt.Namespace)
   271  		printer.PrintLine(nextLine)
   272  	}
   273  
   274  	o := &CreateBackupOptions{
   275  		CreateOptions: create.CreateOptions{
   276  			IOStreams:       streams,
   277  			Factory:         f,
   278  			GVR:             types.BackupGVR(),
   279  			CueTemplateName: "backup_template.cue",
   280  			CustomOutPut:    customOutPut,
   281  		},
   282  	}
   283  	o.CreateOptions.Options = o
   284  
   285  	cmd := &cobra.Command{
   286  		Use:               "backup NAME",
   287  		Short:             "Create a backup for the cluster.",
   288  		Example:           createBackupExample,
   289  		ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()),
   290  		Run: func(cmd *cobra.Command, args []string) {
   291  			o.Args = args
   292  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
   293  			cmdutil.CheckErr(o.CompleteBackup())
   294  			cmdutil.CheckErr(o.Validate())
   295  			cmdutil.CheckErr(o.Run())
   296  		},
   297  	}
   298  
   299  	cmd.Flags().StringVar(&o.BackupMethod, "method", "", "Backup methods are defined in backup policy (required), if only one backup method in backup policy, use it as default backup method, if multiple backup methods in backup policy, use method which volume snapshot is true as default backup method")
   300  	cmd.Flags().StringVar(&o.BackupName, "name", "", "Backup name")
   301  	cmd.Flags().StringVar(&o.BackupPolicy, "policy", "", "Backup policy name, if not specified, use the cluster default backup policy")
   302  	cmd.Flags().StringVar(&o.DeletionPolicy, "deletion-policy", "Delete", "Deletion policy for backup, determine whether the backup content in backup repo will be deleted after the backup is deleted, supported values: [Delete, Retain]")
   303  	cmd.Flags().StringVar(&o.RetentionPeriod, "retention-period", "", "Retention period for backup, supported values: [1y, 1mo, 1d, 1h, 1m] or combine them [1y1mo1d1h1m], if not specified, the backup will not be automatically deleted, you need to manually delete it.")
   304  	cmd.Flags().StringVar(&o.ParentBackupName, "parent-backup", "", "Parent backup name, used for incremental backup")
   305  	// register backup flag completion func
   306  	o.RegisterBackupFlagCompletionFunc(cmd, f)
   307  	return cmd
   308  }
   309  func (o *CreateBackupOptions) RegisterBackupFlagCompletionFunc(cmd *cobra.Command, f cmdutil.Factory) {
   310  	getClusterName := func(cmd *cobra.Command, args []string) string {
   311  		clusterName, _ := cmd.Flags().GetString("cluster")
   312  		if clusterName != "" {
   313  			return clusterName
   314  		}
   315  		if len(args) > 0 {
   316  			return args[0]
   317  		}
   318  		return ""
   319  	}
   320  	util.CheckErr(cmd.RegisterFlagCompletionFunc(
   321  		"deletion-policy",
   322  		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   323  			return []string{string(dpv1alpha1.BackupDeletionPolicyRetain), string(dpv1alpha1.BackupDeletionPolicyDelete)}, cobra.ShellCompDirectiveNoFileComp
   324  		}))
   325  
   326  	util.CheckErr(cmd.RegisterFlagCompletionFunc(
   327  		"policy",
   328  		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   329  			label := fmt.Sprintf("%s=%s", constant.AppInstanceLabelKey, getClusterName(cmd, args))
   330  			return util.CompGetResourceWithLabels(f, cmd, util.GVRToString(types.BackupPolicyGVR()), []string{label}, toComplete), cobra.ShellCompDirectiveNoFileComp
   331  		}))
   332  
   333  	util.CheckErr(cmd.RegisterFlagCompletionFunc(
   334  		"method",
   335  		func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
   336  			namespace, _ := cmd.Flags().GetString("namespace")
   337  			if namespace == "" {
   338  				namespace, _, _ = f.ToRawKubeConfigLoader().Namespace()
   339  			}
   340  			var (
   341  				labelSelector string
   342  				clusterName   = getClusterName(cmd, args)
   343  			)
   344  			if clusterName != "" {
   345  				labelSelector = fmt.Sprintf("%s=%s", constant.AppInstanceLabelKey, clusterName)
   346  			}
   347  			dynamicClient, _ := f.DynamicClient()
   348  			objs, _ := dynamicClient.Resource(types.BackupPolicyGVR()).Namespace(namespace).List(context.TODO(), metav1.ListOptions{
   349  				LabelSelector: labelSelector,
   350  			})
   351  			methodMap := map[string]struct{}{}
   352  			for _, v := range objs.Items {
   353  				backupPolicy := &dpv1alpha1.BackupPolicy{}
   354  				_ = runtime.DefaultUnstructuredConverter.FromUnstructured(v.Object, backupPolicy)
   355  				for _, m := range backupPolicy.Spec.BackupMethods {
   356  					methodMap[m.Name] = struct{}{}
   357  				}
   358  			}
   359  			return maps.Keys(methodMap), cobra.ShellCompDirectiveNoFileComp
   360  		}))
   361  }
   362  
   363  func PrintBackupList(o ListBackupOptions) error {
   364  	var backupNameMap = make(map[string]bool)
   365  	for _, name := range o.Names {
   366  		backupNameMap[name] = true
   367  	}
   368  
   369  	// if format is JSON or YAML, use default printer to output the result.
   370  	if o.Format == printer.JSON || o.Format == printer.YAML {
   371  		if o.BackupName != "" {
   372  			o.Names = []string{o.BackupName}
   373  		}
   374  		_, err := o.Run()
   375  		return err
   376  	}
   377  	dynamic, err := o.Factory.DynamicClient()
   378  	if err != nil {
   379  		return err
   380  	}
   381  	if o.AllNamespaces {
   382  		o.Namespace = ""
   383  	}
   384  	backupList, err := dynamic.Resource(types.BackupGVR()).Namespace(o.Namespace).List(context.TODO(), metav1.ListOptions{
   385  		LabelSelector: o.LabelSelector,
   386  		FieldSelector: o.FieldSelector,
   387  	})
   388  	if err != nil {
   389  		return err
   390  	}
   391  
   392  	if len(backupList.Items) == 0 {
   393  		o.PrintNotFoundResources()
   394  		return nil
   395  	}
   396  
   397  	// sort the unstructured objects with the creationTimestamp in positive order
   398  	sort.Sort(unstructuredList(backupList.Items))
   399  	tbl := printer.NewTablePrinter(o.Out)
   400  	tbl.SetHeader("NAME", "NAMESPACE", "SOURCE-CLUSTER", "METHOD", "STATUS", "TOTAL-SIZE", "DURATION", "CREATE-TIME", "COMPLETION-TIME", "EXPIRATION")
   401  	for _, obj := range backupList.Items {
   402  		backup := &dpv1alpha1.Backup{}
   403  		if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, backup); err != nil {
   404  			return err
   405  		}
   406  		// TODO(ldm): find cluster from backup policy target spec.
   407  		sourceCluster := backup.Labels[constant.AppInstanceLabelKey]
   408  		durationStr := ""
   409  		if backup.Status.Duration != nil {
   410  			durationStr = duration.HumanDuration(backup.Status.Duration.Duration)
   411  		}
   412  		statusString := string(backup.Status.Phase)
   413  		if len(o.Names) > 0 && !backupNameMap[backup.Name] {
   414  			continue
   415  		}
   416  		tbl.AddRow(backup.Name, backup.Namespace, sourceCluster, backup.Spec.BackupMethod, statusString, backup.Status.TotalSize,
   417  			durationStr, util.TimeFormat(&backup.CreationTimestamp), util.TimeFormat(backup.Status.CompletionTimestamp),
   418  			util.TimeFormat(backup.Status.Expiration))
   419  	}
   420  	tbl.Print()
   421  	return nil
   422  }
   423  
   424  func NewListBackupCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   425  	o := &ListBackupOptions{ListOptions: list.NewListOptions(f, streams, types.BackupGVR())}
   426  	cmd := &cobra.Command{
   427  		Use:               "list-backups",
   428  		Short:             "List backups.",
   429  		Aliases:           []string{"ls-backups"},
   430  		Example:           listBackupExample,
   431  		ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()),
   432  		Run: func(cmd *cobra.Command, args []string) {
   433  			o.LabelSelector = util.BuildLabelSelectorByNames(o.LabelSelector, args)
   434  			if o.BackupName != "" {
   435  				o.Names = []string{o.BackupName}
   436  			}
   437  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
   438  			util.CheckErr(o.Complete())
   439  			util.CheckErr(PrintBackupList(*o))
   440  		},
   441  	}
   442  	o.AddFlags(cmd)
   443  	cmd.Flags().StringVar(&o.BackupName, "name", "", "The backup name to get the details.")
   444  	return cmd
   445  }
   446  
   447  func NewDescribeBackupCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   448  	o := &DescribeBackupOptions{
   449  		Factory:   f,
   450  		IOStreams: streams,
   451  		Gvr:       types.BackupGVR(),
   452  	}
   453  	cmd := &cobra.Command{
   454  		Use:               "describe-backup BACKUP-NAME",
   455  		Short:             "Describe a backup.",
   456  		Aliases:           []string{"desc-backup"},
   457  		Example:           describeBackupExample,
   458  		ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.BackupGVR()),
   459  		Run: func(cmd *cobra.Command, args []string) {
   460  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
   461  			util.CheckErr(o.Complete(args))
   462  			util.CheckErr(o.Run())
   463  		},
   464  	}
   465  	return cmd
   466  }
   467  
   468  func NewDeleteBackupCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   469  	o := delete.NewDeleteOptions(f, streams, types.BackupGVR())
   470  	cmd := &cobra.Command{
   471  		Use:               "delete-backup",
   472  		Short:             "Delete a backup.",
   473  		Example:           deleteBackupExample,
   474  		ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()),
   475  		Run: func(cmd *cobra.Command, args []string) {
   476  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
   477  			util.CheckErr(completeForDeleteBackup(o, args))
   478  			util.CheckErr(o.Run())
   479  		},
   480  	}
   481  	cmd.Flags().StringSliceVar(&o.Names, "name", []string{}, "Backup names")
   482  	o.AddFlags(cmd)
   483  	return cmd
   484  }
   485  
   486  // completeForDeleteBackup completes cmd for delete backup
   487  func completeForDeleteBackup(o *delete.DeleteOptions, args []string) error {
   488  	if len(args) == 0 {
   489  		return errors.New("Missing cluster name")
   490  	}
   491  	if len(args) > 1 {
   492  		return errors.New("Only supported delete the Backup of one cluster")
   493  	}
   494  	if !o.Force && len(o.Names) == 0 {
   495  		return errors.New("Missing --name as backup name.")
   496  	}
   497  	if o.Force && len(o.Names) == 0 {
   498  		// do force action, for --force and --name unset, delete all backups of the cluster
   499  		// if backup name unset and cluster name set, delete all backups of the cluster
   500  		o.LabelSelector = util.BuildLabelSelectorByNames(o.LabelSelector, args)
   501  		o.ConfirmedNames = args
   502  	}
   503  	o.ConfirmedNames = o.Names
   504  	return nil
   505  }
   506  
   507  type CreateRestoreOptions struct {
   508  	// backup name to restore in creation
   509  	Backup string `json:"backup,omitempty"`
   510  
   511  	// point in time recovery args
   512  	RestoreTime         *time.Time `json:"restoreTime,omitempty"`
   513  	RestoreTimeStr      string     `json:"restoreTimeStr,omitempty"`
   514  	VolumeRestorePolicy string     `json:"volumeRestorePolicy,omitempty"`
   515  
   516  	create.CreateOptions `json:"-"`
   517  }
   518  
   519  func (o *CreateRestoreOptions) getClusterObject(backup *dpv1alpha1.Backup) (*appsv1alpha1.Cluster, error) {
   520  	// use the cluster snapshot to restore firstly
   521  	clusterString, ok := backup.Annotations[constant.ClusterSnapshotAnnotationKey]
   522  	if ok {
   523  		clusterObj := &appsv1alpha1.Cluster{}
   524  		if err := json.Unmarshal([]byte(clusterString), &clusterObj); err != nil {
   525  			return nil, err
   526  		}
   527  		return clusterObj, nil
   528  	}
   529  	clusterName := backup.Labels[constant.AppInstanceLabelKey]
   530  	return cluster.GetClusterByName(o.Dynamic, clusterName, o.Namespace)
   531  }
   532  
   533  func (o *CreateRestoreOptions) Run() error {
   534  	if o.Backup != "" {
   535  		return o.runRestoreFromBackup()
   536  	}
   537  	return nil
   538  }
   539  
   540  func (o *CreateRestoreOptions) runRestoreFromBackup() error {
   541  	// get backup
   542  	backup := &dpv1alpha1.Backup{}
   543  	if err := cluster.GetK8SClientObject(o.Dynamic, backup, types.BackupGVR(), o.Namespace, o.Backup); err != nil {
   544  		return err
   545  	}
   546  	if backup.Status.Phase != dpv1alpha1.BackupPhaseCompleted {
   547  		return errors.Errorf(`backup "%s" is not completed.`, backup.Name)
   548  	}
   549  	if len(backup.Labels[constant.AppInstanceLabelKey]) == 0 {
   550  		return errors.Errorf(`missing source cluster in backup "%s", "app.kubernetes.io/instance" is empty in labels.`, o.Backup)
   551  	}
   552  
   553  	restoreTimeStr, err := formatRestoreTimeAndValidate(o.RestoreTimeStr, backup)
   554  	if err != nil {
   555  		return err
   556  	}
   557  	// get the cluster object and set the annotation for restore
   558  	clusterObj, err := o.getClusterObject(backup)
   559  	if err != nil {
   560  		return err
   561  	}
   562  	restoreAnnotation, err := getRestoreFromBackupAnnotation(backup, o.VolumeRestorePolicy, len(clusterObj.Spec.ComponentSpecs), clusterObj.Spec.ComponentSpecs[0].Name, restoreTimeStr)
   563  	if err != nil {
   564  		return err
   565  	}
   566  	clusterObj.ObjectMeta = metav1.ObjectMeta{
   567  		Namespace:   clusterObj.Namespace,
   568  		Name:        o.Name,
   569  		Annotations: map[string]string{constant.RestoreFromBackupAnnotationKey: restoreAnnotation},
   570  	}
   571  	return o.createCluster(clusterObj)
   572  }
   573  
   574  func (o *CreateRestoreOptions) createCluster(cluster *appsv1alpha1.Cluster) error {
   575  	clusterGVR := types.ClusterGVR()
   576  	cluster.Status = appsv1alpha1.ClusterStatus{}
   577  	cluster.TypeMeta = metav1.TypeMeta{
   578  		Kind:       types.KindCluster,
   579  		APIVersion: clusterGVR.Group + "/" + clusterGVR.Version,
   580  	}
   581  	// convert the cluster object and create it.
   582  	unstructuredMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&cluster)
   583  	if err != nil {
   584  		return err
   585  	}
   586  	unstructuredObj := &unstructured.Unstructured{Object: unstructuredMap}
   587  	if unstructuredObj, err = o.Dynamic.Resource(clusterGVR).Namespace(o.Namespace).Create(context.TODO(), unstructuredObj, metav1.CreateOptions{}); err != nil {
   588  		return err
   589  	}
   590  	if !o.Quiet {
   591  		fmt.Fprintf(o.Out, "%s %s created\n", unstructuredObj.GetKind(), unstructuredObj.GetName())
   592  	}
   593  	return nil
   594  }
   595  
   596  func isTimeInRange(t time.Time, start time.Time, end time.Time) bool {
   597  	return !t.Before(start) && !t.After(end)
   598  }
   599  
   600  func (o *CreateRestoreOptions) Validate() error {
   601  	if o.Backup == "" {
   602  		return fmt.Errorf("must be specified one of the --backup ")
   603  	}
   604  
   605  	if o.Name == "" {
   606  		name, err := generateClusterName(o.Dynamic, o.Namespace)
   607  		if err != nil {
   608  			return err
   609  		}
   610  		if name == "" {
   611  			return fmt.Errorf("failed to generate a random cluster name")
   612  		}
   613  		o.Name = name
   614  	}
   615  	return nil
   616  }
   617  
   618  func NewCreateRestoreCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   619  	o := &CreateRestoreOptions{}
   620  	o.CreateOptions = create.CreateOptions{
   621  		IOStreams: streams,
   622  		Factory:   f,
   623  		Options:   o,
   624  	}
   625  
   626  	cmd := &cobra.Command{
   627  		Use:     "restore",
   628  		Short:   "Restore a new cluster from backup.",
   629  		Example: createRestoreExample,
   630  		Run: func(cmd *cobra.Command, args []string) {
   631  			o.Args = args
   632  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
   633  			util.CheckErr(o.Complete())
   634  			util.CheckErr(o.Validate())
   635  			util.CheckErr(o.Run())
   636  		},
   637  	}
   638  	cmd.Flags().StringVar(&o.Backup, "backup", "", "Backup name")
   639  	cmd.Flags().StringVar(&o.RestoreTimeStr, "restore-to-time", "", "point in time recovery(PITR)")
   640  	cmd.Flags().StringVar(&o.VolumeRestorePolicy, "volume-restore-policy", "Parallel", "the volume claim restore policy, supported values: [Serial, Parallel]")
   641  	return cmd
   642  }
   643  
   644  func NewListBackupPolicyCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   645  	o := list.NewListOptions(f, streams, types.BackupPolicyGVR())
   646  	cmd := &cobra.Command{
   647  		Use:               "list-backup-policy",
   648  		Short:             "List backups policies.",
   649  		Aliases:           []string{"list-bp"},
   650  		Example:           listBackupPolicyExample,
   651  		ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()),
   652  		Run: func(cmd *cobra.Command, args []string) {
   653  			o.LabelSelector = util.BuildLabelSelectorByNames(o.LabelSelector, args)
   654  			o.Names = nil
   655  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
   656  			util.CheckErr(o.Complete())
   657  			util.CheckErr(PrintBackupPolicyList(*o))
   658  		},
   659  	}
   660  	o.AddFlags(cmd)
   661  	return cmd
   662  }
   663  
   664  // PrintBackupPolicyList prints the backup policy list.
   665  func PrintBackupPolicyList(o list.ListOptions) error {
   666  	var backupPolicyNameMap = make(map[string]bool)
   667  	for _, name := range o.Names {
   668  		backupPolicyNameMap[name] = true
   669  	}
   670  
   671  	// if format is JSON or YAML, use default printer to output the result.
   672  	if o.Format == printer.JSON || o.Format == printer.YAML {
   673  		_, err := o.Run()
   674  		return err
   675  	}
   676  	dynamic, err := o.Factory.DynamicClient()
   677  	if err != nil {
   678  		return err
   679  	}
   680  	if o.AllNamespaces {
   681  		o.Namespace = ""
   682  	}
   683  	backupPolicyList, err := dynamic.Resource(types.BackupPolicyGVR()).Namespace(o.Namespace).List(context.TODO(), metav1.ListOptions{
   684  		LabelSelector: o.LabelSelector,
   685  		FieldSelector: o.FieldSelector,
   686  	})
   687  	if err != nil {
   688  		return err
   689  	}
   690  
   691  	if len(backupPolicyList.Items) == 0 {
   692  		o.PrintNotFoundResources()
   693  		return nil
   694  	}
   695  
   696  	tbl := printer.NewTablePrinter(o.Out)
   697  	tbl.SetHeader("NAME", "NAMESPACE", "DEFAULT", "CLUSTER", "CREATE-TIME", "STATUS")
   698  	for _, obj := range backupPolicyList.Items {
   699  		defaultPolicy, ok := obj.GetAnnotations()[dptypes.DefaultBackupPolicyAnnotationKey]
   700  		backupPolicy := &dpv1alpha1.BackupPolicy{}
   701  		if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, backupPolicy); err != nil {
   702  			return err
   703  		}
   704  		if !ok {
   705  			defaultPolicy = "false"
   706  		}
   707  		if len(o.Names) > 0 && !backupPolicyNameMap[backupPolicy.Name] {
   708  			continue
   709  		}
   710  		createTime := obj.GetCreationTimestamp()
   711  		tbl.AddRow(obj.GetName(), obj.GetNamespace(), defaultPolicy, obj.GetLabels()[constant.AppInstanceLabelKey],
   712  			util.TimeFormat(&createTime), backupPolicy.Status.Phase)
   713  	}
   714  	tbl.Print()
   715  	return nil
   716  }
   717  
   718  type updateBackupPolicyFieldFunc func(backupPolicy *dpv1alpha1.BackupPolicy, targetVal string) error
   719  
   720  type editBackupPolicyOptions struct {
   721  	namespace string
   722  	name      string
   723  	dynamic   dynamic.Interface
   724  	Factory   cmdutil.Factory
   725  
   726  	GVR schema.GroupVersionResource
   727  	genericiooptions.IOStreams
   728  	editContent       []editorRow
   729  	editContentKeyMap map[string]updateBackupPolicyFieldFunc
   730  	original          string
   731  	target            string
   732  	values            []string
   733  	isTest            bool
   734  }
   735  
   736  type editorRow struct {
   737  	// key content key (required).
   738  	key string
   739  	// value jsonpath for backupPolicy.spec.
   740  	jsonpath string
   741  	// updateFunc applies the modified value to backupPolicy (required).
   742  	updateFunc updateBackupPolicyFieldFunc
   743  }
   744  
   745  func NewEditBackupPolicyCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
   746  	o := editBackupPolicyOptions{Factory: f, IOStreams: streams, GVR: types.BackupPolicyGVR()}
   747  	cmd := &cobra.Command{
   748  		Use:                   "edit-backup-policy",
   749  		DisableFlagsInUseLine: true,
   750  		Aliases:               []string{"edit-bp"},
   751  		Short:                 "Edit backup policy",
   752  		Example:               editExample,
   753  		ValidArgsFunction:     util.ResourceNameCompletionFunc(f, types.BackupPolicyGVR()),
   754  		Run: func(cmd *cobra.Command, args []string) {
   755  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
   756  			cmdutil.CheckErr(o.complete(args))
   757  			cmdutil.CheckErr(o.runEditBackupPolicy())
   758  		},
   759  	}
   760  	cmd.Flags().StringArrayVar(&o.values, "set", []string{},
   761  		"set values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2)")
   762  	return cmd
   763  }
   764  
   765  func (o *editBackupPolicyOptions) complete(args []string) error {
   766  	var err error
   767  	if len(args) == 0 {
   768  		return fmt.Errorf("missing backupPolicy name")
   769  	}
   770  	if len(args) > 1 {
   771  		return fmt.Errorf("only support to update one backupPolicy or quote cronExpression")
   772  	}
   773  	o.name = args[0]
   774  	if o.namespace, _, err = o.Factory.ToRawKubeConfigLoader().Namespace(); err != nil {
   775  		return err
   776  	}
   777  	if o.dynamic, err = o.Factory.DynamicClient(); err != nil {
   778  		return err
   779  	}
   780  	updateRepoName := func(backupPolicy *dpv1alpha1.BackupPolicy, targetVal string) error {
   781  		// check if the backup repo exists
   782  		if targetVal != "" {
   783  			_, err := o.dynamic.Resource(types.BackupRepoGVR()).Get(context.Background(), targetVal, metav1.GetOptions{})
   784  			if err != nil {
   785  				return err
   786  			}
   787  		}
   788  		if backupPolicy != nil {
   789  			if targetVal != "" {
   790  				backupPolicy.Spec.BackupRepoName = &targetVal
   791  			} else {
   792  				backupPolicy.Spec.BackupRepoName = nil
   793  			}
   794  		}
   795  		return nil
   796  	}
   797  
   798  	o.editContent = []editorRow{
   799  		{
   800  			key:      "backupRepoName",
   801  			jsonpath: "backupRepoName",
   802  			updateFunc: func(backupPolicy *dpv1alpha1.BackupPolicy, targetVal string) error {
   803  				return updateRepoName(backupPolicy, targetVal)
   804  			},
   805  		},
   806  	}
   807  	o.editContentKeyMap = map[string]updateBackupPolicyFieldFunc{}
   808  	for _, v := range o.editContent {
   809  		if v.updateFunc == nil {
   810  			return fmt.Errorf("updateFunc can not be nil")
   811  		}
   812  		o.editContentKeyMap[v.key] = v.updateFunc
   813  	}
   814  	return nil
   815  }
   816  
   817  func (o *editBackupPolicyOptions) runEditBackupPolicy() error {
   818  	backupPolicy := &dpv1alpha1.BackupPolicy{}
   819  	key := client.ObjectKey{
   820  		Name:      o.name,
   821  		Namespace: o.namespace,
   822  	}
   823  	err := util.GetResourceObjectFromGVR(types.BackupPolicyGVR(), key, o.dynamic, &backupPolicy)
   824  	if err != nil {
   825  		return err
   826  	}
   827  	if len(o.values) == 0 {
   828  		edited, err := o.runWithEditor(backupPolicy)
   829  		if err != nil {
   830  			return err
   831  		}
   832  		o.values = strings.Split(edited, "\n")
   833  	}
   834  	return o.applyChanges(backupPolicy)
   835  }
   836  
   837  func (o *editBackupPolicyOptions) runWithEditor(backupPolicy *dpv1alpha1.BackupPolicy) (string, error) {
   838  	editor := editor.NewDefaultEditor([]string{
   839  		"KUBE_EDITOR",
   840  		"EDITOR",
   841  	})
   842  	contents, err := o.buildEditorContent(backupPolicy)
   843  	if err != nil {
   844  		return "", err
   845  	}
   846  	addHeader := func() string {
   847  		return fmt.Sprintf(`# Please edit the object below. Lines beginning with a '#' will be ignored,
   848  # and an empty file will abort the edit. If an error occurs while saving this file will be
   849  # reopened with the relevant failures.
   850  #
   851  %s
   852  `, *contents)
   853  	}
   854  	if o.isTest {
   855  		// only for testing
   856  		return "", nil
   857  	}
   858  	edited, _, err := editor.LaunchTempFile(fmt.Sprintf("%s-edit-", backupPolicy.Name), "", bytes.NewBufferString(addHeader()))
   859  	if err != nil {
   860  		return "", err
   861  	}
   862  	return string(edited), nil
   863  }
   864  
   865  // buildEditorContent builds the editor content.
   866  func (o *editBackupPolicyOptions) buildEditorContent(backPolicy *dpv1alpha1.BackupPolicy) (*string, error) {
   867  	var contents []string
   868  	for _, v := range o.editContent {
   869  		// get the value with jsonpath
   870  		val, err := o.getValueWithJsonpath(backPolicy.Spec, v.jsonpath)
   871  		if err != nil {
   872  			return nil, err
   873  		}
   874  		if val == nil {
   875  			continue
   876  		}
   877  		row := fmt.Sprintf("%s=%s", v.key, *val)
   878  		o.original += row
   879  		contents = append(contents, row)
   880  	}
   881  	result := strings.Join(contents, "\n")
   882  	return &result, nil
   883  }
   884  
   885  // getValueWithJsonpath gets the value with jsonpath.
   886  func (o *editBackupPolicyOptions) getValueWithJsonpath(spec dpv1alpha1.BackupPolicySpec, path string) (*string, error) {
   887  	parser := jsonpath.New("edit-backup-policy").AllowMissingKeys(true)
   888  	pathExpression, err := get.RelaxedJSONPathExpression(path)
   889  	if err != nil {
   890  		return nil, err
   891  	}
   892  	if err = parser.Parse(pathExpression); err != nil {
   893  		return nil, err
   894  	}
   895  	values, err := parser.FindResults(spec)
   896  	if err != nil {
   897  		return nil, err
   898  	}
   899  	for _, v := range values {
   900  		if len(v) == 0 {
   901  			continue
   902  		}
   903  		v1 := v[0]
   904  		switch v1.Kind() {
   905  		case reflect.Ptr, reflect.Interface:
   906  			if v1.IsNil() {
   907  				return nil, nil
   908  			}
   909  			val := fmt.Sprintf("%v", v1.Elem())
   910  			return &val, nil
   911  		default:
   912  			val := fmt.Sprintf("%v", v1.Interface())
   913  			return &val, nil
   914  		}
   915  	}
   916  	return nil, nil
   917  }
   918  
   919  // applyChanges applies the changes of backupPolicy.
   920  func (o *editBackupPolicyOptions) applyChanges(backupPolicy *dpv1alpha1.BackupPolicy) error {
   921  	for _, v := range o.values {
   922  		row := strings.TrimSpace(v)
   923  		if strings.HasPrefix(row, "#") || row == "" {
   924  			continue
   925  		}
   926  		o.target += row
   927  		arr := strings.Split(row, "=")
   928  		if len(arr) != 2 {
   929  			return fmt.Errorf(`invalid row: %s, format should be "key=value"`, v)
   930  		}
   931  		updateFn, ok := o.editContentKeyMap[arr[0]]
   932  		if !ok {
   933  			return fmt.Errorf(`invalid key: %s`, arr[0])
   934  		}
   935  		arr[1] = strings.Trim(arr[1], `"`)
   936  		arr[1] = strings.Trim(arr[1], `'`)
   937  		if err := updateFn(backupPolicy, arr[1]); err != nil {
   938  			return err
   939  		}
   940  	}
   941  	// if no changes, return.
   942  	if o.original == o.target {
   943  		fmt.Fprintln(o.Out, "updated (no change)")
   944  		return nil
   945  	}
   946  	obj, err := runtime.DefaultUnstructuredConverter.ToUnstructured(backupPolicy)
   947  	if err != nil {
   948  		return err
   949  	}
   950  	if _, err = o.dynamic.Resource(types.BackupPolicyGVR()).Namespace(backupPolicy.Namespace).Update(context.TODO(),
   951  		&unstructured.Unstructured{Object: obj}, metav1.UpdateOptions{}); err != nil {
   952  		return err
   953  	}
   954  	fmt.Fprintln(o.Out, "updated")
   955  	return nil
   956  }
   957  
   958  type DescribeBackupPolicyOptions struct {
   959  	namespace string
   960  	dynamic   dynamic.Interface
   961  	Factory   cmdutil.Factory
   962  	client    clientset.Interface
   963  
   964  	LabelSelector string
   965  	ClusterNames  []string
   966  	Names         []string
   967  
   968  	genericiooptions.IOStreams
   969  }
   970  
   971  func (o *DescribeBackupPolicyOptions) Complete() error {
   972  	var err error
   973  
   974  	if o.client, err = o.Factory.KubernetesClientSet(); err != nil {
   975  		return err
   976  	}
   977  
   978  	if o.dynamic, err = o.Factory.DynamicClient(); err != nil {
   979  		return err
   980  	}
   981  
   982  	if o.namespace, _, err = o.Factory.ToRawKubeConfigLoader().Namespace(); err != nil {
   983  		return err
   984  	}
   985  	return nil
   986  }
   987  
   988  func (o *DescribeBackupPolicyOptions) Validate() error {
   989  	// must specify one of the cluster name or backup policy name
   990  	if len(o.ClusterNames) == 0 && len(o.Names) == 0 {
   991  		return fmt.Errorf("missing cluster name or backup policy name")
   992  	}
   993  
   994  	return nil
   995  }
   996  
   997  func (o *DescribeBackupPolicyOptions) Run() error {
   998  	var backupPolicyNameMap = make(map[string]bool)
   999  	for _, name := range o.Names {
  1000  		backupPolicyNameMap[name] = true
  1001  	}
  1002  
  1003  	backupPolicyList, err := o.dynamic.Resource(types.BackupPolicyGVR()).Namespace(o.namespace).List(context.TODO(), metav1.ListOptions{
  1004  		LabelSelector: o.LabelSelector,
  1005  	})
  1006  	if err != nil {
  1007  		return err
  1008  	}
  1009  
  1010  	if len(backupPolicyList.Items) == 0 {
  1011  		fmt.Fprintf(o.Out, "No backup policy found\n")
  1012  		return nil
  1013  	}
  1014  
  1015  	for _, obj := range backupPolicyList.Items {
  1016  		backupPolicy := &dpv1alpha1.BackupPolicy{}
  1017  		if err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, backupPolicy); err != nil {
  1018  			return err
  1019  		}
  1020  		isDefault := obj.GetAnnotations()[dptypes.DefaultBackupPolicyAnnotationKey] == "true"
  1021  		// if backup policy name is specified, only print the backup policy with the specified name
  1022  		if len(o.Names) > 0 && !backupPolicyNameMap[backupPolicy.Name] {
  1023  			continue
  1024  		}
  1025  		// if backup policy name is not specified, only print the default backup policy
  1026  		if len(o.Names) == 0 && !isDefault {
  1027  			continue
  1028  		}
  1029  		if err := o.printBackupPolicyObj(backupPolicy); err != nil {
  1030  			return err
  1031  		}
  1032  	}
  1033  
  1034  	return nil
  1035  }
  1036  
  1037  func (o *DescribeBackupPolicyOptions) printBackupPolicyObj(obj *dpv1alpha1.BackupPolicy) error {
  1038  	printer.PrintLine("Summary:")
  1039  	realPrintPairStringToLine("Name", obj.Name)
  1040  	realPrintPairStringToLine("Cluster", obj.Labels[constant.AppInstanceLabelKey])
  1041  	realPrintPairStringToLine("Namespace", obj.Namespace)
  1042  	realPrintPairStringToLine("Default", strconv.FormatBool(obj.Annotations[dptypes.DefaultBackupPolicyAnnotationKey] == "true"))
  1043  	if obj.Spec.BackupRepoName != nil {
  1044  		realPrintPairStringToLine("Backup Repo Name", *obj.Spec.BackupRepoName)
  1045  	}
  1046  
  1047  	printer.PrintLine("\nBackup Methods:")
  1048  	p := printer.NewTablePrinter(o.Out)
  1049  	p.SetHeader("Name", "ActionSet", "snapshot-volumes")
  1050  	for _, v := range obj.Spec.BackupMethods {
  1051  		p.AddRow(v.Name, v.ActionSetName, strconv.FormatBool(*v.SnapshotVolumes))
  1052  	}
  1053  	p.Print()
  1054  
  1055  	return nil
  1056  }
  1057  
  1058  func NewDescribeBackupPolicyCmd(f cmdutil.Factory, streams genericiooptions.IOStreams) *cobra.Command {
  1059  	o := &DescribeBackupPolicyOptions{
  1060  		Factory:   f,
  1061  		IOStreams: streams,
  1062  	}
  1063  	cmd := &cobra.Command{
  1064  		Use:               "describe-backup-policy",
  1065  		Aliases:           []string{"desc-backup-policy"},
  1066  		Short:             "Describe backup policy",
  1067  		Example:           describeBackupPolicyExample,
  1068  		ValidArgsFunction: util.ResourceNameCompletionFunc(f, types.ClusterGVR()),
  1069  		Run: func(cmd *cobra.Command, args []string) {
  1070  			o.ClusterNames = args
  1071  			o.LabelSelector = util.BuildLabelSelectorByNames(o.LabelSelector, args)
  1072  			cmdutil.BehaviorOnFatal(printer.FatalWithRedColor)
  1073  			util.CheckErr(o.Complete())
  1074  			util.CheckErr(o.Validate())
  1075  			util.CheckErr(o.Run())
  1076  		},
  1077  	}
  1078  	cmd.Flags().StringSliceVar(&o.Names, "name", []string{}, "Backup policy name")
  1079  	return cmd
  1080  }
  1081  
  1082  func (o *DescribeBackupOptions) Complete(args []string) error {
  1083  	var err error
  1084  
  1085  	if len(args) == 0 {
  1086  		return fmt.Errorf("backup name should be specified")
  1087  	}
  1088  
  1089  	o.names = args
  1090  
  1091  	if o.client, err = o.Factory.KubernetesClientSet(); err != nil {
  1092  		return err
  1093  	}
  1094  
  1095  	if o.dynamic, err = o.Factory.DynamicClient(); err != nil {
  1096  		return err
  1097  	}
  1098  
  1099  	if o.namespace, _, err = o.Factory.ToRawKubeConfigLoader().Namespace(); err != nil {
  1100  		return err
  1101  	}
  1102  	return nil
  1103  }
  1104  
  1105  func (o *DescribeBackupOptions) Run() error {
  1106  	for _, name := range o.names {
  1107  		backupObj := &dpv1alpha1.Backup{}
  1108  		if err := cluster.GetK8SClientObject(o.dynamic, backupObj, o.Gvr, o.namespace, name); err != nil {
  1109  			return err
  1110  		}
  1111  		if err := o.printBackupObj(backupObj); err != nil {
  1112  			return err
  1113  		}
  1114  	}
  1115  	return nil
  1116  }
  1117  
  1118  func (o *DescribeBackupOptions) printBackupObj(obj *dpv1alpha1.Backup) error {
  1119  	targetCluster := obj.Labels[constant.AppInstanceLabelKey]
  1120  	printer.PrintLineWithTabSeparator(
  1121  		printer.NewPair("Name", obj.Name),
  1122  		printer.NewPair("Cluster", targetCluster),
  1123  		printer.NewPair("Namespace", obj.Namespace),
  1124  	)
  1125  	printer.PrintLine("\nSpec:")
  1126  	realPrintPairStringToLine("Method", obj.Spec.BackupMethod)
  1127  	realPrintPairStringToLine("Policy Name", obj.Spec.BackupPolicyName)
  1128  
  1129  	printer.PrintLine("\nStatus:")
  1130  	realPrintPairStringToLine("Phase", string(obj.Status.Phase))
  1131  	realPrintPairStringToLine("Total Size", obj.Status.TotalSize)
  1132  	if obj.Status.BackupMethod != nil {
  1133  		realPrintPairStringToLine("ActionSet Name", obj.Status.BackupMethod.ActionSetName)
  1134  	}
  1135  	if obj.Status.BackupRepoName != "" {
  1136  		realPrintPairStringToLine("Repository", obj.Status.BackupRepoName)
  1137  	}
  1138  	if obj.Status.PersistentVolumeClaimName != "" {
  1139  		realPrintPairStringToLine("PVC Name", obj.Status.PersistentVolumeClaimName)
  1140  	}
  1141  	if obj.Status.Duration != nil {
  1142  		realPrintPairStringToLine("Duration", duration.HumanDuration(obj.Status.Duration.Duration))
  1143  	}
  1144  	realPrintPairStringToLine("Expiration Time", util.TimeFormat(obj.Status.Expiration))
  1145  	realPrintPairStringToLine("Start Time", util.TimeFormat(obj.Status.StartTimestamp))
  1146  	realPrintPairStringToLine("Completion Time", util.TimeFormat(obj.Status.CompletionTimestamp))
  1147  	// print failure reason, ignore error
  1148  	_ = o.enhancePrintFailureReason(obj.Name, obj.Status.FailureReason)
  1149  
  1150  	realPrintPairStringToLine("Path", obj.Status.Path)
  1151  
  1152  	if obj.Status.TimeRange != nil {
  1153  		realPrintPairStringToLine("Time Range Start", util.TimeFormat(obj.Status.TimeRange.Start))
  1154  		realPrintPairStringToLine("Time Range End", util.TimeFormat(obj.Status.TimeRange.End))
  1155  	}
  1156  
  1157  	if len(obj.Status.VolumeSnapshots) > 0 {
  1158  		printer.PrintLine("\nVolume Snapshots:")
  1159  		for _, v := range obj.Status.VolumeSnapshots {
  1160  			realPrintPairStringToLine("Name", v.Name)
  1161  			realPrintPairStringToLine("Content Name", v.ContentName)
  1162  			realPrintPairStringToLine("Volume Name:", v.VolumeName)
  1163  			realPrintPairStringToLine("Size", v.Size)
  1164  		}
  1165  	}
  1166  
  1167  	// get all events about backup
  1168  	events, err := o.client.CoreV1().Events(o.namespace).Search(scheme.Scheme, obj)
  1169  	if err != nil {
  1170  		return err
  1171  	}
  1172  
  1173  	// print the warning events
  1174  	printer.PrintAllWarningEvents(events, o.Out)
  1175  
  1176  	return nil
  1177  }
  1178  
  1179  func realPrintPairStringToLine(name, value string, spaceCount ...int) {
  1180  	if value != "" {
  1181  		printer.PrintPairStringToLine(name, value, spaceCount...)
  1182  	}
  1183  }
  1184  
  1185  // print the pod error logs if failure reason has occurred
  1186  // TODO: the failure reason should be improved in the backup controller
  1187  func (o *DescribeBackupOptions) enhancePrintFailureReason(backupName, failureReason string, spaceCount ...int) error {
  1188  	if failureReason == "" {
  1189  		return nil
  1190  	}
  1191  	ctx := context.Background()
  1192  	// get the latest job log details.
  1193  	labels := fmt.Sprintf("%s=%s",
  1194  		dptypes.BackupNameLabelKey, backupName,
  1195  	)
  1196  	jobList, err := o.client.BatchV1().Jobs("").List(ctx, metav1.ListOptions{LabelSelector: labels})
  1197  	if err != nil {
  1198  		return err
  1199  	}
  1200  	var failedJob *batchv1.Job
  1201  	for _, i := range jobList.Items {
  1202  		if i.Status.Failed > 0 {
  1203  			failedJob = &i
  1204  			break
  1205  		}
  1206  	}
  1207  	if failedJob != nil {
  1208  		podLabels := fmt.Sprintf("%s=%s",
  1209  			"controller-uid", failedJob.UID,
  1210  		)
  1211  		podList, err := o.client.CoreV1().Pods(failedJob.Namespace).List(ctx, metav1.ListOptions{LabelSelector: podLabels})
  1212  		if err != nil {
  1213  			return err
  1214  		}
  1215  		if len(podList.Items) > 0 {
  1216  			tailLines := int64(5)
  1217  			req := o.client.CoreV1().
  1218  				Pods(podList.Items[0].Namespace).
  1219  				GetLogs(podList.Items[0].Name, &corev1.PodLogOptions{TailLines: &tailLines})
  1220  			data, err := req.DoRaw(ctx)
  1221  			if err != nil {
  1222  				return err
  1223  			}
  1224  			failureReason = fmt.Sprintf("%s\n pod %s error logs:\n%s",
  1225  				failureReason, podList.Items[0].Name, string(data))
  1226  		}
  1227  	}
  1228  	printer.PrintPairStringToLine("Failure Reason", failureReason, spaceCount...)
  1229  
  1230  	return nil
  1231  }