Skip to content

Commit

Permalink
Add command to show CiliumIdentity statistics (#15)
Browse files Browse the repository at this point in the history
* Add command to show CiliumIdentity statistics

Signed-off-by: Daichi Sakaue <[email protected]>
  • Loading branch information
yokaze authored Nov 19, 2024
1 parent f0971b0 commit 98c7b0f
Show file tree
Hide file tree
Showing 6 changed files with 265 additions and 20 deletions.
35 changes: 15 additions & 20 deletions cmd/npv/app/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,18 @@ const (

var cachedCiliumClients map[string]*client.Client

var gvrEndpoint schema.GroupVersionResource = schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumendpoints",
}

var gvrIdentity schema.GroupVersionResource = schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumidentities",
}

func init() {
cachedCiliumClients = make(map[string]*client.Client)
}
Expand Down Expand Up @@ -94,13 +106,7 @@ func getProxyEndpoint(ctx context.Context, c *kubernetes.Clientset, namespace, n
}

func getPodEndpointID(ctx context.Context, d *dynamic.DynamicClient, namespace, name string) (int64, error) {
gvr := schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumendpoints",
}

ep, err := d.Resource(gvr).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
ep, err := d.Resource(gvrEndpoint).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
if err != nil {
return 0, err
}
Expand All @@ -119,12 +125,7 @@ func getPodEndpointID(ctx context.Context, d *dynamic.DynamicClient, namespace,
// key: identity number
// value: CiliumIdentity resource
func getIdentityResourceMap(ctx context.Context, d *dynamic.DynamicClient) (map[int]*unstructured.Unstructured, error) {
gvr := schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumidentities",
}
li, err := d.Resource(gvr).List(ctx, metav1.ListOptions{})
li, err := d.Resource(gvrIdentity).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
Expand All @@ -143,13 +144,7 @@ func getIdentityResourceMap(ctx context.Context, d *dynamic.DynamicClient) (map[
// key: identity number
// value: example pod name
func getIdentityExampleMap(ctx context.Context, d *dynamic.DynamicClient) (map[int]string, error) {
gvr := schema.GroupVersionResource{
Group: "cilium.io",
Version: "v2",
Resource: "ciliumendpoints",
}

li, err := d.Resource(gvr).Namespace(corev1.NamespaceAll).List(ctx, metav1.ListOptions{})
li, err := d.Resource(gvrEndpoint).Namespace(corev1.NamespaceAll).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
Expand Down
13 changes: 13 additions & 0 deletions cmd/npv/app/id.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package app

import "github.com/spf13/cobra"

func init() {
rootCmd.AddCommand(idCmd)
}

var idCmd = &cobra.Command{
Use: "id",
Short: "Inspect CiliumIdentity",
Long: `Inspect CiliumIdentity`,
}
108 changes: 108 additions & 0 deletions cmd/npv/app/id_label.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package app

import (
"context"
"encoding/json"
"fmt"
"io"
"maps"
"slices"
"sort"
"strings"
"text/tabwriter"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func init() {
idCmd.AddCommand(idLabelCmd)
}

var idLabelCmd = &cobra.Command{
Use: "label",
Short: "Show label cardinarity of CiliumIdentity",
Long: `Show label cardinarity of CiliumIdentity`,

Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
return runIdLabel(context.Background(), cmd.OutOrStdout())
},
}

func runIdLabel(ctx context.Context, w io.Writer) error {
_, dynamicClient, err := createK8sClients()
if err != nil {
return err
}

li, err := dynamicClient.Resource(gvrIdentity).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}

labelMap := make(map[string][]string)
for _, item := range li.Items {
ns, ok, err := unstructured.NestedString(item.Object, "security-labels", "k8s:io.kubernetes.pod.namespace")
if err != nil {
return err
}
if !ok {
continue
}
if ns != rootOptions.namespace {
continue
}

labels, _, err := unstructured.NestedStringMap(item.Object, "security-labels")
if err != nil {
return err
}
for k, v := range labels {
// These labels do not vary in a single namespace, so their cardinality is always one
if k == "k8s:io.cilium.k8s.policy.cluster" ||
k == "k8s:io.kubernetes.pod.namespace" ||
strings.HasPrefix(k, "k8s:io.cilium.k8s.namespace.labels") {
continue
}
if _, ok := labelMap[k]; !ok {
labelMap[k] = make([]string, 0, 1)
}
labelMap[k] = append(labelMap[k], v)
}
}

for k := range labelMap {
sort.Strings(labelMap[k])
labelMap[k] = slices.Compact(labelMap[k])
}
keys := slices.Collect(maps.Keys(labelMap))
sort.Strings(keys)

switch rootOptions.output {
case OutputJson:
text, err := json.MarshalIndent(labelMap, "", " ")
if err != nil {
return err
}
_, err = w.Write(text)
return err
case OutputSimple:
tw := tabwriter.NewWriter(w, 0, 1, 1, ' ', 0)
if !rootOptions.noHeaders {
if _, err := tw.Write([]byte("LABEL\tCOUNT\tVALUES\n")); err != nil {
return err
}
}
for _, k := range keys {
li := labelMap[k]
if _, err := tw.Write([]byte(fmt.Sprintf("%v\t%v\t%v\n", k, len(li), strings.Join(li, ",")))); err != nil {
return err
}
}
return tw.Flush()
default:
return fmt.Errorf("unknown format: %s", rootOptions.output)
}
}
83 changes: 83 additions & 0 deletions cmd/npv/app/id_summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package app

import (
"context"
"encoding/json"
"fmt"
"io"
"maps"
"slices"
"sort"
"text/tabwriter"

"github.com/spf13/cobra"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)

func init() {
idCmd.AddCommand(idSummaryCmd)
}

var idSummaryCmd = &cobra.Command{
Use: "summary",
Short: "Count CiliumIdentity by namespace",
Long: `Count CiliumIdentity by namespace`,

Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) error {
return runIdSummary(context.Background(), cmd.OutOrStdout())
},
}

func runIdSummary(ctx context.Context, w io.Writer) error {
_, dynamicClient, err := createK8sClients()
if err != nil {
return err
}

li, err := dynamicClient.Resource(gvrIdentity).List(ctx, metav1.ListOptions{})
if err != nil {
return err
}

countMap := make(map[string]int)
for _, item := range li.Items {
ns, ok, err := unstructured.NestedString(item.Object, "security-labels", "k8s:io.kubernetes.pod.namespace")
if err != nil {
return err
}
if !ok {
continue
}
countMap[ns] += 1
}

keys := slices.Collect(maps.Keys(countMap))
sort.Strings(keys)

switch rootOptions.output {
case OutputJson:
text, err := json.MarshalIndent(countMap, "", " ")
if err != nil {
return err
}
_, err = w.Write(text)
return err
case OutputSimple:
tw := tabwriter.NewWriter(w, 0, 1, 1, ' ', 0)
if !rootOptions.noHeaders {
if _, err := tw.Write([]byte("NAMESPACE\tIDENTITY\n")); err != nil {
return err
}
}
for _, k := range keys {
if _, err := tw.Write([]byte(fmt.Sprintf("%v\t%v\n", k, countMap[k]))); err != nil {
return err
}
}
return tw.Flush()
default:
return fmt.Errorf("unknown format: %s", rootOptions.output)
}
}
44 changes: 44 additions & 0 deletions e2e/id_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package e2e

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func testIdLabel() {
expected := `{
"k8s:group": [
"test"
],
"k8s:io.cilium.k8s.policy.serviceaccount": [
"default"
],
"k8s:test": [
"l3-egress-explicit-deny-all",
"l3-egress-implicit-deny-all",
"l3-ingress-explicit-allow-all",
"l3-ingress-explicit-deny-all",
"l3-ingress-implicit-deny-all",
"l4-egress-explicit-deny-any",
"l4-egress-explicit-deny-tcp",
"l4-ingress-explicit-allow-any",
"l4-ingress-explicit-allow-tcp",
"l4-ingress-explicit-deny-any",
"l4-ingress-explicit-deny-udp",
"self"
]
}`
It("should show Security Identity label cardinality", func() {
result := runViewerSafe(Default, nil, "id", "label", "-n=test", "-o=json")
Expect(string(result)).To(Equal(expected))
})
}

func testIdSummary() {
expected := `{"default":1,"kube-system":2,"local-path-storage":1,"test":12}`
It("should show ID summary", func() {
result := runViewerSafe(Default, nil, "id", "summary", "-o=json")
result = jqSafe(Default, result, "-c")
Expect(string(result)).To(Equal(expected))
})
}
2 changes: 2 additions & 0 deletions e2e/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ func runTest() {
Context("dump", testDump)
Context("list", testList)
Context("list-manifests", testListManifests)
Context("id-label", testIdLabel)
Context("id-summary", testIdSummary)
Context("inspect", testInspect)
Context("summary", testSummary)
}

0 comments on commit 98c7b0f

Please sign in to comment.