update registrar

mine
derailed 2019-09-26 17:03:32 -06:00
parent a630a95606
commit 7d05e4401e
3 changed files with 239 additions and 388 deletions

View File

@ -119,7 +119,7 @@ func (v *aliasView) hydrate() resource.TableData {
for k := range cmds { for k := range cmds {
fields := resource.Row{ fields := resource.Row{
ui.Pad(k, 30), ui.Pad(k, 30),
ui.Pad(cmds[k].kind, 30), ui.Pad(cmds[k].gvr, 30),
ui.Pad(cmds[k].api, 30), ui.Pad(cmds[k].api, 30),
} }
data.Rows[k] = &resource.RowEvent{ data.Rows[k] = &resource.RowEvent{

View File

@ -70,10 +70,8 @@ func (c *command) isStdCmd(cmd string) bool {
return false return false
} }
func (c *command) isAliasCmd(cmd string) bool { func (c *command) isAliasCmd(alias string, cmds map[string]resCmd) bool {
cmds := make(map[string]*resCmd, 30) res, ok := cmds[alias]
resourceViews(c.app.Conn(), cmds)
res, ok := cmds[cmd]
if !ok { if !ok {
return false return false
} }
@ -83,7 +81,11 @@ func (c *command) isAliasCmd(cmd string) bool {
r = res.listFn(c.app.Conn(), resource.DefaultNamespace) r = res.listFn(c.app.Conn(), resource.DefaultNamespace)
} }
v := res.viewFn(res.kind, c.app, r) v := newResourceView(res.gvr, c.app, r)
if res.viewFn != nil {
v = res.viewFn(res.gvr, c.app, r)
}
if res.colorerFn != nil { if res.colorerFn != nil {
v.setColorerFn(res.colorerFn) v.setColorerFn(res.colorerFn)
} }
@ -94,16 +96,17 @@ func (c *command) isAliasCmd(cmd string) bool {
v.setDecorateFn(res.decorateFn) v.setDecorateFn(res.decorateFn)
} }
c.app.Flash().Infof("Viewing resource %s...", res.kind) const fmat = "Viewing resource %s..."
log.Debug().Msgf("Running command %s", cmd) c.app.Flash().Infof(fmat, res.gvr)
c.exec(cmd, v) log.Debug().Msgf("Running command %s", alias)
c.exec(alias, v)
return true return true
} }
func (c *command) isCRDCmd(cmd string) bool { func (c *command) isCRDCmd(cmd string) bool {
crds := map[string]*resCmd{} crds := map[string]resCmd{}
allCRDs(c.app.Conn(), crds)
res, ok := crds[cmd] res, ok := crds[cmd]
if !ok { if !ok {
return false return false
@ -114,7 +117,7 @@ func (c *command) isCRDCmd(cmd string) bool {
name = res.singular name = res.singular
} }
v := newResourceView( v := newResourceView(
res.kind, res.gvr,
c.app, c.app,
resource.NewCustomList(c.app.Conn(), "", res.api, res.version, name), resource.NewCustomList(c.app.Conn(), "", res.api, res.version, name),
) )
@ -130,23 +133,23 @@ func (c *command) run(cmd string) bool {
return true return true
} }
if c.isAliasCmd(cmd) { cmds := make(map[string]resCmd, 30)
return true resourceViews(c.app.Conn(), cmds)
allCRDs(c.app.Conn(), cmds)
a, ok := aliases[cmd]
if !ok {
c.app.Flash().Warnf("Huh? `%s` command not found", cmd)
return false
} }
if c.isCRDCmd(cmd) { return c.isAliasCmd(a, cmds)
return true
}
c.app.Flash().Warnf("Huh? `%s` command not found", cmd)
return false
} }
func (c *command) exec(cmd string, v ui.Igniter) { func (c *command) exec(cmd string, v ui.Igniter) {
if v == nil { if v == nil {
return return
} }
c.app.Config.SetActiveView(cmd) c.app.Config.SetActiveView(cmd)
c.app.Config.Save() c.app.Config.Save()
c.app.inject(v) c.app.inject(v)

View File

@ -1,6 +1,8 @@
package views package views
import ( import (
"fmt"
"path"
"strings" "strings"
"github.com/derailed/k9s/internal/k8s" "github.com/derailed/k9s/internal/k8s"
@ -26,7 +28,9 @@ type (
resCmd struct { resCmd struct {
crdCmd crdCmd
kind string gvr string
namespaced bool
verbs metav1.Verbs
viewFn viewFn viewFn viewFn
listFn listFn listFn listFn
enterFn enterFn enterFn enterFn
@ -61,51 +65,55 @@ var DefaultAliasConfig = AliasConfig{
}, },
} }
func aliasCmds(c k8s.Connection, m map[string]*resCmd) { func aliasCmds(c k8s.Connection, m map[string]resCmd) {
resourceViews(c, m) resourceViews(c, m)
if c != nil { if c != nil {
allCRDs(c, m) allCRDs(c, m)
} }
} }
func allCRDs(c k8s.Connection, m map[string]*resCmd) { func listFunc(l resource.List) viewFn {
crds, _ := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces). return func(ns string, app *appView, list resource.List) resourceViewer {
return newResourceView(
ns,
app,
l,
)
}
}
func allCRDs(c k8s.Connection, m map[string]resCmd) {
crds, err := resource.NewCustomResourceDefinitionList(c, resource.AllNamespaces).
Resource(). Resource().
List(resource.AllNamespaces) List(resource.AllNamespaces)
if err != nil {
log.Error().Err(err).Msg("CRDs load fail")
return
}
for _, crd := range crds { for _, crd := range crds {
ff := crd.ExtFields() ff := crd.ExtFields()
grp := k8s.APIGroup{ gvr := path.Join(ff["group"].(string), ff["version"].(string), ff["kind"].(string))
GKV: k8s.GKV{ var name string
Group: ff["group"].(string),
Kind: ff["kind"].(string),
Version: ff["version"].(string),
},
}
res := resCmd{
kind: grp.Kind,
crdCmd: crdCmd{
api: grp.Group,
version: grp.Version,
},
}
if p, ok := ff["plural"].(string); ok { if p, ok := ff["plural"].(string); ok {
res.plural = p aliases[p] = gvr
m[p] = &res name = p
} }
if s, ok := ff["singular"].(string); ok { if s, ok := ff["singular"].(string); ok {
res.singular = s aliases[s] = gvr
m[s] = &res name = s
} }
if aa, ok := ff["aliases"].([]interface{}); ok { if aa, ok := ff["aliases"].([]interface{}); ok {
for _, a := range aa { for _, a := range aa {
m[a.(string)] = &res aliases[a.(string)] = gvr
} }
} }
m[gvr] = resCmd{
gvr: gvr,
viewFn: listFunc(resource.NewCustomList(c, "", ff["group"].(string), ff["version"].(string), name)),
colorerFn: ui.DefaultColorer,
}
} }
} }
@ -148,378 +156,218 @@ func showSAPolicy(app *appView, _, _, selection string) {
app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n)) app.inject(newPolicyView(app, mapFuSubject("ServiceAccount"), n))
} }
func collect(commandList ...[]*resCmd) (accum []*resCmd) { type Aliases map[string]string
for _, commands := range commandList {
accum = append(accum, commands...)
}
return
}
func resourceViews(c k8s.Connection, cmdMap map[string]*resCmd) { var aliases Aliases
commands := collect(
primRes(), coreRes(), stateRes(), rbacRes(), apiExtRes(),
batchRes(), appsRes(), extRes(), v1beta1Res(), custRes(),
)
if c != nil { func load(c k8s.Connection, viewRes map[string]resCmd) {
commands = append(commands, hpaRes(c)...) // cc := map[string]resCmd{}
} aliases = make(Aliases, len(viewRes))
rr, _ := c.DialOrDie().Discovery().ServerPreferredResources()
for _, rsc := range commands { for _, r := range rr {
cmdMap[strings.ToLower(rsc.kind)] = rsc log.Debug().Msgf("Group %#v", r.GroupVersion)
} for _, res := range r.APIResources {
log.Debug().Msgf("\tRes %s -- %q:%q -- %+v", res.Name, res.Group, res.Version, res.ShortNames)
// Add default aliases gvr := path.Join(r.GroupVersion, res.Name)
// TODO: read aliases from a config file. // Get singular, plural, shortname and to alias under gvr name
for rsc, alias := range DefaultAliasConfig.Aliases { if cmd, ok := viewRes[gvr]; !ok {
if cmd, ok := cmdMap[strings.ToLower(rsc)]; ok { log.Error().Msgf(fmt.Sprintf(">> Missed %s", gvr))
addAlias(cmdMap, cmd, alias) } else {
} log.Debug().Msgf("Res %#v", res)
} cmd.namespaced = res.Namespaced
cmd.verbs = res.Verbs
if c != nil { cmd.gvr = gvr
discoverAliasesFromServer(c, cmdMap) viewRes[gvr] = cmd
} aliases[strings.ToLower(res.Kind)] = gvr
} aliases[res.Name] = gvr
aliases[res.SingularName] = gvr
func addAlias(cmdMap map[string]*resCmd, cmd *resCmd, alias string) { for _, s := range res.ShortNames {
if alias == "" { aliases[s] = gvr
return }
} }
alias = strings.ToLower(alias)
if _, ok := cmdMap[alias]; !ok {
cmdMap[alias] = cmd
}
}
func addAPIResourceAliases(cmds map[string]*resCmd, resource metav1.APIResource) {
if strings.Contains(resource.Name, "/") {
// Ignore resources that has slash, e.g.,
// deployment/status, namespace/finalizers and etc.
return
}
if cmd, ok := cmds[strings.ToLower(resource.Kind)]; ok {
addAlias(cmds, cmd, resource.Name)
addAlias(cmds, cmd, resource.SingularName)
for _, sn := range resource.ShortNames {
addAlias(cmds, cmd, sn)
} }
} }
} }
func discoverAliasesFromServer(con k8s.Connection, cmds map[string]*resCmd) { func resourceViews(c k8s.Connection, m map[string]resCmd) {
_, resourceLists, _ := con.DialOrDie().Discovery().ServerGroupsAndResources() coreRes(m)
for _, resourceList := range resourceLists { miscRes(m)
for _, resource := range resourceList.APIResources { appsRes(m)
addAPIResourceAliases(cmds, resource) authRes(m)
} extRes(m)
netRes(m)
batchRes(m)
policyRes(m)
hpaRes(m)
load(c, m)
}
func coreRes(m map[string]resCmd) {
m["v1/nodes"] = resCmd{
viewFn: newNodeView,
listFn: resource.NewNodeList,
colorerFn: nsColorer,
}
m["v1/namespaces"] = resCmd{
viewFn: newNamespaceView,
listFn: resource.NewNamespaceList,
colorerFn: nsColorer,
}
m["v1/pods"] = resCmd{
viewFn: newPodView,
listFn: resource.NewPodList,
colorerFn: podColorer,
}
m["v1/serviceaccounts"] = resCmd{
viewFn: newResourceView,
listFn: resource.NewServiceAccountList,
enterFn: showSAPolicy,
}
m["v1/services"] = resCmd{
viewFn: newSvcView,
listFn: resource.NewServiceList,
}
m["v1/configmaps"] = resCmd{
listFn: resource.NewConfigMapList,
}
m["v1/persistentvolumes"] = resCmd{
listFn: resource.NewPersistentVolumeList,
colorerFn: pvColorer,
}
m["v1/persistentvolumeclaims"] = resCmd{
listFn: resource.NewPersistentVolumeClaimList,
colorerFn: pvcColorer,
}
m["v1/secrets"] = resCmd{
viewFn: newSecretView,
listFn: resource.NewSecretList,
}
m["v1/endpoints"] = resCmd{
listFn: resource.NewEndpointsList,
}
m["v1/events"] = resCmd{
listFn: resource.NewEventList,
colorerFn: evColorer,
}
m["v1/replicationcontrollers"] = resCmd{
viewFn: newScalableResourceView,
listFn: resource.NewReplicationControllerList,
colorerFn: rsColorer,
} }
} }
func stateRes() []*resCmd { func miscRes(m map[string]resCmd) {
return []*resCmd{ m["storage.k8s.io/storageclasses"] = resCmd{
{ listFn: resource.NewStorageClassList,
kind: "ConfigMap",
viewFn: newResourceView,
listFn: resource.NewConfigMapList,
},
{
kind: "PersistentVolume",
viewFn: newResourceView,
listFn: resource.NewPersistentVolumeList,
colorerFn: pvColorer,
},
{
kind: "PersistentVolumeClaim",
viewFn: newResourceView,
listFn: resource.NewPersistentVolumeClaimList,
colorerFn: pvcColorer,
},
{
kind: "Secret",
viewFn: newSecretView,
listFn: resource.NewSecretList,
},
{
kind: "StorageClass",
crdCmd: crdCmd{
api: "storage.k8s.io",
},
viewFn: newResourceView,
listFn: resource.NewStorageClassList,
},
} }
} m["ctx"] = resCmd{
gvr: "Contexts",
func primRes() []*resCmd { viewFn: newContextView,
return []*resCmd{ listFn: resource.NewContextList,
{ colorerFn: ctxColorer,
kind: "Node",
viewFn: newNodeView,
listFn: resource.NewNodeList,
colorerFn: nsColorer,
},
{
kind: "Namespace",
viewFn: newNamespaceView,
listFn: resource.NewNamespaceList,
colorerFn: nsColorer,
},
{
kind: "Pod",
viewFn: newPodView,
listFn: resource.NewPodList,
colorerFn: podColorer,
},
{
kind: "ServiceAccount",
viewFn: newResourceView,
listFn: resource.NewServiceAccountList,
enterFn: showSAPolicy,
},
{
kind: "Service",
viewFn: newSvcView,
listFn: resource.NewServiceList,
},
} }
} m["usr"] = resCmd{
viewFn: newSubjectView,
func coreRes() []*resCmd {
return []*resCmd{
{
kind: "Contexts",
viewFn: newContextView,
listFn: resource.NewContextList,
colorerFn: ctxColorer,
},
{
kind: "DaemonSet",
viewFn: newDaemonSetView,
listFn: resource.NewDaemonSetList,
colorerFn: dpColorer,
},
{
kind: "EndPoints",
viewFn: newResourceView,
listFn: resource.NewEndpointsList,
},
{
kind: "Event",
viewFn: newResourceView,
listFn: resource.NewEventList,
colorerFn: evColorer,
},
{
kind: "ReplicationController",
viewFn: newScalableResourceView,
listFn: resource.NewReplicationControllerList,
colorerFn: rsColorer,
},
} }
} m["grp"] = resCmd{
viewFn: newSubjectView,
func custRes() []*resCmd { }
return []*resCmd{ m["pf"] = resCmd{
{ gvr: "PortForward",
kind: "Users", viewFn: newForwardView,
viewFn: newSubjectView, }
}, m["be"] = resCmd{
{ gvr: "Benchmark",
kind: "Groups", viewFn: newBenchView,
viewFn: newSubjectView, }
}, m["sd"] = resCmd{
{ gvr: "ScreenDumps",
kind: "PortForward", viewFn: newDumpView,
viewFn: newForwardView,
},
{
kind: "Benchmark",
viewFn: newBenchView,
},
{
kind: "ScreenDumps",
viewFn: newDumpView,
},
} }
} }
func rbacRes() []*resCmd { func appsRes(m map[string]resCmd) {
return []*resCmd{ m["apps/v1/deployments"] = resCmd{
{ viewFn: newDeployView,
kind: "ClusterRole", listFn: resource.NewDeploymentList,
crdCmd: crdCmd{ colorerFn: dpColorer,
api: "rbac.authorization.k8s.io", }
}, m["apps/v1/replicasets"] = resCmd{
viewFn: newResourceView, viewFn: newReplicaSetView,
listFn: resource.NewClusterRoleList, listFn: resource.NewReplicaSetList,
enterFn: showRBAC, colorerFn: rsColorer,
}, }
{ m["apps/v1/statefulsets"] = resCmd{
kind: "ClusterRoleBinding", viewFn: newStatefulSetView,
crdCmd: crdCmd{ listFn: resource.NewStatefulSetList,
api: "rbac.authorization.k8s.io", colorerFn: stsColorer,
}, }
viewFn: newResourceView, m["apps/v1/daemonsets"] = resCmd{
listFn: resource.NewClusterRoleBindingList, viewFn: newDaemonSetView,
enterFn: showClusterRole, listFn: resource.NewDaemonSetList,
}, colorerFn: dpColorer,
{
kind: "RoleBinding",
crdCmd: crdCmd{
api: "rbac.authorization.k8s.io",
},
viewFn: newResourceView,
listFn: resource.NewRoleBindingList,
enterFn: showRole,
},
{
kind: "Role",
crdCmd: crdCmd{
api: "rbac.authorization.k8s.io",
},
viewFn: newResourceView,
listFn: resource.NewRoleList,
enterFn: showRBAC,
},
} }
} }
func apiExtRes() []*resCmd { func authRes(m map[string]resCmd) {
return []*resCmd{ m["rbac.authorization.k8s.io/v1/clusterroles"] = resCmd{
{ listFn: resource.NewClusterRoleList,
kind: "CustomResourceDefinition", enterFn: showRBAC,
crdCmd: crdCmd{ }
api: "apiextensions.k8s.io", m["rbac.authorization.k8s.io/v1/clusterrolebindings"] = resCmd{
}, listFn: resource.NewClusterRoleBindingList,
viewFn: newResourceView, enterFn: showClusterRole,
listFn: resource.NewCustomResourceDefinitionList, }
enterFn: showCRD, m["rbac.authorization.k8s.io/v1/rolebindings"] = resCmd{
}, listFn: resource.NewRoleBindingList,
{ enterFn: showRole,
kind: "NetworkPolicy", }
crdCmd: crdCmd{ m["rbac.authorization.k8s.io/v1/roles"] = resCmd{
api: "apiextensions.k8s.io", listFn: resource.NewRoleList,
}, enterFn: showRBAC,
viewFn: newResourceView,
listFn: resource.NewNetworkPolicyList,
},
} }
} }
func batchRes() []*resCmd { func extRes(m map[string]resCmd) {
return []*resCmd{ m["apiextensions.k8s.io/v1/customresourcedefinitions"] = resCmd{
{ listFn: resource.NewCustomResourceDefinitionList,
kind: "CronJob", enterFn: showCRD,
crdCmd: crdCmd{
api: "batch",
},
viewFn: newCronJobView,
listFn: resource.NewCronJobList,
},
{
kind: "Job",
crdCmd: crdCmd{
api: "batch",
},
viewFn: newJobView,
listFn: resource.NewJobList,
},
} }
} }
func appsRes() []*resCmd { func netRes(m map[string]resCmd) {
return []*resCmd{ m["networking.k8s.io/v1/networkpolicies"] = resCmd{
{ gvr: "apiextensions.k8s.io/NetworkPolicies",
kind: "Deployment", listFn: resource.NewNetworkPolicyList,
crdCmd: crdCmd{
api: "apps",
},
viewFn: newDeployView,
listFn: resource.NewDeploymentList,
colorerFn: dpColorer,
},
{
kind: "ReplicaSet",
crdCmd: crdCmd{
api: "apps",
},
viewFn: newReplicaSetView,
listFn: resource.NewReplicaSetList,
colorerFn: rsColorer,
},
{
kind: "StatefulSet",
crdCmd: crdCmd{
api: "apps",
},
viewFn: newStatefulSetView,
listFn: resource.NewStatefulSetList,
colorerFn: stsColorer,
},
} }
m["networking.k8s.io/v1beta1/ingresses"] = resCmd{
} listFn: resource.NewIngressList,
func extRes() []*resCmd {
return []*resCmd{
{
kind: "Ingress",
crdCmd: crdCmd{
api: "extensions",
},
viewFn: newResourceView,
listFn: resource.NewIngressList,
},
} }
} }
func v1beta1Res() []*resCmd { func batchRes(m map[string]resCmd) {
return []*resCmd{ m["batch/v1/cronjobs"] = resCmd{
{ viewFn: newCronJobView,
kind: "PodDisruptionBudget", listFn: resource.NewCronJobList,
crdCmd: crdCmd{ }
api: "v1.beta1", m["batch/v1/jobs"] = resCmd{
}, viewFn: newJobView,
viewFn: newResourceView, listFn: resource.NewJobList,
listFn: resource.NewPDBList,
colorerFn: pdbColorer,
},
} }
} }
func hpaRes(c k8s.Connection) []*resCmd { func policyRes(m map[string]resCmd) {
rev, ok, err := c.SupportsRes("autoscaling", []string{"v1", "v2beta1", "v2beta2"}) m["policy/v1beta1/poddisruptionbudgets"] = resCmd{
if err != nil { viewFn: newResourceView,
log.Error().Err(err).Msg("Checking HPA") listFn: resource.NewPDBList,
return nil colorerFn: pdbColorer,
}
}
func hpaRes(m map[string]resCmd) {
m["autoscaling/v1/horizontalpodautoscalers"] = resCmd{
listFn: resource.NewHorizontalPodAutoscalerV1List,
} }
if !ok {
log.Error().Msg("HPA are not supported on this cluster")
return nil
}
hpa := &resCmd{
kind: "HorizontalPodAutoscaler",
crdCmd: crdCmd{
api: "autoscaling",
},
viewFn: newResourceView,
}
switch rev {
case "v1":
hpa.listFn = resource.NewHorizontalPodAutoscalerV1List
case "v2beta1":
hpa.listFn = resource.NewHorizontalPodAutoscalerV2Beta1List
case "v2beta2":
hpa.listFn = resource.NewHorizontalPodAutoscalerList
default:
log.Panic().Msgf("K9s unsupported HPA version. Exiting!")
}
return []*resCmd{hpa}
} }