|
|
@@ -0,0 +1,139 @@
|
|
|
+package opencost
|
|
|
+
|
|
|
+import (
|
|
|
+ "fmt"
|
|
|
+
|
|
|
+ "github.com/opencost/opencost/core/pkg/filter/ast"
|
|
|
+ kfilter "github.com/opencost/opencost/core/pkg/filter/k8sobject"
|
|
|
+ "github.com/opencost/opencost/core/pkg/filter/matcher"
|
|
|
+ "github.com/opencost/opencost/core/pkg/filter/transform"
|
|
|
+ appsv1 "k8s.io/api/apps/v1"
|
|
|
+ batchv1 "k8s.io/api/batch/v1"
|
|
|
+ corev1 "k8s.io/api/core/v1"
|
|
|
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
+ "k8s.io/apimachinery/pkg/runtime"
|
|
|
+)
|
|
|
+
|
|
|
+// K8sObjectMatcher is a matcher implementation for Kubernetes runtime.Object
|
|
|
+// instances, compiled using the matcher.MatchCompiler.
|
|
|
+type K8sObjectMatcher matcher.Matcher[runtime.Object]
|
|
|
+
|
|
|
+// NewK8sObjectMatchCompiler creates a new instance of a
|
|
|
+// matcher.MatchCompiler[runtime.Object] which can be used to compile
|
|
|
+// filter.Filter ASTs into matcher.Matcher[runtime.Object] implementations.
|
|
|
+//
|
|
|
+// If the label config is nil, the compiler will fail to compile alias filters
|
|
|
+// if any are present in the AST.
|
|
|
+func NewK8sObjectMatchCompiler() *matcher.MatchCompiler[runtime.Object] {
|
|
|
+ passes := []transform.CompilerPass{}
|
|
|
+
|
|
|
+ return matcher.NewMatchCompiler(
|
|
|
+ k8sObjectFieldMap,
|
|
|
+ k8sObjectSliceFieldMap,
|
|
|
+ k8sObjectMapFieldMap,
|
|
|
+ passes...,
|
|
|
+ )
|
|
|
+}
|
|
|
+
|
|
|
+func objectMetaFromObject(o runtime.Object) (metav1.ObjectMeta, error) {
|
|
|
+ switch v := o.(type) {
|
|
|
+ case *appsv1.Deployment:
|
|
|
+ return v.ObjectMeta, nil
|
|
|
+ case *appsv1.StatefulSet:
|
|
|
+ return v.ObjectMeta, nil
|
|
|
+ case *appsv1.DaemonSet:
|
|
|
+ return v.ObjectMeta, nil
|
|
|
+ case *corev1.Pod:
|
|
|
+ return v.ObjectMeta, nil
|
|
|
+ case *batchv1.CronJob:
|
|
|
+ return v.ObjectMeta, nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return metav1.ObjectMeta{}, fmt.Errorf("currently-unsupported runtime.Object type for filtering: %T", o)
|
|
|
+}
|
|
|
+
|
|
|
+// Maps fields from an allocation to a string value based on an identifier
|
|
|
+func k8sObjectFieldMap(o runtime.Object, identifier ast.Identifier) (string, error) {
|
|
|
+ if identifier.Field == nil {
|
|
|
+ return "", fmt.Errorf("cannot map field from identifier with nil field")
|
|
|
+ }
|
|
|
+
|
|
|
+ m, err := objectMetaFromObject(o)
|
|
|
+ if err != nil {
|
|
|
+ return "", fmt.Errorf("retrieving object meta: %w", err)
|
|
|
+ }
|
|
|
+ var controllerKind string
|
|
|
+ var controllerName string
|
|
|
+ var pod string
|
|
|
+
|
|
|
+ switch v := o.(type) {
|
|
|
+ case *appsv1.Deployment:
|
|
|
+ controllerKind = "deployment"
|
|
|
+ controllerName = v.Name
|
|
|
+ case *appsv1.StatefulSet:
|
|
|
+ controllerKind = "statefulset"
|
|
|
+ controllerName = v.Name
|
|
|
+ case *appsv1.DaemonSet:
|
|
|
+ controllerKind = "daemonset"
|
|
|
+ controllerName = v.Name
|
|
|
+ case *corev1.Pod:
|
|
|
+ pod = v.Name
|
|
|
+ if len(v.OwnerReferences) == 0 {
|
|
|
+ controllerKind = "pod"
|
|
|
+ controllerName = v.Name
|
|
|
+ }
|
|
|
+ case *batchv1.CronJob:
|
|
|
+ controllerKind = "cronjob"
|
|
|
+ controllerName = v.Name
|
|
|
+ default:
|
|
|
+ return "", fmt.Errorf("currently-unsupported runtime.Object type for filtering: %T", o)
|
|
|
+ }
|
|
|
+
|
|
|
+ // For now, we will just do our best to implement Allocation fields because
|
|
|
+ // most k8s-based queries are on Allocation data. The other we will
|
|
|
+ // eventually want to support is Asset, but I'm not sure that I have time
|
|
|
+ // for that right now.
|
|
|
+ field := kfilter.K8sObjectField(identifier.Field.Name)
|
|
|
+ switch field {
|
|
|
+ case kfilter.FieldNamespace:
|
|
|
+ return m.Namespace, nil
|
|
|
+ case kfilter.FieldControllerName:
|
|
|
+ return controllerName, nil
|
|
|
+ case kfilter.FieldControllerKind:
|
|
|
+ return controllerKind, nil
|
|
|
+ case kfilter.FieldPod:
|
|
|
+ return pod, nil
|
|
|
+ case kfilter.FieldLabel:
|
|
|
+ if m.Labels != nil {
|
|
|
+ return m.Labels[identifier.Key], nil
|
|
|
+ }
|
|
|
+ return "", nil
|
|
|
+ case kfilter.FieldAnnotation:
|
|
|
+ if m.Annotations != nil {
|
|
|
+ return m.Annotations[identifier.Key], nil
|
|
|
+ }
|
|
|
+ return "", nil
|
|
|
+ }
|
|
|
+
|
|
|
+ return "", fmt.Errorf("Failed to find string identifier on K8sObject: %s (consider adding support if this is an expected field)", identifier.Field.Name)
|
|
|
+}
|
|
|
+
|
|
|
+// Maps slice fields from an allocation to a []string value based on an identifier
|
|
|
+func k8sObjectSliceFieldMap(o runtime.Object, identifier ast.Identifier) ([]string, error) {
|
|
|
+ return nil, fmt.Errorf("K8sObject filters current have no supported []string identifiers")
|
|
|
+}
|
|
|
+
|
|
|
+// Maps map fields from an allocation to a map[string]string value based on an identifier
|
|
|
+func k8sObjectMapFieldMap(o runtime.Object, identifier ast.Identifier) (map[string]string, error) {
|
|
|
+ m, err := objectMetaFromObject(o)
|
|
|
+ if err != nil {
|
|
|
+ return nil, fmt.Errorf("retrieving object meta: %w", err)
|
|
|
+ }
|
|
|
+ switch kfilter.K8sObjectField(identifier.Field.Name) {
|
|
|
+ case kfilter.FieldLabel:
|
|
|
+ return m.Labels, nil
|
|
|
+ case kfilter.FieldAnnotation:
|
|
|
+ return m.Annotations, nil
|
|
|
+ }
|
|
|
+ return nil, fmt.Errorf("Failed to find map[string]string identifier on K8sObject: %s", identifier.Field.Name)
|
|
|
+}
|