Skip to content

Commit

Permalink
Add a dashboard for pipelines (#213)
Browse files Browse the repository at this point in the history
  • Loading branch information
LinuxSuRen committed Sep 28, 2021
1 parent 0b61bfb commit 70ef6ed
Show file tree
Hide file tree
Showing 6 changed files with 303 additions and 22 deletions.
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ require (
github.com/Masterminds/semver v1.5.0 // indirect
github.com/Masterminds/sprig v2.22.0+incompatible
github.com/Pallinder/go-randomdata v1.2.0
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1
github.com/huandu/xstrings v1.3.2 // indirect
github.com/linuxsuren/cobra-extension v0.0.10
github.com/linuxsuren/go-cli-alias v0.0.7
github.com/linuxsuren/http-downloader v0.0.35
github.com/mitchellh/copystructure v1.1.1 // indirect
github.com/rivo/tview v0.0.0-20210923051754-2cb20002bc4c
github.com/spf13/cobra v1.2.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
Expand Down
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,10 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1 h1:QqwPZCwh/k1uYqq6uXSb9TRDhTkfQbO80v8zhnIe5zM=
github.com/gdamore/tcell/v2 v2.4.1-0.20210905002822-f057f0a857a1/go.mod h1:Az6Jt+M5idSED2YPGtwnfJV0kXohgdCBPmHGSYc1r04=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
Expand Down Expand Up @@ -368,6 +372,8 @@ github.com/linuxsuren/http-downloader v0.0.2-0.20201207132639-19888a6beaec/go.mo
github.com/linuxsuren/http-downloader v0.0.6/go.mod h1:xxgh2OE7WGL9TwDE9L8Gh7Lqq9fFPuHbh5tofUitEfE=
github.com/linuxsuren/http-downloader v0.0.35 h1:hZ+ctSWdhSej5C5nbpHKYEFCh72q30qwfvAin+XYtz4=
github.com/linuxsuren/http-downloader v0.0.35/go.mod h1:FgFOdXaKLLYJDUiI39BpTteyhfeq3KRmlQNwct+uEPc=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.5 h1:b6kJs+EmPFMYGkow9GiUyCyOvIwYetYJ3fSaWak/Gls=
Expand All @@ -387,6 +393,8 @@ github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNx
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b h1:j7+1HpAFS1zy5+Q4qx1fWh90gTKwiN4QCGoY9TWyyO4=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
Expand Down Expand Up @@ -459,6 +467,10 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rivo/tview v0.0.0-20210923051754-2cb20002bc4c h1:ye4bWm8SafYmr0DADOKSfeVZ1Swzm9aLW+baCOcHDWE=
github.com/rivo/tview v0.0.0-20210923051754-2cb20002bc4c/go.mod h1:WIfMkQNY+oq/mWwtsjOYHIZBuwthioY2srOmljJkTnk=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
Expand Down Expand Up @@ -715,6 +727,7 @@ golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210309074719-68d13333faf2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -725,6 +738,8 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56 h1:b8jxX3zqjpqb2LklXPzKSGJhzyxCOZSz8ncv8Nv+y7w=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
25 changes: 22 additions & 3 deletions kubectl-plugin/common/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,13 @@ package common

import (
"context"
"github.com/kubesphere-sigs/ks/kubectl-plugin/types"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"

"fmt"
"k8s.io/cli-runtime/pkg/genericclioptions"
Expand All @@ -23,14 +28,21 @@ func GetClient() (client dynamic.Interface, clientSet *kubernetes.Clientset, err
// GetDynamicClient gets the dynamic k8s client from context
func GetDynamicClient(ctx context.Context) (client dynamic.Interface) {
factory := ctx.Value(ClientFactory{})
client, _ = factory.(*ClientFactory).GetClient()
client, _, _ = factory.(*ClientFactory).GetClient()
return
}

// GetClientset gets the clientset of k8s
func GetClientset(ctx context.Context) (clientset *kubernetes.Clientset) {
factory := ctx.Value(ClientFactory{})
_, clientset = factory.(*ClientFactory).GetClient()
_, clientset, _ = factory.(*ClientFactory).GetClient()
return
}

// GetRestClient returns the restClient of the Kubernetes
func GetRestClient(ctx context.Context) (client *rest.RESTClient) {
factory := ctx.Value(ClientFactory{})
_, _, client = factory.(*ClientFactory).GetClient()
return
}

Expand All @@ -42,7 +54,7 @@ type ClientFactory struct {
}

// GetClient returns k8s client
func (c *ClientFactory) GetClient() (client dynamic.Interface, clientSet *kubernetes.Clientset) {
func (c *ClientFactory) GetClient() (client dynamic.Interface, clientSet *kubernetes.Clientset, restClient *rest.RESTClient) {
KubernetesConfigFlags := genericclioptions.NewConfigFlags(false)
if c.context != "" {
KubernetesConfigFlags.Context = &c.context
Expand All @@ -56,6 +68,13 @@ func (c *ClientFactory) GetClient() (client dynamic.Interface, clientSet *kubern
if err != nil {
fmt.Println(err)
}
crdConfig := *config
crdConfig.ContentConfig.GroupVersion = &schema.GroupVersion{Group: types.GetPipelineSchema().Group,
Version: types.GetPipelineSchema().Version}
crdConfig.APIPath = "/apis"
crdConfig.NegotiatedSerializer = serializer.NewCodecFactory(scheme.Scheme)
crdConfig.UserAgent = rest.DefaultKubernetesUserAgent()
restClient, _ = rest.UnversionedRESTClientFor(&crdConfig)
}
return
}
Expand Down
239 changes: 239 additions & 0 deletions kubectl-plugin/pipeline/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
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"
v1 "k8s.io/api/apps/v1"
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.Table
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 = tview.NewTable()
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)
go func() {
o.getComponentsInfo()
}()
if err = o.app.SetRoot(grid, true).Run(); err != nil {
panic(err)
}
return
}

func (o *dashboardOption) getComponentsInfo() {
// TODO consider reading the namespace from somewhere
if watchEvent, err := o.clientset.AppsV1().Deployments("kubesphere-devops-system").Watch(context.TODO(), metav1.ListOptions{
LabelSelector: "app.kubernetes.io/instance=devops",
}); err == nil {
for event := range watchEvent.ResultChan() {
deploy := event.Object.(*v1.Deployment)
updateTable(o.footer, deploy.Name, deploy.Name,
fmt.Sprintf("%d/%d", deploy.Status.ReadyReplicas, deploy.Status.Replicas), deploy.Spec.Template.Spec.Containers[0].Image)
}
}
}

func updateTable(table *tview.Table, name string, values ...string) {
rowCount := table.GetRowCount()
found := false
for i := 0; i < rowCount; i++ {
cell := table.GetCell(i, 0)
if cell.Text == name {
for j, val := range values {
table.SetCellSimple(i, j, val)
}
found = true
break
}
}

if !found {
rowCount++
for j, val := range values {
table.SetCellSimple(rowCount, j, val)
}
}
}

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
_ = run.triggerPipeline(o.namespace, pipeline, nil)
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 {
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
}
1 change: 1 addition & 0 deletions kubectl-plugin/pipeline/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func NewPipelineCmd(client dynamic.Interface) (cmd *cobra.Command) {
newPipelineViewCmd(client),
newPipelineCreateCmd(client),
newPipelineRunCmd(),
newDashboardCmd(),
newGCCmd(client))
return
}
Expand Down
Loading

0 comments on commit 70ef6ed

Please sign in to comment.