// SPDX-License-Identifier: Apache-2.0 // Copyright Authors of K9s package render import ( "fmt" "strings" "github.com/derailed/k9s/internal/client" "github.com/derailed/k9s/internal/model1" netv1 "k8s.io/api/networking/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" ) var defaultNPHeader = model1.Header{ model1.HeaderColumn{Name: "NAMESPACE"}, model1.HeaderColumn{Name: "NAME"}, model1.HeaderColumn{Name: "POD-SELECTOR"}, model1.HeaderColumn{Name: "ING-SELECTOR", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "ING-PORTS", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "ING-BLOCK", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "EGR-SELECTOR", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "EGR-PORTS", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "EGR-BLOCK", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "LABELS", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "VALID", Attrs: model1.Attrs{Wide: true}}, model1.HeaderColumn{Name: "AGE", Attrs: model1.Attrs{Time: true}}, } // NetworkPolicy renders a K8s NetworkPolicy to screen. type NetworkPolicy struct { Base } // Header returns a header row. func (p NetworkPolicy) Header(_ string) model1.Header { return p.doHeader(defaultNPHeader) } // Render renders a K8s resource to screen. func (p NetworkPolicy) Render(o any, _ string, row *model1.Row) error { raw, ok := o.(*unstructured.Unstructured) if !ok { return fmt.Errorf("expected Unstructured, but got %T", o) } if err := p.defaultRow(raw, row); err != nil { return err } if p.specs.isEmpty() { return nil } cols, err := p.specs.realize(raw, defaultNPHeader, row) if err != nil { return err } cols.hydrateRow(row) return nil } func (NetworkPolicy) defaultRow(raw *unstructured.Unstructured, r *model1.Row) error { var np netv1.NetworkPolicy err := runtime.DefaultUnstructuredConverter.FromUnstructured(raw.Object, &np) if err != nil { return err } ip, is, ib := ingress(np.Spec.Ingress) ep, es, eb := egress(np.Spec.Egress) var podSel string if len(np.Spec.PodSelector.MatchLabels) > 0 { podSel = mapToStr(np.Spec.PodSelector.MatchLabels) } if len(np.Spec.PodSelector.MatchExpressions) > 0 { podSel += "::" + expToStr(np.Spec.PodSelector.MatchExpressions) } r.ID = client.MetaFQN(&np.ObjectMeta) r.Fields = model1.Fields{ np.Namespace, np.Name, podSel, is, ip, ib, es, ep, eb, mapToStr(np.Labels), "", ToAge(np.GetCreationTimestamp()), } return nil } // Helpers... func ingress(ii []netv1.NetworkPolicyIngressRule) (port, selector, block string) { var ports, sels, blocks []string for _, i := range ii { if p := portsToStr(i.Ports); p != "" { ports = append(ports, p) } ll, pp := peersToStr(i.From) if ll != "" { sels = append(sels, ll) } if pp != "" { blocks = append(blocks, pp) } } return strings.Join(ports, ","), strings.Join(sels, ","), strings.Join(blocks, ",") } func egress(ee []netv1.NetworkPolicyEgressRule) (port, selector, block string) { var ports, sels, blocks []string for _, e := range ee { if p := portsToStr(e.Ports); p != "" { ports = append(ports, p) } ll, pp := peersToStr(e.To) if ll != "" { sels = append(sels, ll) } if pp != "" { blocks = append(blocks, pp) } } return strings.Join(ports, ","), strings.Join(sels, ","), strings.Join(blocks, ",") } func portsToStr(pp []netv1.NetworkPolicyPort) string { ports := make([]string, 0, len(pp)) for _, p := range pp { proto, port := NAValue, NAValue if p.Protocol != nil { proto = string(*p.Protocol) } if p.Port != nil { port = p.Port.String() } ports = append(ports, proto+":"+port) } return strings.Join(ports, ",") } func peersToStr(pp []netv1.NetworkPolicyPeer) (selector, ip string) { sels := make([]string, 0, len(pp)) ips := make([]string, 0, len(pp)) for _, p := range pp { if peer := renderPeer(p); peer != "" { sels = append(sels, peer) } if p.IPBlock == nil { continue } if b := renderBlock(p.IPBlock); b != "" { ips = append(ips, b) } } return strings.Join(sels, ","), strings.Join(ips, ",") } func renderBlock(b *netv1.IPBlock) string { s := b.CIDR if len(b.Except) == 0 { return s } e, more := b.Except, false if len(b.Except) > 2 { e, more = e[:2], true } if more { return s + "[" + strings.Join(e, ",") + "...]" } return s + "[" + strings.Join(b.Except, ",") + "]" } func renderPeer(i netv1.NetworkPolicyPeer) string { var s string if i.PodSelector != nil { if m := mapToStr(i.PodSelector.MatchLabels); m != "" { s += "po:" + m } if e := expToStr(i.PodSelector.MatchExpressions); e != "" { s += "--" + e } } if i.NamespaceSelector != nil { if m := mapToStr(i.NamespaceSelector.MatchLabels); m != "" { s += "ns:" + m } if e := expToStr(i.NamespaceSelector.MatchExpressions); e != "" { s += "--" + e } } return s } func expToStr(ee []metav1.LabelSelectorRequirement) string { ss := make([]string, len(ee)) for i, e := range ee { ss[i] = labToStr(e) } return strings.Join(ss, ",") } func labToStr(e metav1.LabelSelectorRequirement) string { return fmt.Sprintf("%s-%s%s", e.Key, e.Operator, strings.Join(e.Values, ",")) }