From b246ca0752877c6aca7fdf1b7c54822c89629aab Mon Sep 17 00:00:00 2001 From: Michael Cristina Date: Tue, 8 Oct 2019 17:00:51 -0500 Subject: [PATCH] feat: Add action to restart rollout of dp, ds, sts --- internal/k8s/dp.go | 18 +++++++++ internal/k8s/ds.go | 18 +++++++++ internal/k8s/sts.go | 18 +++++++++ internal/resource/base.go | 5 +++ internal/resource/dp.go | 9 +++++ internal/resource/ds.go | 8 ++++ internal/resource/sts.go | 9 +++++ internal/views/dp.go | 10 ++++- internal/views/ds.go | 8 +++- internal/views/restartable_resource.go | 52 ++++++++++++++++++++++++++ internal/views/sts.go | 6 ++- 11 files changed, 156 insertions(+), 5 deletions(-) create mode 100644 internal/views/restartable_resource.go diff --git a/internal/k8s/dp.go b/internal/k8s/dp.go index e541630c..ecf32501 100644 --- a/internal/k8s/dp.go +++ b/internal/k8s/dp.go @@ -2,6 +2,8 @@ package k8s import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubectl/pkg/polymorphichelpers" ) // Deployment represents a Kubernetes Deployment. @@ -54,3 +56,19 @@ func (d *Deployment) Scale(ns, n string, replicas int32) error { _, err = d.DialOrDie().AppsV1().Deployments(ns).UpdateScale(n, scale) return err } + +// Restart a Deployment rollout. +func (d *Deployment) Restart(ns, n string) error { + + dp, err := d.DialOrDie().AppsV1().Deployments(ns).Get(n, metav1.GetOptions{}) + if err != nil { + return err + } + update, err := polymorphichelpers.ObjectRestarterFn(dp) + if err != nil { + return err + } + + _, err = d.DialOrDie().AppsV1().Deployments(ns).Patch(dp.Name, types.StrategicMergePatchType, update) + return err +} diff --git a/internal/k8s/ds.go b/internal/k8s/ds.go index b72624f8..fe41de62 100644 --- a/internal/k8s/ds.go +++ b/internal/k8s/ds.go @@ -2,6 +2,8 @@ package k8s import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubectl/pkg/polymorphichelpers" ) // DaemonSet represents a Kubernetes DaemonSet @@ -48,3 +50,19 @@ func (d *DaemonSet) Delete(ns, n string, cascade, force bool) error { PropagationPolicy: &p, }) } + +// Restart a DaemonSet rollout. +func (d *DaemonSet) Restart(ns, n string) error { + + ds, err := d.DialOrDie().AppsV1().DaemonSets(ns).Get(n, metav1.GetOptions{}) + if err != nil { + return err + } + update, err := polymorphichelpers.ObjectRestarterFn(ds) + if err != nil { + return err + } + + _, err = d.DialOrDie().AppsV1().DaemonSets(ns).Patch(ds.Name, types.StrategicMergePatchType, update) + return err +} diff --git a/internal/k8s/sts.go b/internal/k8s/sts.go index 04d0f130..9157675a 100644 --- a/internal/k8s/sts.go +++ b/internal/k8s/sts.go @@ -2,6 +2,8 @@ package k8s import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/kubectl/pkg/polymorphichelpers" ) // StatefulSet manages a Kubernetes StatefulSet. @@ -60,3 +62,19 @@ func (s *StatefulSet) Scale(ns, n string, replicas int32) error { _, err = s.DialOrDie().AppsV1().StatefulSets(ns).UpdateScale(n, scale) return err } + +// Restart a StatefulSet rollout. +func (s *StatefulSet) Restart(ns, n string) error { + + sts, err := s.DialOrDie().AppsV1().StatefulSets(ns).Get(n, metav1.GetOptions{}) + if err != nil { + return err + } + update, err := polymorphichelpers.ObjectRestarterFn(sts) + if err != nil { + return err + } + + _, err = s.DialOrDie().AppsV1().StatefulSets(ns).Patch(sts.Name, types.StrategicMergePatchType, update) + return err +} diff --git a/internal/resource/base.go b/internal/resource/base.go index 2274dac3..b765c6f7 100644 --- a/internal/resource/base.go +++ b/internal/resource/base.go @@ -35,6 +35,11 @@ type ( Scale(ns string, name string, replicas int32) error } + // Restartable represents a rollout restartable Kubernetes resource. + Restartable interface { + Restart(ns string, name string) error + } + // Connection represents a Kubenetes apiserver connection. Connection k8s.Connection diff --git a/internal/resource/dp.go b/internal/resource/dp.go index 5d3e8c40..b1fb76c9 100644 --- a/internal/resource/dp.go +++ b/internal/resource/dp.go @@ -10,6 +10,10 @@ import ( appsv1 "k8s.io/api/apps/v1" ) +// Compile time checks to ensure type satisfies interface +var _ Restartable = (*Deployment)(nil) +var _ Scalable = (*Deployment)(nil) + // Deployment tracks a kubernetes resource. type Deployment struct { *Base @@ -129,3 +133,8 @@ func (r *Deployment) Fields(ns string) Row { func (r *Deployment) Scale(ns, n string, replicas int32) error { return r.Resource.(Scalable).Scale(ns, n, replicas) } + +// Restart the rollout of the specified resource. +func (r *Deployment) Restart(ns, n string) error { + return r.Resource.(Restartable).Restart(ns, n) +} diff --git a/internal/resource/ds.go b/internal/resource/ds.go index 5f1824d3..31c40d7e 100644 --- a/internal/resource/ds.go +++ b/internal/resource/ds.go @@ -10,6 +10,9 @@ import ( appsv1 "k8s.io/api/apps/v1" ) +// Compile time checks to ensure type satisfies interface +var _ Restartable = (*DaemonSet)(nil) + // DaemonSet tracks a kubernetes resource. type DaemonSet struct { *Base @@ -112,3 +115,8 @@ func (r *DaemonSet) Fields(ns string) Row { toAge(i.ObjectMeta.CreationTimestamp), ) } + +// Restart the rollout of the specified resource. +func (r *DaemonSet) Restart(ns, n string) error { + return r.Resource.(Restartable).Restart(ns, n) +} diff --git a/internal/resource/sts.go b/internal/resource/sts.go index dd9842f8..72bb77e0 100644 --- a/internal/resource/sts.go +++ b/internal/resource/sts.go @@ -10,6 +10,10 @@ import ( appsv1 "k8s.io/api/apps/v1" ) +// Compile time checks to ensure type satisfies interface +var _ Restartable = (*StatefulSet)(nil) +var _ Scalable = (*StatefulSet)(nil) + // StatefulSet tracks a kubernetes resource. type StatefulSet struct { *Base @@ -118,3 +122,8 @@ func (r *StatefulSet) Fields(ns string) Row { func (r *StatefulSet) Scale(ns, n string, replicas int32) error { return r.Resource.(Scalable).Scale(ns, n, replicas) } + +// Restart the rollout of the specified resource. +func (r *StatefulSet) Restart(ns, n string) error { + return r.Resource.(Restartable).Restart(ns, n) +} diff --git a/internal/views/dp.go b/internal/views/dp.go index d1bea114..8c5c50f8 100644 --- a/internal/views/dp.go +++ b/internal/views/dp.go @@ -10,14 +10,19 @@ import ( type deployView struct { *logResourceView - scalableResourceView *scalableResourceView + scalableResourceView *scalableResourceView + restartableResourceView *restartableResourceView } const scaleDialogKey = "scale" func newDeployView(title, gvr string, app *appView, list resource.List) resourceViewer { logResourceView := newLogResourceView(title, gvr, app, list) - v := deployView{logResourceView, newScalableResourceViewForParent(logResourceView.resourceView)} + v := deployView{ + logResourceView: logResourceView, + scalableResourceView: newScalableResourceViewForParent(logResourceView.resourceView), + restartableResourceView: newRestartableResourceViewForParent(logResourceView.resourceView), + } v.extraActionsFn = v.extraActions v.enterFn = v.showPods @@ -27,6 +32,7 @@ func newDeployView(title, gvr string, app *appView, list resource.List) resource func (v *deployView) extraActions(aa ui.KeyActions) { v.logResourceView.extraActions(aa) v.scalableResourceView.extraActions(aa) + v.restartableResourceView.extraActions(aa) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), false) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), false) } diff --git a/internal/views/ds.go b/internal/views/ds.go index 3a3c38ed..b77c5338 100644 --- a/internal/views/ds.go +++ b/internal/views/ds.go @@ -10,10 +10,15 @@ import ( type daemonSetView struct { *logResourceView + restartableResourceView *restartableResourceView } func newDaemonSetView(title, gvr string, app *appView, list resource.List) resourceViewer { - v := daemonSetView{newLogResourceView(title, gvr, app, list)} + view := newLogResourceView(title, gvr, app, list) + v := daemonSetView{ + logResourceView: view, + restartableResourceView: newRestartableResourceViewForParent(view.resourceView), + } v.extraActionsFn = v.extraActions v.enterFn = v.showPods @@ -22,6 +27,7 @@ func newDaemonSetView(title, gvr string, app *appView, list resource.List) resou func (v *daemonSetView) extraActions(aa ui.KeyActions) { v.logResourceView.extraActions(aa) + v.restartableResourceView.extraActions(aa) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(2, false), false) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(3, false), false) } diff --git a/internal/views/restartable_resource.go b/internal/views/restartable_resource.go new file mode 100644 index 00000000..6adc550d --- /dev/null +++ b/internal/views/restartable_resource.go @@ -0,0 +1,52 @@ +package views + +import ( + "errors" + + "github.com/derailed/k9s/internal/resource" + "github.com/derailed/k9s/internal/ui" + "github.com/gdamore/tcell" +) + +type ( + restartableResourceView struct { + *resourceView + } +) + +func newRestartableResourceViewForParent(parent *resourceView) *restartableResourceView { + v := restartableResourceView{ + parent, + } + parent.extraActionsFn = v.extraActions + return &v +} + +func (v *restartableResourceView) extraActions(aa ui.KeyActions) { + aa[tcell.KeyCtrlR] = ui.NewKeyAction("Restart Rollout", v.restartCmd, true) +} + +func (v *restartableResourceView) restartCmd(evt *tcell.EventKey) *tcell.EventKey { + if !v.masterPage().RowSelected() { + return evt + } + + v.restartRollout(v.masterPage().GetSelectedItem()) + return nil +} + +func (v *restartableResourceView) restartRollout(selection string) { + ns, n := namespaced(selection) + + r, ok := v.list.Resource().(resource.Restartable) + if !ok { + v.app.Flash().Err(errors.New("resource is not of type resource.Restartable")) + return + } + + err := r.Restart(ns, n) + if err != nil { + v.app.Flash().Err(err) + } + v.app.Flash().Info("Restarted Rollout") +} diff --git a/internal/views/sts.go b/internal/views/sts.go index 9d3c57d2..d8e1eae7 100644 --- a/internal/views/sts.go +++ b/internal/views/sts.go @@ -11,12 +11,13 @@ import ( type statefulSetView struct { *logResourceView - scalableResourceView *scalableResourceView + scalableResourceView *scalableResourceView + restartableResourceView *restartableResourceView } func newStatefulSetView(title, gvr string, app *appView, list resource.List) resourceViewer { logResourceView := newLogResourceView(title, gvr, app, list) - v := statefulSetView{logResourceView, newScalableResourceViewForParent(logResourceView.resourceView)} + v := statefulSetView{logResourceView: logResourceView, scalableResourceView: newScalableResourceViewForParent(logResourceView.resourceView), restartableResourceView: newRestartableResourceViewForParent(logResourceView.resourceView)} v.extraActionsFn = v.extraActions v.enterFn = v.showPods @@ -26,6 +27,7 @@ func newStatefulSetView(title, gvr string, app *appView, list resource.List) res func (v *statefulSetView) extraActions(aa ui.KeyActions) { v.logResourceView.extraActions(aa) v.scalableResourceView.extraActions(aa) + v.restartableResourceView.extraActions(aa) aa[ui.KeyShiftD] = ui.NewKeyAction("Sort Desired", v.sortColCmd(1, false), false) aa[ui.KeyShiftC] = ui.NewKeyAction("Sort Current", v.sortColCmd(2, false), false) }