From b329c1618d9cb6dc99687b5ecb956c54955d7651 Mon Sep 17 00:00:00 2001 From: Justin Toh Date: Sat, 16 Oct 2021 22:16:45 +0800 Subject: [PATCH] feat: Support APIService relationships Signed-off-by: Justin Toh --- README.md | 1 + go.mod | 1 + go.sum | 2 ++ internal/graph/graph.go | 7 +++++ internal/graph/kubernetes.go | 31 +++++++++++++++++++-- internal/printers/printers_humanreadable.go | 27 +++++++++++++++++- 6 files changed, 66 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index ebacabe..a15c0a8 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ monitoring-system └── ServiceAccount/kube-state-metrics List of supported relationships used for discovering dependent objects: - Kubernetes + - [APIService References](https://kubernetes.io/docs/reference/kubernetes-api/cluster-resources/api-service-v1/) - [ClusterRole References](https://kubernetes.io/docs/reference/kubernetes-api/authorization-resources/cluster-role-v1/), [ClusterRoleBinding References](https://kubernetes.io/docs/reference/kubernetes-api/authorization-resources/cluster-role-binding-v1/), [Role References](https://kubernetes.io/docs/reference/kubernetes-api/authorization-resources/role-v1/) & [RoleBinding References](https://kubernetes.io/docs/reference/kubernetes-api/authorization-resources/role-binding-v1/) - [Controller References](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/api-machinery/controller-ref.md) & [Owner References](https://kubernetes.io/docs/concepts/overview/working-with-objects/owners-dependents/) - [CSINode References](https://kubernetes.io/docs/reference/kubernetes-api/config-and-storage-resources/csi-node-v1/) diff --git a/go.mod b/go.mod index ff146f5..172c7bc 100644 --- a/go.mod +++ b/go.mod @@ -12,5 +12,6 @@ require ( k8s.io/cli-runtime v0.22.1 k8s.io/client-go v0.22.1 k8s.io/klog/v2 v2.9.0 + k8s.io/kube-aggregator v0.22.1 k8s.io/kubectl v0.22.1 ) diff --git a/go.sum b/go.sum index e8f6388..608e818 100644 --- a/go.sum +++ b/go.sum @@ -1469,6 +1469,8 @@ k8s.io/klog/v2 v2.2.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= k8s.io/klog/v2 v2.9.0 h1:D7HV+n1V57XeZ0m6tdRkfknthUaM06VFbWldOFh8kzM= k8s.io/klog/v2 v2.9.0/go.mod h1:hy9LJ/NvuK+iVyP4Ehqva4HxZG/oXyIS3n3Jmire4Ec= +k8s.io/kube-aggregator v0.22.1 h1:hsntyWsnkLiL4ccmoKfqiUVyxnlnqtqPRMuq/mT2wGQ= +k8s.io/kube-aggregator v0.22.1/go.mod h1:VbmI+8fUeCPkzSvarWTrlIGEgUGEGI/66SFajDQ0Pdc= k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e h1:KLHHjkdQFomZy8+06csTWZ0m1343QqxZhR2LJ1OxCYM= k8s.io/kube-openapi v0.0.0-20210421082810-95288971da7e/go.mod h1:vHXdDvt9+2spS2Rx9ql3I8tycm3H9FDfdUoIuKCefvw= diff --git a/internal/graph/graph.go b/internal/graph/graph.go index 9b0565e..6f35d5f 100644 --- a/internal/graph/graph.go +++ b/internal/graph/graph.go @@ -486,6 +486,13 @@ func ResolveDependents(m meta.RESTMapper, objects []unstructuredv1.Unstructured, klog.V(4).Infof("Failed to get relationships for validatingwebhookconfiguration named \"%s\": %s", node.Name, err) continue } + // Populate dependents based on APIService relationships + case node.Group == "apiregistration.k8s.io" && node.Kind == "APIService": + rmap, err = getAPIServiceRelationships(node) + if err != nil { + klog.V(4).Infof("Failed to get relationships for apiservice named \"%s\": %s", node.Name, err) + continue + } // Populate dependents based on Event relationships case (node.Group == "events.k8s.io" || node.Group == "") && node.Kind == "Event": rmap, err = getEventRelationships(node) diff --git a/internal/graph/kubernetes.go b/internal/graph/kubernetes.go index 719f053..f8c6ff6 100644 --- a/internal/graph/kubernetes.go +++ b/internal/graph/kubernetes.go @@ -17,9 +17,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" ) const ( + // Kubernetes APIService relationships. + RelationshipAPIService Relationship = "APIService" + // Kubernetes ClusterRole, ClusterRoleBinding, RoleBinding relationships. RelationshipClusterRoleAggregationRule Relationship = "ClusterRoleAggregationRule" RelationshipClusterRolePolicyRule Relationship = "ClusterRolePolicyRule" @@ -99,6 +103,29 @@ const ( RelationshipVolumeAttachmentSourceVolumeStorageClass Relationship = "VolumeAttachmentSourceVolumeStorageClass" ) +// getAPIServiceRelationships returns a map of relationships that this +// APIService has with other objects, based on what was referenced in its +// manifest. +func getAPIServiceRelationships(n *Node) (*RelationshipMap, error) { + var apisvc apiregistrationv1.APIService + err := runtime.DefaultUnstructuredConverter.FromUnstructured(n.UnstructuredContent(), &apisvc) + if err != nil { + return nil, err + } + + // var os ObjectSelector + var ref ObjectReference + result := newRelationshipMap() + + // RelationshipAPIService + if svc := apisvc.Spec.Service; svc != nil { + ref = ObjectReference{Kind: "Service", Namespace: svc.Namespace, Name: svc.Name} + result.AddDependencyByKey(ref.Key(), RelationshipAPIService) + } + + return &result, nil +} + // getClusterRoleRelationships returns a map of relationships that this // ClusterRole has with other objects, based on what was referenced in // its manifest. @@ -109,8 +136,8 @@ func getClusterRoleRelationships(n *Node) (*RelationshipMap, error) { return nil, err } - var ols ObjectLabelSelector var os ObjectSelector + var ols ObjectLabelSelector var ref ObjectReference result := newRelationshipMap() @@ -645,8 +672,8 @@ func getRoleRelationships(n *Node) (*RelationshipMap, error) { if err != nil { return nil, err } - var os ObjectSelector + var ref ObjectReference result := newRelationshipMap() diff --git a/internal/printers/printers_humanreadable.go b/internal/printers/printers_humanreadable.go index eb19d4a..e9504ca 100644 --- a/internal/printers/printers_humanreadable.go +++ b/internal/printers/printers_humanreadable.go @@ -18,6 +18,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/duration" "k8s.io/client-go/util/jsonpath" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" "github.com/tohjustin/kube-lineage/internal/graph" ) @@ -160,6 +161,28 @@ func getObjectReadyStatus(u *unstructuredv1.Unstructured) (string, string, error return ready, status, nil } +// getAPIServiceReadyStatus returns the ready & status value of a APIService +// which is based off the table cell values computed by printAPIService from +// https://github.com/kubernetes/kubernetes/blob/v1.22.1/pkg/printers/internalversion/printers.go. +func getAPIServiceReadyStatus(u *unstructuredv1.Unstructured) (string, string, error) { + var apisvc apiregistrationv1.APIService + err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.UnstructuredContent(), &apisvc) + if err != nil { + return "", "", err + } + var ready, status string + for _, condition := range apisvc.Status.Conditions { + if condition.Type == apiregistrationv1.Available { + ready = string(condition.Status) + if condition.Status != apiregistrationv1.ConditionTrue { + status = condition.Reason + } + } + } + + return ready, status, nil +} + // getDaemonSetReadyStatus returns the ready & status value of a DaemonSet // which is based off the table cell values computed by printDaemonSet from // https://github.com/kubernetes/kubernetes/blob/v1.22.1/pkg/printers/internalversion/printers.go. @@ -413,7 +436,7 @@ func getVolumeAttachmentReadyStatus(u *unstructuredv1.Unstructured) (string, str } // nodeToTableRow converts the provided node into a table row. -//nolint:gocognit,goconst +//nolint:funlen,gocognit,goconst func nodeToTableRow(node *graph.Node, rset graph.RelationshipSet, namePrefix string, showGroupFn func(kind string) bool) metav1.TableRow { var name, ready, status, age string var relationships interface{} @@ -443,6 +466,8 @@ func nodeToTableRow(node *graph.Node, rset graph.RelationshipSet, namePrefix str ready, status, _ = getStatefulSetReadyStatus(node.Unstructured) case node.Group == "policy" && node.Kind == "PodDisruptionBudget": ready, status, _ = getPodDisruptionBudgetReadyStatus(node.Unstructured) + case node.Group == "apiregistration.k8s.io" && node.Kind == "APIService": + ready, status, _ = getAPIServiceReadyStatus(node.Unstructured) case node.Group == "events.k8s.io" && node.Kind == "Event": ready, status, _ = getEventReadyStatus(node.Unstructured) case node.Group == "storage.k8s.io" && node.Kind == "VolumeAttachment":