k8sobjectmatcher.go 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. package opencost
  2. import (
  3. "fmt"
  4. "github.com/opencost/opencost/core/pkg/filter/ast"
  5. kfilter "github.com/opencost/opencost/core/pkg/filter/k8sobject"
  6. "github.com/opencost/opencost/core/pkg/filter/matcher"
  7. "github.com/opencost/opencost/core/pkg/filter/transform"
  8. appsv1 "k8s.io/api/apps/v1"
  9. batchv1 "k8s.io/api/batch/v1"
  10. corev1 "k8s.io/api/core/v1"
  11. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  12. "k8s.io/apimachinery/pkg/runtime"
  13. )
  14. // K8sObjectMatcher is a matcher implementation for Kubernetes runtime.Object
  15. // instances, compiled using the matcher.MatchCompiler.
  16. type K8sObjectMatcher matcher.Matcher[runtime.Object]
  17. // NewK8sObjectMatchCompiler creates a new instance of a
  18. // matcher.MatchCompiler[runtime.Object] which can be used to compile
  19. // filter.Filter ASTs into matcher.Matcher[runtime.Object] implementations.
  20. //
  21. // If the label config is nil, the compiler will fail to compile alias filters
  22. // if any are present in the AST.
  23. func NewK8sObjectMatchCompiler() *matcher.MatchCompiler[runtime.Object] {
  24. passes := []transform.CompilerPass{}
  25. return matcher.NewMatchCompiler(
  26. k8sObjectFieldMap,
  27. k8sObjectSliceFieldMap,
  28. k8sObjectMapFieldMap,
  29. passes...,
  30. )
  31. }
  32. func objectMetaFromObject(o runtime.Object) (metav1.ObjectMeta, error) {
  33. switch v := o.(type) {
  34. case *appsv1.Deployment:
  35. return v.ObjectMeta, nil
  36. case *appsv1.StatefulSet:
  37. return v.ObjectMeta, nil
  38. case *appsv1.DaemonSet:
  39. return v.ObjectMeta, nil
  40. case *corev1.Pod:
  41. return v.ObjectMeta, nil
  42. case *batchv1.CronJob:
  43. return v.ObjectMeta, nil
  44. }
  45. return metav1.ObjectMeta{}, fmt.Errorf("currently-unsupported runtime.Object type for filtering: %T", o)
  46. }
  47. // Maps fields from an allocation to a string value based on an identifier
  48. func k8sObjectFieldMap(o runtime.Object, identifier ast.Identifier) (string, error) {
  49. if identifier.Field == nil {
  50. return "", fmt.Errorf("cannot map field from identifier with nil field")
  51. }
  52. m, err := objectMetaFromObject(o)
  53. if err != nil {
  54. return "", fmt.Errorf("retrieving object meta: %w", err)
  55. }
  56. var controllerKind string
  57. var controllerName string
  58. var pod string
  59. switch v := o.(type) {
  60. case *appsv1.Deployment:
  61. controllerKind = "deployment"
  62. controllerName = v.Name
  63. case *appsv1.StatefulSet:
  64. controllerKind = "statefulset"
  65. controllerName = v.Name
  66. case *appsv1.DaemonSet:
  67. controllerKind = "daemonset"
  68. controllerName = v.Name
  69. case *corev1.Pod:
  70. pod = v.Name
  71. if len(v.OwnerReferences) == 0 {
  72. controllerKind = "pod"
  73. controllerName = v.Name
  74. }
  75. case *batchv1.CronJob:
  76. controllerKind = "cronjob"
  77. controllerName = v.Name
  78. default:
  79. return "", fmt.Errorf("currently-unsupported runtime.Object type for filtering: %T", o)
  80. }
  81. // For now, we will just do our best to implement Allocation fields because
  82. // most k8s-based queries are on Allocation data. The other we will
  83. // eventually want to support is Asset, but I'm not sure that I have time
  84. // for that right now.
  85. field := kfilter.K8sObjectField(identifier.Field.Name)
  86. switch field {
  87. case kfilter.FieldNamespace:
  88. return m.Namespace, nil
  89. case kfilter.FieldControllerName:
  90. return controllerName, nil
  91. case kfilter.FieldControllerKind:
  92. return controllerKind, nil
  93. case kfilter.FieldPod:
  94. return pod, nil
  95. case kfilter.FieldLabel:
  96. if m.Labels != nil {
  97. return m.Labels[identifier.Key], nil
  98. }
  99. return "", nil
  100. case kfilter.FieldAnnotation:
  101. if m.Annotations != nil {
  102. return m.Annotations[identifier.Key], nil
  103. }
  104. return "", nil
  105. }
  106. return "", fmt.Errorf("Failed to find string identifier on K8sObject: %s (consider adding support if this is an expected field)", identifier.Field.Name)
  107. }
  108. // Maps slice fields from an allocation to a []string value based on an identifier
  109. func k8sObjectSliceFieldMap(o runtime.Object, identifier ast.Identifier) ([]string, error) {
  110. return nil, fmt.Errorf("K8sObject filters current have no supported []string identifiers")
  111. }
  112. // Maps map fields from an allocation to a map[string]string value based on an identifier
  113. func k8sObjectMapFieldMap(o runtime.Object, identifier ast.Identifier) (map[string]string, error) {
  114. m, err := objectMetaFromObject(o)
  115. if err != nil {
  116. return nil, fmt.Errorf("retrieving object meta: %w", err)
  117. }
  118. switch kfilter.K8sObjectField(identifier.Field.Name) {
  119. case kfilter.FieldLabel:
  120. return m.Labels, nil
  121. case kfilter.FieldAnnotation:
  122. return m.Annotations, nil
  123. }
  124. return nil, fmt.Errorf("Failed to find map[string]string identifier on K8sObject: %s", identifier.Field.Name)
  125. }