github.com/oam-dev/kubevela@v1.9.11/references/cli/workflow.go (about)

     1  /*
     2  Copyright 2021 The KubeVela Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package cli
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io"
    23  	"os"
    24  
    25  	"github.com/AlecAivazis/survey/v2"
    26  	"github.com/gosuri/uitable"
    27  	workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1"
    28  	"github.com/pkg/errors"
    29  	"github.com/spf13/cobra"
    30  	"sigs.k8s.io/controller-runtime/pkg/client"
    31  
    32  	pkgmulticluster "github.com/kubevela/pkg/multicluster"
    33  	wfTypes "github.com/kubevela/workflow/pkg/types"
    34  	wfUtils "github.com/kubevela/workflow/pkg/utils"
    35  
    36  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1alpha1"
    37  	"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
    38  	"github.com/oam-dev/kubevela/apis/types"
    39  	"github.com/oam-dev/kubevela/pkg/utils/common"
    40  	cmdutil "github.com/oam-dev/kubevela/pkg/utils/util"
    41  	querytypes "github.com/oam-dev/kubevela/pkg/velaql/providers/query/types"
    42  	"github.com/oam-dev/kubevela/pkg/workflow/operation"
    43  )
    44  
    45  // NewWorkflowCommand create `workflow` command
    46  func NewWorkflowCommand(c common.Args, order string, ioStreams cmdutil.IOStreams) *cobra.Command {
    47  	cmd := &cobra.Command{
    48  		Use:   "workflow",
    49  		Short: "Operate application delivery workflow.",
    50  		Long:  "Operate the Workflow during Application Delivery. Note that workflow command is both valid for Application Workflow and WorkflowRun(expect for [restart, rollout] command, they're only valid for Application Workflow). The command will try to find the Application first, if not found, it will try to find WorkflowRun. You can also specify the resource type by using --type flag.",
    51  		Annotations: map[string]string{
    52  			types.TagCommandType:  types.TypeCD,
    53  			types.TagCommandOrder: order,
    54  		},
    55  	}
    56  	wargs := &WorkflowArgs{
    57  		Args:   c,
    58  		Writer: ioStreams.Out,
    59  	}
    60  	cmd.SetOut(ioStreams.Out)
    61  	cmd.AddCommand(
    62  		NewWorkflowSuspendCommand(c, ioStreams, wargs),
    63  		NewWorkflowResumeCommand(c, ioStreams, wargs),
    64  		NewWorkflowTerminateCommand(c, ioStreams, wargs),
    65  		NewWorkflowRestartCommand(c, ioStreams, wargs),
    66  		NewWorkflowRollbackCommand(c, ioStreams, wargs),
    67  		NewWorkflowLogsCommand(c, ioStreams, wargs),
    68  		NewWorkflowDebugCommand(c, ioStreams, wargs),
    69  		NewWorkflowListCommand(c, ioStreams, wargs),
    70  	)
    71  	return cmd
    72  }
    73  
    74  // NewWorkflowSuspendCommand create workflow suspend command
    75  func NewWorkflowSuspendCommand(_ common.Args, _ cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
    76  	cmd := &cobra.Command{
    77  		Use:     "suspend",
    78  		Short:   "Suspend a workflow.",
    79  		Long:    "Suspend a workflow in cluster.",
    80  		Example: "vela workflow suspend <workflow-name>",
    81  		PreRun:  wargs.checkWorkflowNotComplete(),
    82  		RunE: func(cmd *cobra.Command, args []string) error {
    83  			ctx := context.Background()
    84  			if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
    85  				return err
    86  			}
    87  			if wargs.StepName != "" {
    88  				return wargs.StepOperator.Suspend(ctx, wargs.StepName)
    89  			}
    90  			return wargs.Operator.Suspend(ctx)
    91  		},
    92  	}
    93  	addNamespaceAndEnvArg(cmd)
    94  	cmd.Flags().StringVarP(&wargs.StepName, "step", "s", "", "specify the step name in the workflow")
    95  	cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
    96  	return cmd
    97  }
    98  
    99  // NewWorkflowResumeCommand create workflow resume command
   100  func NewWorkflowResumeCommand(_ common.Args, _ cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
   101  	cmd := &cobra.Command{
   102  		Use:     "resume",
   103  		Short:   "Resume a suspend workflow.",
   104  		Long:    "Resume a suspend workflow in cluster.",
   105  		Example: "vela workflow resume <workflow-name>",
   106  		PreRun:  wargs.checkWorkflowNotComplete(),
   107  		RunE: func(cmd *cobra.Command, args []string) error {
   108  			ctx := context.Background()
   109  			if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
   110  				return err
   111  			}
   112  			if wargs.StepName != "" {
   113  				return wargs.StepOperator.Resume(ctx, wargs.StepName)
   114  			}
   115  			return wargs.Operator.Resume(ctx)
   116  		},
   117  	}
   118  	addNamespaceAndEnvArg(cmd)
   119  	cmd.Flags().StringVarP(&wargs.StepName, "step", "s", "", "specify the step name in the workflow")
   120  	cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
   121  	return cmd
   122  }
   123  
   124  // NewWorkflowTerminateCommand create workflow terminate command
   125  func NewWorkflowTerminateCommand(_ common.Args, _ cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
   126  	cmd := &cobra.Command{
   127  		Use:     "terminate",
   128  		Short:   "Terminate a workflow.",
   129  		Long:    "Terminate a workflow in cluster.",
   130  		Example: "vela workflow terminate <workflow-name>",
   131  		PreRun:  wargs.checkWorkflowNotComplete(),
   132  		RunE: func(cmd *cobra.Command, args []string) error {
   133  			ctx := context.Background()
   134  			if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
   135  				return err
   136  			}
   137  			return wargs.Operator.Terminate(ctx)
   138  		},
   139  	}
   140  	addNamespaceAndEnvArg(cmd)
   141  	cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
   142  	return cmd
   143  }
   144  
   145  // NewWorkflowRestartCommand create workflow restart command
   146  func NewWorkflowRestartCommand(_ common.Args, _ cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
   147  	cmd := &cobra.Command{
   148  		Use:     "restart",
   149  		Short:   "Restart a workflow.",
   150  		Long:    "Restart a workflow in cluster.",
   151  		Example: "vela workflow restart <workflow-name>",
   152  		RunE: func(cmd *cobra.Command, args []string) error {
   153  			ctx := context.Background()
   154  			if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
   155  				return err
   156  			}
   157  			if wargs.StepName != "" {
   158  				return wargs.StepOperator.Restart(ctx, wargs.StepName)
   159  			}
   160  			return wargs.Operator.Restart(ctx)
   161  		},
   162  	}
   163  	addNamespaceAndEnvArg(cmd)
   164  	cmd.Flags().StringVarP(&wargs.StepName, "step", "s", "", "specify the step name in the workflow")
   165  	cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
   166  	return cmd
   167  }
   168  
   169  // NewWorkflowRollbackCommand create workflow rollback command
   170  func NewWorkflowRollbackCommand(_ common.Args, _ cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
   171  	cmd := &cobra.Command{
   172  		Use:     "rollback",
   173  		Short:   "Rollback an application workflow to the latest revision.",
   174  		Long:    "Rollback an application workflow to the latest revision.",
   175  		Example: "vela workflow rollback <application-name>",
   176  		RunE: func(cmd *cobra.Command, args []string) error {
   177  			ctx := context.Background()
   178  			if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
   179  				return err
   180  			}
   181  			return wargs.Operator.Rollback(ctx)
   182  		},
   183  	}
   184  	addNamespaceAndEnvArg(cmd)
   185  	cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
   186  	return cmd
   187  }
   188  
   189  // NewWorkflowLogsCommand create workflow logs command
   190  func NewWorkflowLogsCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
   191  	cmd := &cobra.Command{
   192  		Use:     "logs",
   193  		Short:   "Tail logs for workflow steps",
   194  		Long:    "Tail logs for workflow steps, note that you need to use op.#Logs in step definition to set the log config of the step.",
   195  		Example: "vela workflow logs <workflow-name>",
   196  		RunE: func(cmd *cobra.Command, args []string) error {
   197  			cli, err := c.GetClient()
   198  			if err != nil {
   199  				return err
   200  			}
   201  			ctx := context.Background()
   202  			if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
   203  				return err
   204  			}
   205  			return wargs.printStepLogs(ctx, cli, ioStream)
   206  		},
   207  	}
   208  	cmd.Flags().StringVarP(&wargs.StepName, "step", "s", "", "specify the step name in the workflow")
   209  	cmd.Flags().StringVarP(&wargs.Output, "output", "o", "default", "output format for logs, support: [default, raw, json]")
   210  	cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
   211  	addNamespaceAndEnvArg(cmd)
   212  	return cmd
   213  }
   214  
   215  // NewWorkflowDebugCommand create workflow debug command
   216  func NewWorkflowDebugCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command {
   217  	dOpts := &debugOpts{
   218  		step: wargs.StepName,
   219  	}
   220  	cmd := &cobra.Command{
   221  		Use:     "debug",
   222  		Short:   "Debug workflow steps",
   223  		Long:    "Debug workflow steps",
   224  		Example: "vela workflow debug <workflow-name>",
   225  		PreRun:  wargs.checkDebugMode(),
   226  		RunE: func(cmd *cobra.Command, args []string) error {
   227  			cli, err := c.GetClient()
   228  			if err != nil {
   229  				return err
   230  			}
   231  			pd, err := c.GetPackageDiscover()
   232  			if err != nil {
   233  				return err
   234  			}
   235  			ctx := context.Background()
   236  			if err := wargs.getWorkflowInstance(ctx, cmd, args); err != nil {
   237  				return err
   238  			}
   239  			dOpts.opts = wargs.getWorkflowSteps()
   240  			dOpts.errMap = wargs.ErrMap
   241  			return dOpts.debugWorkflow(ctx, wargs, cli, pd, ioStream)
   242  		},
   243  	}
   244  	cmd.Flags().StringVarP(&wargs.StepName, "step", "s", "", "specify the step name in the workflow")
   245  	cmd.Flags().StringVarP(&dOpts.focus, "focus", "f", "", "specify the focus value to debug, only valid for application with workflow")
   246  	cmd.Flags().StringVarP(&wargs.Type, "type", "t", "", "the type of the resource, support: [app, workflow]")
   247  	addNamespaceAndEnvArg(cmd)
   248  	return cmd
   249  }
   250  
   251  // NewWorkflowListCommand create workflow list command
   252  func NewWorkflowListCommand(c common.Args, ioStream cmdutil.IOStreams, _ *WorkflowArgs) *cobra.Command {
   253  	cmd := &cobra.Command{
   254  		Use:     "list",
   255  		Short:   "List running workflows",
   256  		Long:    "List running workflows",
   257  		Example: "vela workflow list",
   258  		RunE: func(cmd *cobra.Command, args []string) error {
   259  			cli, err := c.GetClient()
   260  			if err != nil {
   261  				return err
   262  			}
   263  			namespace, err := GetFlagNamespaceOrEnv(cmd, c)
   264  			if err != nil {
   265  				return err
   266  			}
   267  			if AllNamespace {
   268  				namespace = ""
   269  			}
   270  			ctx := context.Background()
   271  			return printWorkflowList(ctx, cli, namespace, ioStream)
   272  		},
   273  	}
   274  	cmd.Flags().BoolVarP(&AllNamespace, "all-namespaces", "A", false, "If true, check the specified action in all namespaces.")
   275  	addNamespaceAndEnvArg(cmd)
   276  	return cmd
   277  }
   278  
   279  func printWorkflowList(ctx context.Context, c client.Reader, namespace string, ioStream cmdutil.IOStreams) error {
   280  	table, err := buildWorkflowListTable(ctx, c, namespace)
   281  	if err != nil {
   282  		return err
   283  	}
   284  	ioStream.Info(table.String())
   285  	return nil
   286  }
   287  
   288  func buildWorkflowListTable(ctx context.Context, c client.Reader, namespace string) (*uitable.Table, error) {
   289  	table := newUITable()
   290  	header := []interface{}{"NAME", "TYPE", "PHASE", "START-TIME", "END-TIME"}
   291  	if AllNamespace {
   292  		header = append([]interface{}{"NAMESPACE"}, header...)
   293  	}
   294  	table.AddRow(header...)
   295  	applist := v1beta1.ApplicationList{}
   296  	if err := c.List(ctx, &applist, client.InNamespace(namespace)); err != nil {
   297  		return nil, errors.WithMessage(err, "unable to list application workflows")
   298  	}
   299  
   300  	for _, a := range applist.Items {
   301  		status := a.Status.Workflow
   302  		if a.Status.Workflow != nil {
   303  			if AllNamespace {
   304  				table.AddRow(a.Namespace, a.Name, "Application", status.Phase, status.StartTime, status.EndTime)
   305  			} else {
   306  				table.AddRow(a.Name, "Application", status.Phase, status.StartTime, status.EndTime)
   307  			}
   308  		}
   309  	}
   310  
   311  	wrList := workflowv1alpha1.WorkflowRunList{}
   312  
   313  	if err := c.List(ctx, &wrList, client.InNamespace(namespace)); err != nil {
   314  		return nil, errors.WithMessage(err, "unable to list workflowruns")
   315  	}
   316  
   317  	for _, w := range wrList.Items {
   318  		status := w.Status
   319  		if status.Phase != "" {
   320  			if AllNamespace {
   321  				table.AddRow(w.Namespace, w.Name, "WorkflowRun", status.Phase, status.StartTime, status.EndTime)
   322  			} else {
   323  				table.AddRow(w.Name, "WorkflowRun", status.Phase, status.StartTime, status.EndTime)
   324  			}
   325  		}
   326  	}
   327  	return table, nil
   328  }
   329  
   330  // WorkflowArgs is the args for workflow command
   331  type WorkflowArgs struct {
   332  	Type             string
   333  	Output           string
   334  	ControllerLabels map[string]string
   335  	Operator         wfUtils.WorkflowOperator
   336  	StepOperator     wfUtils.WorkflowStepOperator
   337  	Writer           io.Writer
   338  	Args             common.Args
   339  	StepName         string
   340  	StepID           string
   341  	ErrMap           map[string]string
   342  	App              *v1beta1.Application
   343  	WorkflowRun      *workflowv1alpha1.WorkflowRun
   344  	WorkflowInstance *wfTypes.WorkflowInstance
   345  }
   346  
   347  const (
   348  	instanceTypeApplication string = "app"
   349  	instanceTypeWorkflowRun string = "workflow"
   350  )
   351  
   352  func (w *WorkflowArgs) getWorkflowInstance(ctx context.Context, cmd *cobra.Command, args []string) error {
   353  	if len(args) < 1 {
   354  		return fmt.Errorf("please specify the name of application/workflow")
   355  	}
   356  	name := args[0]
   357  	namespace, err := GetFlagNamespaceOrEnv(cmd, w.Args)
   358  	if err != nil {
   359  		return err
   360  	}
   361  	cli, err := w.Args.GetClient()
   362  	if err != nil {
   363  		return err
   364  	}
   365  	config, err := w.Args.GetConfig()
   366  	if err != nil {
   367  		return err
   368  	}
   369  	config.Wrap(pkgmulticluster.NewTransportWrapper())
   370  	switch w.Type {
   371  	case "":
   372  		app := &v1beta1.Application{}
   373  		if err := cli.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, app); err == nil {
   374  			w.Type = instanceTypeApplication
   375  			w.App = app
   376  		} else {
   377  			wr := &workflowv1alpha1.WorkflowRun{}
   378  			if err := cli.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, wr); err == nil {
   379  				w.Type = instanceTypeWorkflowRun
   380  				w.WorkflowRun = wr
   381  			}
   382  		}
   383  		if w.Type == "" {
   384  			return fmt.Errorf("can't find application or workflowrun %s", name)
   385  		}
   386  	case instanceTypeApplication:
   387  		app := &v1beta1.Application{}
   388  		if err := cli.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, app); err != nil {
   389  			return err
   390  		}
   391  		w.App = app
   392  	case instanceTypeWorkflowRun:
   393  		wr := &workflowv1alpha1.WorkflowRun{}
   394  		if err := cli.Get(ctx, client.ObjectKey{Namespace: namespace, Name: name}, wr); err != nil {
   395  			return err
   396  		}
   397  		w.WorkflowRun = wr
   398  	default:
   399  	}
   400  	return w.generateWorkflowInstance(ctx, cli)
   401  }
   402  
   403  func (w *WorkflowArgs) generateWorkflowInstance(ctx context.Context, cli client.Client) error {
   404  	debug := false
   405  	switch w.Type {
   406  	case instanceTypeApplication:
   407  		if w.App.Status.Workflow == nil {
   408  			return fmt.Errorf("the workflow in application %s is not start", w.App.Name)
   409  		}
   410  		for _, policy := range w.App.Spec.Policies {
   411  			if policy.Type == v1alpha1.DebugPolicyType {
   412  				debug = true
   413  				break
   414  			}
   415  		}
   416  		status := w.App.Status.Workflow
   417  		w.WorkflowInstance = &wfTypes.WorkflowInstance{
   418  			WorkflowMeta: wfTypes.WorkflowMeta{
   419  				Name:      w.App.Name,
   420  				Namespace: w.App.Namespace,
   421  				UID:       w.App.UID,
   422  			},
   423  			Debug: debug,
   424  			Status: workflowv1alpha1.WorkflowRunStatus{
   425  				Phase:          status.Phase,
   426  				Message:        status.Message,
   427  				Suspend:        status.Suspend,
   428  				SuspendState:   status.SuspendState,
   429  				Terminated:     status.Terminated,
   430  				Finished:       status.Finished,
   431  				ContextBackend: status.ContextBackend,
   432  				Steps:          status.Steps,
   433  				StartTime:      status.StartTime,
   434  				EndTime:        status.EndTime,
   435  			},
   436  		}
   437  		if w.App.Spec.Workflow != nil {
   438  			w.WorkflowInstance.Steps = w.App.Spec.Workflow.Steps
   439  		}
   440  		w.Operator = operation.NewApplicationWorkflowOperator(cli, w.Writer, w.App)
   441  		w.StepOperator = operation.NewApplicationWorkflowStepOperator(cli, w.Writer, w.App)
   442  		w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-core"}
   443  	case instanceTypeWorkflowRun:
   444  		var steps []workflowv1alpha1.WorkflowStep
   445  		if w.WorkflowRun.Spec.WorkflowRef != "" {
   446  			workflow := &workflowv1alpha1.Workflow{}
   447  			if err := cli.Get(ctx, client.ObjectKey{Namespace: w.WorkflowRun.Namespace, Name: w.WorkflowRun.Spec.WorkflowRef}, workflow); err != nil {
   448  				return err
   449  			}
   450  			steps = workflow.Steps
   451  		} else {
   452  			steps = w.WorkflowRun.Spec.WorkflowSpec.Steps
   453  		}
   454  		if w.WorkflowRun.Annotations != nil {
   455  			if d, ok := w.WorkflowRun.Annotations[wfTypes.AnnotationWorkflowRunDebug]; ok && d == "true" {
   456  				debug = true
   457  			}
   458  		}
   459  		w.WorkflowInstance = &wfTypes.WorkflowInstance{
   460  			WorkflowMeta: wfTypes.WorkflowMeta{
   461  				Name:      w.WorkflowRun.Name,
   462  				Namespace: w.WorkflowRun.Namespace,
   463  				UID:       w.WorkflowRun.UID,
   464  			},
   465  			Steps:  steps,
   466  			Status: w.WorkflowRun.Status,
   467  			Debug:  debug,
   468  		}
   469  		w.Operator = wfUtils.NewWorkflowRunOperator(cli, w.Writer, w.WorkflowRun)
   470  		w.StepOperator = wfUtils.NewWorkflowRunStepOperator(cli, w.Writer, w.WorkflowRun)
   471  		w.ControllerLabels = map[string]string{"app.kubernetes.io/name": "vela-workflow"}
   472  	default:
   473  		return fmt.Errorf("unknown workflow instance type %s", w.Type)
   474  	}
   475  	return nil
   476  }
   477  
   478  func (w *WorkflowArgs) printStepLogs(ctx context.Context, cli client.Client, ioStreams cmdutil.IOStreams) error {
   479  	if w.StepName == "" {
   480  		if err := w.selectWorkflowStep("Select a step to show logs:"); err != nil {
   481  			return err
   482  		}
   483  	}
   484  	if w.WorkflowInstance.Status.ContextBackend == nil {
   485  		return fmt.Errorf("the workflow context backend is not set")
   486  	}
   487  	logConfig, err := wfUtils.GetLogConfigFromStep(ctx, cli, w.WorkflowInstance.Status.ContextBackend.Name, w.WorkflowInstance.Name, w.WorkflowInstance.Namespace, w.StepName)
   488  	if err != nil {
   489  		return errors.WithMessage(err, fmt.Sprintf("step [%s]", w.StepName))
   490  	}
   491  	if err := selectStepLogSource(logConfig); err != nil {
   492  		return err
   493  	}
   494  	switch {
   495  	case logConfig.Data:
   496  		return w.printResourceLogs(ctx, cli, ioStreams, []wfTypes.Resource{{
   497  			Namespace:     types.DefaultKubeVelaNS,
   498  			LabelSelector: w.ControllerLabels,
   499  		}}, []string{fmt.Sprintf(`stepSessionID="%s"`, w.StepID), fmt.Sprintf("%s/%s", w.WorkflowInstance.Namespace, w.WorkflowInstance.Name), "cue logs"})
   500  	case logConfig.Source != nil:
   501  		if len(logConfig.Source.Resources) > 0 {
   502  			return w.printResourceLogs(ctx, cli, ioStreams, logConfig.Source.Resources, nil)
   503  		}
   504  		if logConfig.Source.URL != "" {
   505  			readCloser, err := wfUtils.GetLogsFromURL(ctx, logConfig.Source.URL)
   506  			if err != nil {
   507  				return err
   508  			}
   509  			//nolint:errcheck
   510  			defer readCloser.Close()
   511  			if _, err := io.Copy(ioStreams.Out, readCloser); err != nil {
   512  				return err
   513  			}
   514  		}
   515  	}
   516  	return nil
   517  }
   518  
   519  func (w *WorkflowArgs) getWorkflowSteps() []string {
   520  	if w.ErrMap == nil {
   521  		w.ErrMap = make(map[string]string)
   522  	}
   523  	stepsKey := make([]string, 0)
   524  	for _, step := range w.WorkflowInstance.Status.Steps {
   525  		stepsKey = append(stepsKey, wrapStepName(step.StepStatus))
   526  		if step.Phase == workflowv1alpha1.WorkflowStepPhaseFailed {
   527  			w.ErrMap[step.Name] = step.Message
   528  		}
   529  		for _, sub := range step.SubStepsStatus {
   530  			stepsKey = append(stepsKey, fmt.Sprintf("  %s", wrapStepName(sub)))
   531  			if sub.Phase == workflowv1alpha1.WorkflowStepPhaseFailed {
   532  				w.ErrMap[step.Name] = sub.Message
   533  			}
   534  		}
   535  	}
   536  	return stepsKey
   537  }
   538  
   539  func (w *WorkflowArgs) selectWorkflowStep(msg string) error {
   540  	stepsKey := w.getWorkflowSteps()
   541  	if len(stepsKey) == 0 {
   542  		return fmt.Errorf("workflow is not start")
   543  	}
   544  
   545  	prompt := &survey.Select{
   546  		Message: msg,
   547  		Options: stepsKey,
   548  	}
   549  	var stepName string
   550  	err := survey.AskOne(prompt, &stepName, survey.WithValidator(survey.Required))
   551  	if err != nil {
   552  		return fmt.Errorf("failed to select step %s: %w", unwrapStepName(w.StepName), err)
   553  	}
   554  	w.StepName = unwrapStepName(stepName)
   555  	w.StepID = unwrapStepID(stepName, w.WorkflowInstance)
   556  	return nil
   557  }
   558  
   559  func selectStepLogSource(logConfig *wfTypes.LogConfig) error {
   560  	var source string
   561  	if logConfig.Data && logConfig.Source != nil {
   562  		prompt := &survey.Select{
   563  			Message: "Select logs from data or source",
   564  			Options: []string{"data", "source"},
   565  		}
   566  		err := survey.AskOne(prompt, &source, survey.WithValidator(survey.Required))
   567  		if err != nil {
   568  			return fmt.Errorf("failed to select %s: %w", source, err)
   569  		}
   570  		if source != "data" {
   571  			logConfig.Data = false
   572  		}
   573  	}
   574  	return nil
   575  }
   576  
   577  func (w *WorkflowArgs) printResourceLogs(ctx context.Context, cli client.Client, ioStreams cmdutil.IOStreams, resources []wfTypes.Resource, filters []string) error {
   578  	pods, err := wfUtils.GetPodListFromResources(ctx, cli, resources)
   579  	if err != nil {
   580  		return err
   581  	}
   582  	podList := make([]querytypes.PodBase, 0)
   583  	for _, pod := range pods {
   584  		podBase := querytypes.PodBase{}
   585  		podBase.Metadata.Name = pod.Name
   586  		podBase.Metadata.Namespace = pod.Namespace
   587  		podList = append(podList, podBase)
   588  	}
   589  	if len(pods) == 0 {
   590  		return errors.New("no pod found")
   591  	}
   592  	var selectPod *querytypes.PodBase
   593  	if len(pods) > 1 {
   594  		selectPod, err = AskToChooseOnePod(podList)
   595  		if err != nil {
   596  			return err
   597  		}
   598  	} else {
   599  		selectPod = &podList[0]
   600  	}
   601  	l := Args{
   602  		Args:   w.Args,
   603  		Output: w.Output,
   604  	}
   605  	return l.printPodLogs(ctx, ioStreams, selectPod, filters)
   606  }
   607  
   608  func (w *WorkflowArgs) checkWorkflowNotComplete() func(cmd *cobra.Command, args []string) {
   609  	return func(cmd *cobra.Command, args []string) {
   610  		if err := w.getWorkflowInstance(context.Background(), cmd, args); err != nil {
   611  			return
   612  		}
   613  		if w.WorkflowInstance.Status.Phase == workflowv1alpha1.WorkflowStateSucceeded {
   614  			cmd.Printf("%s workflow not allowed because application %s is running\n", cmd.Use, args[0])
   615  			os.Exit(1)
   616  		}
   617  	}
   618  }
   619  
   620  func (w *WorkflowArgs) checkDebugMode() func(cmd *cobra.Command, args []string) {
   621  	return func(cmd *cobra.Command, args []string) {
   622  		if err := w.getWorkflowInstance(context.Background(), cmd, args); err != nil {
   623  			return
   624  		}
   625  		if !w.WorkflowInstance.Debug {
   626  			msg := ""
   627  			if w.Type == instanceTypeApplication {
   628  				msg = "please make sure your application have the debug policy, you can add the debug policy by using `vela up -f <app.yaml> --debug"
   629  			} else {
   630  				msg = "please make sure your workflow have the debug annotation [workflowrun.oam.dev/debug:true] then re-run the workflow"
   631  			}
   632  			cmd.Printf("workflow %s is not in debug mode, %s", w.WorkflowInstance.Name, msg)
   633  			os.Exit(1)
   634  		}
   635  	}
   636  }