diff --git a/change_logs/release_0.3.3.md b/change_logs/release_0.3.3.md new file mode 100644 index 00000000..d331af0d --- /dev/null +++ b/change_logs/release_0.3.3.md @@ -0,0 +1,43 @@ +# Release v0.4.0 + +## Notes + +Thank you to all that contributed with flushing out issues with K9s! I'll try +to mark some of these issues as fixed. But if you don't mind grab the latest +rev and see if we're happier with some of the fixes! + +If you've filed an issue please help me verify and close. + +Thank you so much for your support and awesome suggestions to make K9s better!! + +--- + +## Change Logs + +1. [Feature #82](https://github.com/derailed/k9s/issues/82) + 1. Added ability to view RBAC policies while in clusterrole or role view. + 1. 😃 The RBAC view will auto-refresh just like any K9s views hence showing live RBAC updates! + 1. RBAC view supports standard K8s verbs ie get,list,deletecollection,watch,create,patch,update,delete. + 1. Any verbs not in this standard K8s verb list, will end up in the EXTRAS column. + 1. For non resource URLS, we map standard REST verbs to K8s verbs ie post=create patch=update, etc.. + 1. Added initial sorts by name and group while in RBAC view. + 1. Usage: To activate, enter command mode via `:cr` or `:ro` for clusterole(cr)/role(ro), select a row and press `` + 1. To bail out of the view and return to previous use `p` or `` +1. One feature that was mentioned in the comments for the RBAC feature above Tx [faheem-cliqz](https://github.com/faheem-cliqz)! was the ability to check RBAC rules for a given user. Namely reverse RBAC lookup + 1. Added a new view, code name *Fu* view to show all the clusterroles/roles associated with a given user. + 1. The view also supports for checking RBAC Fu for a user, a group or an app via a serviceaccount. + 1. To activate: Enter command mode via `:fu` followed by u|g|s:subject + ``. + For example: To view user *fred* Fu enter `:fu u:fred` + `` will show all clusterroles/roles and verbs associated + with the user *fred* + 1. For group Fu lookup, use the same command as above and substitute `u:fred` with `g:fred` + 1. For ServiceAccount *fred* Fu check: use `s:fred` +1. Try to eliminate a tad of a jitter while scrolling on most views. Please report back if that's not the case + +> NOTE!: This feature is very much an alpha feature right now. +> I find it really powerful and useful, hopefully I am not the only one hunanimous on that?? + +--- + +## Resolved Bugs + ++ None diff --git a/internal/resource/cr.go b/internal/resource/cr.go index e42ff812..a356e93b 100644 --- a/internal/resource/cr.go +++ b/internal/resource/cr.go @@ -72,7 +72,7 @@ func (r *ClusterRole) Fields(ns string) Row { i := r.instance return append(ff, - Pad(i.Name, 70), - toAge(i.ObjectMeta.CreationTimestamp), + Pad(i.Name, RBACPad), + Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad), ) } diff --git a/internal/resource/cr_test.go b/internal/resource/cr_test.go index 8fc7bc94..45dc00ab 100644 --- a/internal/resource/cr_test.go +++ b/internal/resource/cr_test.go @@ -2,7 +2,6 @@ package resource_test import ( "fmt" - "strings" "testing" "time" @@ -42,12 +41,12 @@ func TestCRListAccess(t *testing.T) { func TestCRFields(t *testing.T) { r := newClusterRole().Fields("blee") - assert.Equal(t, "fred"+strings.Repeat(" ", 66), r[0]) + assert.Equal(t, resource.Pad("fred", resource.RBACPad), r[0]) } func TestCRFieldsAllNS(t *testing.T) { r := newClusterRole().Fields(resource.AllNamespaces) - assert.Equal(t, "fred"+strings.Repeat(" ", 66), r[0]) + assert.Equal(t, resource.Pad("fred", resource.RBACPad), r[0]) } func TestCRMarshal(t *testing.T) { @@ -85,7 +84,7 @@ func TestCRListData(t *testing.T) { for _, d := range row.Deltas { assert.Equal(t, "", d) } - assert.Equal(t, resource.Row{"fred" + strings.Repeat(" ", 66)}, row.Fields[:1]) + assert.Equal(t, resource.Row{resource.Pad("fred", resource.RBACPad)}, row.Fields[:1]) } // Helpers... diff --git a/internal/resource/cronjob.go b/internal/resource/cronjob.go index 14e449cb..e42899e9 100644 --- a/internal/resource/cronjob.go +++ b/internal/resource/cronjob.go @@ -102,7 +102,7 @@ func (r *CronJob) Fields(ns string) Row { i := r.instance if ns == AllNamespaces { - ff = append(ff, i.Namespace) + ff = append(ff, Pad(i.Namespace, NSPad)) } lastScheduled := "" @@ -111,11 +111,11 @@ func (r *CronJob) Fields(ns string) Row { } return append(ff, - i.Name, + Pad(i.Name, NamePad), i.Spec.Schedule, boolPtrToStr(i.Spec.Suspend), strconv.Itoa(len(i.Status.Active)), lastScheduled, - toAge(i.ObjectMeta.CreationTimestamp), + Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad), ) } diff --git a/internal/resource/cronjob_test.go b/internal/resource/cronjob_test.go index 51f21d6a..560eebec 100644 --- a/internal/resource/cronjob_test.go +++ b/internal/resource/cronjob_test.go @@ -39,7 +39,7 @@ func TestCronJobListAccess(t *testing.T) { func TestCronJobFields(t *testing.T) { r := newCronJob().Fields("blee") - assert.Equal(t, "fred", r[0]) + assert.Equal(t, resource.Pad("fred", resource.NamePad), r[0]) } func TestCronJobMarshal(t *testing.T) { @@ -75,7 +75,7 @@ func TestCronJobListData(t *testing.T) { for _, d := range row.Deltas { assert.Equal(t, "", d) } - assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) + assert.Equal(t, resource.Row{resource.Pad("fred", resource.NamePad)}, row.Fields[:1]) } // Helpers... diff --git a/internal/resource/helpers.go b/internal/resource/helpers.go index a57b87dd..5868c6d0 100644 --- a/internal/resource/helpers.go +++ b/internal/resource/helpers.go @@ -36,6 +36,14 @@ const ( NAValue = "" ) +// Columns Padding... +const ( + NSPad = 13 + NamePad = 50 + AgePad = 5 + RBACPad = 80 +) + func asPerc(f float64) string { return fmt.Sprintf("%d%%", int(f)) } diff --git a/internal/resource/job.go b/internal/resource/job.go index 5354a66c..027d81ac 100644 --- a/internal/resource/job.go +++ b/internal/resource/job.go @@ -132,18 +132,18 @@ func (r *Job) Fields(ns string) Row { i := r.instance if ns == AllNamespaces { - ff = append(ff, i.Namespace) + ff = append(ff, Pad(i.Namespace, NSPad)) } cc, ii := r.toContainers(i.Spec.Template.Spec) return append(ff, - i.Name, + Pad(i.Name, NamePad), r.toCompletion(i.Spec, i.Status), r.toDuration(i.Status), cc, ii, - toAge(i.ObjectMeta.CreationTimestamp), + Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad), ) } diff --git a/internal/resource/job_test.go b/internal/resource/job_test.go index 1501bbc3..d437377f 100644 --- a/internal/resource/job_test.go +++ b/internal/resource/job_test.go @@ -38,7 +38,7 @@ func TestJobListAccess(t *testing.T) { func TestJobFields(t *testing.T) { r := newJob().Fields("blee") - assert.Equal(t, "fred", r[0]) + assert.Equal(t, resource.Pad("fred", resource.NamePad), r[0]) } func TestJobMarshal(t *testing.T) { @@ -74,7 +74,7 @@ func TestJobListData(t *testing.T) { for _, d := range row.Deltas { assert.Equal(t, "", d) } - assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) + assert.Equal(t, resource.Row{resource.Pad("fred", resource.NamePad)}, row.Fields[:1]) } // Helpers... diff --git a/internal/resource/pod.go b/internal/resource/pod.go index 7cd3efed..62498e98 100644 --- a/internal/resource/pod.go +++ b/internal/resource/pod.go @@ -203,14 +203,14 @@ func (r *Pod) Fields(ns string) Row { i := r.instance if ns == AllNamespaces { - ff = append(ff, FixCol(i.Namespace, 13)) + ff = append(ff, FixCol(i.Namespace, NSPad)) } ss := i.Status.ContainerStatuses cr, _, rc := r.statuses(ss) return append(ff, - FixCol(i.ObjectMeta.Name, 50), + FixCol(i.ObjectMeta.Name, NamePad), strconv.Itoa(cr)+"/"+strconv.Itoa(len(ss)), r.phase(i.Status), strconv.Itoa(rc), @@ -219,7 +219,7 @@ func (r *Pod) Fields(ns string) Row { i.Status.PodIP, i.Spec.NodeName, r.mapQOS(i.Status.QOSClass), - toAge(i.ObjectMeta.CreationTimestamp), + Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad), ) } diff --git a/internal/resource/ro.go b/internal/resource/ro.go index 0d69057b..b47572c3 100644 --- a/internal/resource/ro.go +++ b/internal/resource/ro.go @@ -79,12 +79,12 @@ func (r *Role) Fields(ns string) Row { i := r.instance if ns == AllNamespaces { - ff = append(ff, i.Namespace) + ff = append(ff, Pad(i.Namespace, NSPad)) } return append(ff, - i.Name, - toAge(i.ObjectMeta.CreationTimestamp), + Pad(i.Name, RBACPad), + Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad), ) } diff --git a/internal/resource/ro_test.go b/internal/resource/ro_test.go index 82bef2bd..1932c772 100644 --- a/internal/resource/ro_test.go +++ b/internal/resource/ro_test.go @@ -54,7 +54,7 @@ func TestRoleListData(t *testing.T) { for _, d := range row.Deltas { assert.Equal(t, "", d) } - assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) + assert.Equal(t, resource.Row{resource.Pad("fred", resource.RBACPad)}, row.Fields[:1]) } // Helpers... diff --git a/internal/resource/rs.go b/internal/resource/rs.go index 3852ece1..4bd87ef7 100644 --- a/internal/resource/rs.go +++ b/internal/resource/rs.go @@ -77,16 +77,16 @@ func (*ReplicaSet) Header(ns string) Row { func (r *ReplicaSet) Fields(ns string) Row { ff := make(Row, 0, len(r.Header(ns))) if ns == AllNamespaces { - ff = append(ff, r.instance.Namespace) + ff = append(ff, Pad(r.instance.Namespace, NSPad)) } i := r.instance return append(ff, - i.Name, + Pad(i.Name, NamePad), strconv.Itoa(int(*i.Spec.Replicas)), strconv.Itoa(int(i.Status.Replicas)), strconv.Itoa(int(i.Status.ReadyReplicas)), - toAge(i.ObjectMeta.CreationTimestamp), + Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad), ) } diff --git a/internal/resource/rs_test.go b/internal/resource/rs_test.go index 486c509e..6462d73d 100644 --- a/internal/resource/rs_test.go +++ b/internal/resource/rs_test.go @@ -54,7 +54,7 @@ func TestReplicaSetListData(t *testing.T) { for _, d := range row.Deltas { assert.Equal(t, "", d) } - assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) + assert.Equal(t, resource.Row{resource.Pad("fred", resource.NamePad)}, row.Fields[:1]) } // Helpers... diff --git a/internal/resource/svc.go b/internal/resource/svc.go index a9ae820a..6fc8bb6a 100644 --- a/internal/resource/svc.go +++ b/internal/resource/svc.go @@ -91,16 +91,16 @@ func (r *Service) Fields(ns string) Row { i := r.instance if ns == AllNamespaces { - ff = append(ff, i.Namespace) + ff = append(ff, Pad(i.Namespace, NSPad)) } return append(ff, - i.ObjectMeta.Name, + Pad(i.ObjectMeta.Name, NamePad), string(i.Spec.Type), i.Spec.ClusterIP, r.toIPs(i.Spec.Type, r.getSvcExtIPS(i)), r.toPorts(i.Spec.Ports), - toAge(i.ObjectMeta.CreationTimestamp), + Pad(toAge(i.ObjectMeta.CreationTimestamp), AgePad), ) } diff --git a/internal/resource/svc_test.go b/internal/resource/svc_test.go index 517abccc..994f3199 100644 --- a/internal/resource/svc_test.go +++ b/internal/resource/svc_test.go @@ -52,8 +52,8 @@ func TestSvcFields(t *testing.T) { { i: newSvc(), e: resource.Row{ - "blee", - "fred", + resource.Pad("blee", resource.NSPad), + resource.Pad("fred", resource.NamePad), "ClusterIP", "1.1.1.1", "2.2.2.2", @@ -102,7 +102,7 @@ func TestSVCListData(t *testing.T) { for _, d := range row.Deltas { assert.Equal(t, "", d) } - assert.Equal(t, resource.Row{"fred"}, row.Fields[:1]) + assert.Equal(t, resource.Row{resource.Pad("fred", 50)}, row.Fields[:1]) } // Helpers... diff --git a/internal/views/fu.go b/internal/views/fu.go index 0dcb6901..ee6a838b 100644 --- a/internal/views/fu.go +++ b/internal/views/fu.go @@ -115,7 +115,6 @@ func (v *fuView) hints() hints { } func (v *fuView) reconcile() (resource.TableData, error) { - log.Debug().Msg("ClusterRoles...") evts, errs := v.clusterPolicies() if len(errs) > 0 { for _, err := range errs { @@ -124,7 +123,6 @@ func (v *fuView) reconcile() (resource.TableData, error) { return resource.TableData{}, errs[0] } - log.Debug().Msg("Roles...") nevts, errs := v.namespacePolicies() if len(errs) > 0 { for _, err := range errs {