From c2e3b8517002b30a93244c9298e4fcdc68546918 Mon Sep 17 00:00:00 2001 From: Rick <1450685+LinuxSuRen@users.noreply.github.com> Date: Mon, 27 Sep 2021 14:11:31 +0800 Subject: [PATCH] Support to create a parallel pipeline (#211) --- kubectl-plugin/pipeline/dashboard.go | 204 +++++++++++++++++++++++++++ 1 file changed, 204 insertions(+) create mode 100644 kubectl-plugin/pipeline/dashboard.go diff --git a/kubectl-plugin/pipeline/dashboard.go b/kubectl-plugin/pipeline/dashboard.go new file mode 100644 index 0000000..05734b2 --- /dev/null +++ b/kubectl-plugin/pipeline/dashboard.go @@ -0,0 +1,204 @@ +package pipeline + +import ( + "context" + "fmt" + "github.com/gdamore/tcell/v2" + "github.com/kubesphere-sigs/ks/kubectl-plugin/common" + "github.com/kubesphere-sigs/ks/kubectl-plugin/types" + "github.com/rivo/tview" + "github.com/spf13/cobra" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +type dashboardOption struct { + client dynamic.Interface + clientset *kubernetes.Clientset + restClient *rest.RESTClient + + namespace string + pipeline string + + header *tview.TextView + footer *tview.TextView + app *tview.Application + pipelineListView *tview.Table +} + +func newDashboardCmd() (cmd *cobra.Command) { + opt := &dashboardOption{} + cmd = &cobra.Command{ + Use: "dashboard", + Aliases: []string{"dash"}, + RunE: opt.runE, + } + return +} + +func (o *dashboardOption) runE(cmd *cobra.Command, args []string) (err error) { + o.app = tview.NewApplication() + o.client = common.GetDynamicClient(cmd.Root().Context()) + o.clientset = common.GetClientset(cmd.Root().Context()) + o.restClient = common.GetRestClient(cmd.Root().Context()) + + newPrimitive := func(text string) *tview.TextView { + return tview.NewTextView(). + SetTextAlign(tview.AlignCenter). + SetText(text) + } + + grid := tview.NewGrid() + grid.SetRows(3, 0, 3) + grid.SetColumns(30, 0, 30) + grid.SetBorder(true) + o.header = newPrimitive("header") + o.footer = newPrimitive("footer") + grid.AddItem(o.header, 0, 0, 1, 3, 0, 0, false) + grid.AddItem(o.footer, 2, 0, 1, 3, 0, 0, false) + grid.AddItem(o.createNamespaceList(), 1, 0, 1, 1, 0, 100, true) + grid.AddItem(o.createPipelineList(), 1, 1, 1, 2, 0, 100, false) + if err = o.app.SetRoot(grid, true).Run(); err != nil { + panic(err) + } + return +} + +func (o *dashboardOption) createPipelineList() (listView tview.Primitive) { + table := tview.NewTable() + table.SetBorder(true).SetTitle("pipelines") + table.SetSelectable(true, false).Select(1, 0).SetFixed(1, 0) + table.SetBorderPadding(0, 0, 1, 1) + o.pipelineListView = table + listView = table + table.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + o.header.Clear() + o.header.SetText("(r) Run a Pipeline, (v) List the PipelineRuns") + switch key := event.Rune(); key { + case 'v': + o.listPipelineRuns(0, o.namespace, "", 0) + case 'r': + run := &pipelineRunOpt{ + client: o.client, + } + row, col := table.GetSelection() + cell := table.GetCell(row, col) + pipeline := cell.Text + if err := run.triggerPipeline(o.namespace, pipeline, nil); err != nil { + o.footer.SetText(err.Error()) + } + o.listPipelineRuns(0, o.namespace, "", 0) + } + if event.Key() == tcell.KeyESC { + o.listPipelines(0, o.namespace, "", 0) + } + return event + }) + return +} + +func (o *dashboardOption) listPipelineRuns(index int, mainText string, secondaryText string, shortcut rune) { + o.pipelineListView.Clear() + o.pipelineListView.SetTitle("PipelineRuns") + o.getTable(mainText, "pipelineruns", o.pipelineListView) +} + +func (o *dashboardOption) getTable(ns, kind string, table *tview.Table) (err error) { + tableData := &metav1beta1.Table{} + table.Clear() + table.SetTitle(fmt.Sprintf("%s(%s)[%d]", kind, ns, 0)) + listOpt := &metav1.ListOptions{} + if kind == "pipelineruns" { + listOpt.LabelSelector = fmt.Sprintf("devops.kubesphere.io/pipeline=%s", o.pipeline) + } + + if err = o.restClient.Get().Namespace(ns).Resource(kind). + VersionedParams(listOpt, metav1.ParameterCodec). + SetHeader("Accept", "application/json;as=Table;v=v1beta1;g=meta.k8s.io"). + Do(context.TODO()).Into(tableData); err != nil { + return + } else { + for i, col := range tableData.ColumnDefinitions { + table.SetCellSimple(0, i, col.Name) + } + for i, row := range tableData.Rows { + for j, cell := range row.Cells { + table.SetCellSimple(i+1, j, fmt.Sprintf("%v", cell)) + } + } + table.SetTitle(fmt.Sprintf("%s(%s)[%d]", kind, ns, len(tableData.Rows))) + } + return +} + +func (o *dashboardOption) listPipelines(index int, mainText string, secondaryText string, shortcut rune) { + o.pipelineListView.Clear() + o.namespace = mainText + o.pipelineListView.SetTitle("Pipelines") + o.getTable(mainText, "pipelines", o.pipelineListView) + o.pipelineListView.SetSelectionChangedFunc(func(row, column int) { + if row == 0 { + o.pipelineListView.Select(1, 0) + } + cell := o.pipelineListView.GetCell(row, column) + o.pipeline = cell.Text + }) +} + +func (o *dashboardOption) createNamespaceList() (listView tview.Primitive) { + list := tview.NewList() + list.SetBorder(true).SetTitle("namespaces") + go func() { + if watchEvent, err := o.client.Resource(types.GetNamespaceSchema()).Watch(context.TODO(), metav1.ListOptions{ + LabelSelector: "kubesphere.io/devopsproject", + }); err == nil { + for event := range watchEvent.ResultChan() { + switch event.Type { + case watch.Added: + unss := event.Object.(*unstructured.Unstructured) + ss := &corev1.Namespace{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unss.Object, ss); err == nil { + list.AddItem(ss.Name, "", 0, nil) + } + case watch.Deleted: + for i := 0; i < list.GetItemCount(); i++ { + name, _ := list.GetItemText(i) + unss := event.Object.(*unstructured.Unstructured) + ss := &corev1.Namespace{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(unss.Object, ss); err == nil { + if name == ss.Name { + list.RemoveItem(i) + break + } + } + } + } + o.app.Draw() + } + } + }() + list.SetChangedFunc(o.listPipelines) + o.app.SetInputCapture(func(event *tcell.EventKey) *tcell.EventKey { + switch key := event.Rune(); key { + case 'j': + event = tcell.NewEventKey(tcell.KeyDown, key, tcell.ModNone) + case 'k': + event = tcell.NewEventKey(tcell.KeyUp, key, tcell.ModNone) + case 'l': + o.app.SetFocus(o.pipelineListView) + case 'h': + o.app.SetFocus(list) + } + return event + }) + o.app.SetFocus(list) + listView = list + return +}