-
Notifications
You must be signed in to change notification settings - Fork 519
/
k8sobjectmatcher.go
139 lines (125 loc) · 4.38 KB
/
k8sobjectmatcher.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
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)
}