From a07acc02c93b574aa09db9ba04c26802a1c97b04 Mon Sep 17 00:00:00 2001 From: Anoop Gopalakrishnan <2038273+anoop2811@users.noreply.github.com> Date: Tue, 19 Sep 2023 19:19:08 -0700 Subject: [PATCH] Feat: Add command to list all workflows in vela-cli (#6326) - fixes #6326 Signed-off-by: Muralicharan Gurumoorthy Co-authored-by: Muralicharan Gurumoorthy --- pkg/utils/common/common.go | 3 + references/cli/workflow.go | 83 ++++++++++++++++- references/cli/workflow_test.go | 154 ++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 1 deletion(-) diff --git a/pkg/utils/common/common.go b/pkg/utils/common/common.go index ae852c629..a9eb7c612 100644 --- a/pkg/utils/common/common.go +++ b/pkg/utils/common/common.go @@ -63,6 +63,8 @@ import ( terraformapiv1 "github.com/oam-dev/terraform-controller/api/v1beta1" terraformapi "github.com/oam-dev/terraform-controller/api/v1beta2" + workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" + oamcore "github.com/oam-dev/kubevela/apis/core.oam.dev" "github.com/oam-dev/kubevela/apis/types" velacue "github.com/oam-dev/kubevela/pkg/cue" @@ -93,6 +95,7 @@ func init() { _ = metricsV1beta1api.AddToScheme(Scheme) _ = kruisev1alpha1.AddToScheme(Scheme) _ = gatewayv1beta1.AddToScheme(Scheme) + _ = workflowv1alpha1.AddToScheme(Scheme) // +kubebuilder:scaffold:scheme } diff --git a/references/cli/workflow.go b/references/cli/workflow.go index 6f7e79a94..e870d7a3a 100644 --- a/references/cli/workflow.go +++ b/references/cli/workflow.go @@ -23,12 +23,13 @@ import ( "os" "github.com/AlecAivazis/survey/v2" + "github.com/gosuri/uitable" + workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" "github.com/pkg/errors" "github.com/spf13/cobra" "sigs.k8s.io/controller-runtime/pkg/client" pkgmulticluster "github.com/kubevela/pkg/multicluster" - workflowv1alpha1 "github.com/kubevela/workflow/api/v1alpha1" wfTypes "github.com/kubevela/workflow/pkg/types" wfUtils "github.com/kubevela/workflow/pkg/utils" @@ -65,6 +66,7 @@ func NewWorkflowCommand(c common.Args, order string, ioStreams cmdutil.IOStreams NewWorkflowRollbackCommand(c, ioStreams, wargs), NewWorkflowLogsCommand(c, ioStreams, wargs), NewWorkflowDebugCommand(c, ioStreams, wargs), + NewWorkflowListCommand(c, ioStreams, wargs), ) return cmd } @@ -246,6 +248,85 @@ func NewWorkflowDebugCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *W return cmd } +// NewWorkflowListCommand create workflow list command +func NewWorkflowListCommand(c common.Args, ioStream cmdutil.IOStreams, wargs *WorkflowArgs) *cobra.Command { + cmd := &cobra.Command{ + Use: "list", + Short: "List running workflows", + Long: "List running workflows", + Example: "vela workflow list", + RunE: func(cmd *cobra.Command, args []string) error { + cli, err := c.GetClient() + if err != nil { + return err + } + namespace, err := GetFlagNamespaceOrEnv(cmd, c) + if err != nil { + return err + } + if AllNamespace { + namespace = "" + } + ctx := context.Background() + return printWorkflowList(ctx, cli, namespace, ioStream) + }, + } + cmd.Flags().BoolVarP(&AllNamespace, "all-namespaces", "A", false, "If true, check the specified action in all namespaces.") + addNamespaceAndEnvArg(cmd) + return cmd +} + +func printWorkflowList(ctx context.Context, c client.Reader, namespace string, ioStream cmdutil.IOStreams) error { + table, err := buildWorkflowListTable(ctx, c, namespace) + if err != nil { + return err + } + ioStream.Info(table.String()) + return nil +} + +func buildWorkflowListTable(ctx context.Context, c client.Reader, namespace string) (*uitable.Table, error) { + table := newUITable() + header := []interface{}{"NAME", "TYPE", "PHASE", "START-TIME", "END-TIME"} + if AllNamespace { + header = append([]interface{}{"NAMESPACE"}, header...) + } + table.AddRow(header...) + applist := v1beta1.ApplicationList{} + if err := c.List(ctx, &applist, client.InNamespace(namespace)); err != nil { + return nil, errors.WithMessage(err, "unable to list application workflows") + } + + for _, a := range applist.Items { + status := a.Status.Workflow + if a.Status.Workflow != nil { + if AllNamespace { + table.AddRow(a.Namespace, a.Name, "Application", status.Phase, status.StartTime, status.EndTime) + } else { + table.AddRow(a.Name, "Application", status.Phase, status.StartTime, status.EndTime) + } + } + } + + wrList := workflowv1alpha1.WorkflowRunList{} + + if err := c.List(ctx, &wrList, client.InNamespace(namespace)); err != nil { + return nil, errors.WithMessage(err, "unable to list workflowruns") + } + + for _, w := range wrList.Items { + status := w.Status + if status.Phase != "" { + if AllNamespace { + table.AddRow(w.Namespace, w.Name, "WorkflowRun", status.Phase, status.StartTime, status.EndTime) + } else { + table.AddRow(w.Name, "WorkflowRun", status.Phase, status.StartTime, status.EndTime) + } + } + } + return table, nil +} + // WorkflowArgs is the args for workflow command type WorkflowArgs struct { Type string diff --git a/references/cli/workflow_test.go b/references/cli/workflow_test.go index cdc0874bc..1a843fa49 100644 --- a/references/cli/workflow_test.go +++ b/references/cli/workflow_test.go @@ -17,10 +17,13 @@ limitations under the License. package cli import ( + "bytes" "context" "fmt" "os" + "strings" "testing" + "time" "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" @@ -818,3 +821,154 @@ func TestWorkflowRollback(t *testing.T) { }) } } + +func TestWorkflowList(t *testing.T) { + c := initArgs() + buf := new(bytes.Buffer) + ioStream := cmdutil.IOStreams{In: os.Stdin, Out: buf, ErrOut: os.Stderr} + ctx := context.TODO() + testCases := map[string]struct { + workflows []interface{} + apps []*v1beta1.Application + workflowRuns []*workflowv1alpha1.WorkflowRun + expectedErr error + namespace string + expectAppListSize int + }{ + "specified all namespaces flag": { + workflows: []interface{}{ + &v1beta1.Application{ + ObjectMeta: metav1.ObjectMeta{ + Name: "app-workflow", + Namespace: "some-ns", + }, + Spec: workflowSpec, + Status: common.AppStatus{ + LatestRevision: &common.Revision{ + Name: "revision-v1", + }, + Workflow: &common.WorkflowStatus{ + Terminated: true, + Phase: workflowv1alpha1.WorkflowStateInitializing, + StartTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)), + EndTime: metav1.NewTime(time.Now()), + }, + }, + }, + &workflowv1alpha1.WorkflowRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "workflow-run", + Namespace: "some-ns", + }, + Status: workflowv1alpha1.WorkflowRunStatus{ + Phase: workflowv1alpha1.WorkflowStateExecuting, + StartTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)), + EndTime: metav1.NewTime(time.Now()), + }, + }, + }, + expectedErr: nil, + }, + "specified namespace flag": { + workflows: []interface{}{ + &workflowv1alpha1.WorkflowRun{ + ObjectMeta: metav1.ObjectMeta{ + Name: "workflow-run-1", + Namespace: "test", + }, + Status: workflowv1alpha1.WorkflowRunStatus{ + Phase: workflowv1alpha1.WorkflowStateExecuting, + StartTime: metav1.NewTime(time.Now().Add(-10 * time.Minute)), + EndTime: metav1.NewTime(time.Now()), + }, + }, + }, + namespace: "test", + expectedErr: nil, + }, + } + for name, tc := range testCases { + t.Run(name, func(t *testing.T) { + r := require.New(t) + buf.Reset() + cmd := NewWorkflowListCommand(c, ioStream, &WorkflowArgs{Args: c, Writer: ioStream.Out}) + initCommand(cmd) + // clean up the arguments before start + cmd.SetArgs([]string{}) + client, err := c.GetClient() + r.NoError(err) + if tc.workflows != nil && len(tc.workflows) > 0 { + for _, w := range tc.workflows { + if workflow, ok := w.(*workflowv1alpha1.WorkflowRun); ok { + err := client.Create(ctx, workflow) + r.NoError(err) + } else if app, ok := w.(*v1beta1.Application); ok { + err := client.Create(ctx, app) + r.NoError(err) + } + } + } + args := []string{} + if tc.namespace == "" { + args = append(args, "-A") + } else { + args = append(args, "-n", tc.namespace) + } + + cmd.SetArgs(args) + err = cmd.Execute() + + lines := strings.Split(strings.TrimSpace(buf.String()), "\n") + + headerFields := strings.Fields(lines[0]) + if tc.namespace == "" { + r.Equal(headerFields[0], "NAMESPACE") + + } else { + r.Equal(headerFields[0], "NAME") + } + offset := 0 + for i, line := range lines[1:] { + fields := strings.Fields(line) + if workflow, ok := tc.workflows[i].(*workflowv1alpha1.WorkflowRun); ok { + if tc.namespace == "" { + r.Equal(fields[0], workflow.Namespace) + offset = offset + 1 + } else if tc.namespace == workflow.Namespace { + r.Equal(fields[0+offset], workflow.Name) + r.Equal(fields[1+offset], "WorkflowRun") + r.Equal(fields[2+offset], string(workflow.Status.Phase)) + r.Equal(fields[3+offset], workflow.Status.StartTime.Format("2006-01-02")) + r.Equal(fields[4+offset], workflow.Status.StartTime.Format("15:04:05")) + //skipping a few fields due to the format including timezone information + r.Equal(fields[7+offset], workflow.Status.EndTime.Format("2006-01-02")) + r.Equal(fields[8+offset], workflow.Status.EndTime.Format("15:04:05")) + } + } else if app, ok := tc.workflows[i].(*v1beta1.Application); ok { + offset = 0 + if tc.namespace == "" { + r.Equal(fields[0+offset], app.Namespace) + offset = offset + 1 + } else if tc.namespace == app.Namespace { + r.Equal(fields[0+offset], app.Name) + r.Equal(fields[1+offset], "Application") + r.Equal(fields[2+offset], string(app.Status.Workflow.Phase)) + r.Equal(fields[3+offset], app.Status.Workflow.StartTime.Format("2006-01-02")) + r.Equal(fields[4+offset], app.Status.Workflow.StartTime.Format("15:04:05")) + //skipping a few fields due to the format including timezone information + r.Equal(fields[7+offset], app.Status.Workflow.EndTime.Format("2006-01-02")) + r.Equal(fields[8+offset], app.Status.Workflow.EndTime.Format("15:04:05")) + } + } + + } + + if tc.expectedErr != nil { + r.Equal(tc.expectedErr, err) + return + } + r.NoError(err) + + }) + } +}