Skip to content

Commit

Permalink
Feat: add workflow rollback cli (#2795)
Browse files Browse the repository at this point in the history
* Feat: add workflow rollback cli

* fix ci
  • Loading branch information
FogDong committed Nov 24, 2021
1 parent 216a95a commit 6f90155
Show file tree
Hide file tree
Showing 2 changed files with 196 additions and 0 deletions.
62 changes: 62 additions & 0 deletions references/cli/workflow.go
Expand Up @@ -21,6 +21,7 @@ import (
"fmt"

"github.com/spf13/cobra"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/oam-dev/kubevela/apis/core.oam.dev/v1beta1"
Expand All @@ -45,6 +46,7 @@ func NewWorkflowCommand(c common.Args, ioStreams cmdutil.IOStreams) *cobra.Comma
NewWorkflowResumeCommand(c, ioStreams),
NewWorkflowTerminateCommand(c, ioStreams),
NewWorkflowRestartCommand(c, ioStreams),
NewWorkflowRollbackCommand(c, ioStreams),
)
return cmd
}
Expand Down Expand Up @@ -223,6 +225,47 @@ func NewWorkflowRestartCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra
return cmd
}

// NewWorkflowRollbackCommand create workflow rollback command
func NewWorkflowRollbackCommand(c common.Args, ioStream cmdutil.IOStreams) *cobra.Command {
cmd := &cobra.Command{
Use: "rollback",
Short: "Rollback an application workflow to the latest revision",
Long: "Rollback an application workflow to the latest revision",
Example: "vela workflow rollback <application-name>",
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) < 1 {
return fmt.Errorf("must specify application name")
}
namespace, err := GetFlagNamespaceOrEnv(cmd, c)
if err != nil {
return err
}
app, err := appfile.LoadApplication(namespace, args[0], c)
if err != nil {
return err
}
if app.Spec.Workflow == nil {
return fmt.Errorf("the application must have workflow")
}
if app.Status.Workflow != nil && !app.Status.Workflow.Terminated && !app.Status.Workflow.Suspend && !app.Status.Workflow.Finished {
return fmt.Errorf("can not rollback a running workflow")
}
kubecli, err := c.GetClient()
if err != nil {
return err
}

err = rollbackWorkflow(kubecli, app)
if err != nil {
return err
}
return nil
},
}
addNamespaceArg(cmd)
return cmd
}

func suspendWorkflow(kubecli client.Client, app *v1beta1.Application) error {
// set the workflow suspend to true
app.Status.Workflow.Suspend = true
Expand Down Expand Up @@ -270,3 +313,22 @@ func restartWorkflow(kubecli client.Client, app *v1beta1.Application) error {
fmt.Printf("Successfully restart workflow: %s\n", app.Name)
return nil
}

func rollbackWorkflow(kubecli client.Client, app *v1beta1.Application) error {
if app.Status.LatestRevision == nil || app.Status.LatestRevision.Name == "" {
return fmt.Errorf("the latest revision is not set: %s", app.Name)
}
// get the last revision
revision := &v1beta1.ApplicationRevision{}
if err := kubecli.Get(context.TODO(), k8stypes.NamespacedName{Name: app.Status.LatestRevision.Name, Namespace: app.Namespace}, revision); err != nil {
return fmt.Errorf("failed to get the latest revision: %w", err)
}

app.Spec = revision.Spec.Application.Spec
if err := kubecli.Status().Update(context.TODO(), app); err != nil {
return err
}

fmt.Printf("Successfully rollback workflow to the latest revision: %s\n", app.Name)
return nil
}
134 changes: 134 additions & 0 deletions references/cli/workflow_test.go
Expand Up @@ -429,3 +429,137 @@ func TestWorkflowRestart(t *testing.T) {
})
}
}

func TestWorkflowRollback(t *testing.T) {
c := initArgs()
ioStream := cmdutil.IOStreams{In: os.Stdin, Out: os.Stdout, ErrOut: os.Stderr}
ctx := context.TODO()

testCases := map[string]struct {
app *v1beta1.Application
revision *v1beta1.ApplicationRevision
expectedErr error
}{
"no app name specified": {
expectedErr: fmt.Errorf("must specify application name"),
},
"no workflow in app": {
app: &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "no-workflow",
Namespace: "default",
},
},
expectedErr: fmt.Errorf("the application must have workflow"),
},
"workflow running": {
app: &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "workflow-not-running",
Namespace: "default",
},
Spec: workflowSpec,
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{
Suspend: false,
Terminated: false,
Finished: false,
},
},
},
expectedErr: fmt.Errorf("can not rollback a running workflow"),
},
"invalid revision": {
app: &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "invalid-revision",
Namespace: "default",
},
Spec: workflowSpec,
Status: common.AppStatus{
Workflow: &common.WorkflowStatus{
Suspend: true,
},
},
},
expectedErr: fmt.Errorf("the latest revision is not set: invalid-revision"),
},
"rollback successfully": {
app: &v1beta1.Application{
ObjectMeta: metav1.ObjectMeta{
Name: "workflow",
Namespace: "test",
},
Spec: workflowSpec,
Status: common.AppStatus{
LatestRevision: &common.Revision{
Name: "revision-v1",
},
Workflow: &common.WorkflowStatus{
Terminated: true,
},
},
},
revision: &v1beta1.ApplicationRevision{
ObjectMeta: metav1.ObjectMeta{
Name: "revision-v1",
Namespace: "test",
},
Spec: v1beta1.ApplicationRevisionSpec{
Application: v1beta1.Application{
Spec: v1beta1.ApplicationSpec{
Components: []common.ApplicationComponent{{
Name: "revision-component",
Type: "worker",
Properties: &runtime.RawExtension{Raw: []byte(`{"cmd":["sleep","1000"],"image":"busybox"}`)},
}},
},
},
},
},
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
r := require.New(t)
cmd := NewWorkflowRollbackCommand(c, ioStream)
initCommand(cmd)

if tc.app != nil {
err := c.Client.Create(ctx, tc.app)
r.NoError(err)

if tc.app.Namespace != corev1.NamespaceDefault {
err := c.Client.Create(ctx, &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
Name: tc.app.Namespace,
},
})
r.NoError(err)
cmd.SetArgs([]string{tc.app.Name, "-n", tc.app.Namespace})
} else {
cmd.SetArgs([]string{tc.app.Name})
}
}
if tc.revision != nil {
err := c.Client.Create(ctx, tc.revision)
r.NoError(err)
}
err := cmd.Execute()
if tc.expectedErr != nil {
r.Equal(tc.expectedErr, err)
return
}
r.NoError(err)

wf := &v1beta1.Application{}
err = c.Client.Get(ctx, types.NamespacedName{
Namespace: tc.app.Namespace,
Name: tc.app.Name,
}, wf)
r.NoError(err)
r.Equal(wf.Spec.Components[0].Name, "revision-component")
})
}
}

0 comments on commit 6f90155

Please sign in to comment.