diff --git a/internal/model/registry.go b/internal/model/registry.go index 9ed07953..6871252e 100644 --- a/internal/model/registry.go +++ b/internal/model/registry.go @@ -210,4 +210,8 @@ var Registry = map[string]ResourceMeta{ Renderer: &render.Application{}, TreeRenderer: &xray.Application{}, }, + "argoproj.io/v1alpha1/appprojects": { + Renderer: &render.AppProject{}, + TreeRenderer: &xray.AppProject{}, + }, } diff --git a/internal/render/appproject.go b/internal/render/appproject.go new file mode 100644 index 00000000..81b59d7e --- /dev/null +++ b/internal/render/appproject.go @@ -0,0 +1,47 @@ +package render + +import ( + "fmt" + + "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + "github.com/derailed/k9s/internal/client" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" +) + +// AppProject renders an ArgoCD App Project to screen. +type AppProject struct{} + +// ColorerFunc colors a resource row. +func (AppProject) ColorerFunc() ColorerFunc { + return DefaultColorer +} + +// Header returns a header row. +func (AppProject) Header(ns string) Header { + return Header{ + HeaderColumn{Name: "NAME"}, + HeaderColumn{Name: "AGE", Time: true, Decorator: AgeDecorator}, + } +} + +// Render renders a K8s resource to screen. +func (AppProject) Render(o interface{}, ns string, r *Row) error { + raw, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("Expected AppProject, but got %T", o) + } + var app v1alpha1.AppProject + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &app) + if err != nil { + return err + } + + r.ID = client.MetaFQN(app.ObjectMeta) + r.Fields = Fields{ + app.Name, + toAge(app.ObjectMeta.CreationTimestamp), + } + + return nil +} diff --git a/internal/view/command.go b/internal/view/command.go index 2c4c72ca..dd05e245 100644 --- a/internal/view/command.go +++ b/internal/view/command.go @@ -70,6 +70,7 @@ func allowedXRay(gvr client.GVR) bool { "apps/v1/statefulsets", "apps/v1/replicasets", "argoproj.io/v1alpha1/applications", + "argoproj.io/v1alpha1/appprojects", } for _, g := range gg { if g == gvr.String() { diff --git a/internal/xray/appproject.go b/internal/xray/appproject.go new file mode 100644 index 00000000..570a9004 --- /dev/null +++ b/internal/xray/appproject.go @@ -0,0 +1,77 @@ +package xray + +import ( + "context" + "fmt" + + v1alpha1 "github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1" + "github.com/derailed/k9s/internal" + "github.com/derailed/k9s/internal/client" + "github.com/derailed/k9s/internal/dao" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" +) + +// AppProject represents an xray renderer. +type AppProject struct{} + +// Render renders an xray node. +func (a *AppProject) Render(ctx context.Context, ns string, o interface{}) error { + raw, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("Expected Unstructured, but got %T", o) + } + + var proj v1alpha1.AppProject + err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &proj) + if err != nil { + return err + } + + parent, ok := ctx.Value(KeyParent).(*TreeNode) + if !ok { + return fmt.Errorf("Expecting a TreeNode but got %T", ctx.Value(KeyParent)) + } + + root := NewTreeNode("argoproj.io/v1alpha1/appprojects", client.FQN(proj.Namespace, proj.Name)) + ctx = context.WithValue(ctx, KeyParent, root) + + f, ok := ctx.Value(internal.KeyFactory).(dao.Factory) + if !ok { + return fmt.Errorf("Expecting a factory but got %T", ctx.Value(internal.KeyFactory)) + } + + oo, err := f.List("argoproj.io/v1alpha1/applications", "", false, labels.Everything()) + if err != nil { + return err + } + for _, o := range oo { + a, ok := o.(*unstructured.Unstructured) + if !ok { + return fmt.Errorf("expecting *Unstructured but got %T", o) + } + var aa v1alpha1.Application + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(a.Object, &aa); err != nil { + return err + } + if aa.Spec.Project != proj.Name { + continue + } + var app Application + if err := app.Render(ctx, proj.Namespace, a); err != nil { + return err + } + + } + + gvr, nsID := "v1/namespaces", client.FQN(client.ClusterScope, proj.Namespace) + nsn := parent.Find(gvr, nsID) + if nsn == nil { + nsn = NewTreeNode(gvr, nsID) + parent.Add(nsn) + } + nsn.Add(root) + + return nil +} diff --git a/internal/xray/tree_node.go b/internal/xray/tree_node.go index f960b433..2d382618 100644 --- a/internal/xray/tree_node.go +++ b/internal/xray/tree_node.go @@ -494,6 +494,8 @@ func toEmoji(gvr string) string { return "📔" case "argoproj.io/v1alpha1/applications": return "🏠" + case "argoproj.io/v1alpha1/appprojects": + return "🏘" case "containers": return "🐳" case "report":