filterutil.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757
  1. package filterutil
  2. import (
  3. "strings"
  4. "github.com/opencost/opencost/pkg/costmodel/clusters"
  5. "github.com/opencost/opencost/pkg/kubecost"
  6. "github.com/opencost/opencost/pkg/log"
  7. "github.com/opencost/opencost/pkg/prom"
  8. "github.com/opencost/opencost/pkg/util/mapper"
  9. "github.com/opencost/opencost/pkg/util/typeutil"
  10. filter "github.com/opencost/opencost/pkg/filter21"
  11. afilter "github.com/opencost/opencost/pkg/filter21/allocation"
  12. assetfilter "github.com/opencost/opencost/pkg/filter21/asset"
  13. "github.com/opencost/opencost/pkg/filter21/ast"
  14. // cloudfilter "github.com/opencost/opencost/pkg/filter/cloud"
  15. )
  16. // ============================================================================
  17. // This file contains:
  18. // Parsing (HTTP query params -> v2.1 filter) for V1 of query param filters
  19. //
  20. // e.g. "filterNamespaces=ku&filterControllers=deployment:kc"
  21. // ============================================================================
  22. // This is somewhat of a fancy solution, but allows us to "register" DefaultFieldByName funcs
  23. // funcs by Field type.
  24. var defaultFieldByType = map[string]any{
  25. // typeutil.TypeOf[cloudfilter.CloudAggregationField](): cloudfilter.DefaultFieldByName,
  26. typeutil.TypeOf[afilter.AllocationField](): afilter.DefaultFieldByName,
  27. typeutil.TypeOf[assetfilter.AssetField](): assetfilter.DefaultFieldByName,
  28. }
  29. // DefaultFieldByName looks up a specific T field instance by name and returns the default
  30. // ast.Field value for that type.
  31. func DefaultFieldByName[T ~string](field T) *ast.Field {
  32. lookup, ok := defaultFieldByType[typeutil.TypeOf[T]()]
  33. if !ok {
  34. log.Errorf("Failed to get default field lookup for: %s", typeutil.TypeOf[T]())
  35. return nil
  36. }
  37. defaultLookup, ok := lookup.(func(T) *ast.Field)
  38. if !ok {
  39. log.Errorf("Failed to cast default field lookup for: %s", typeutil.TypeOf[T]())
  40. return nil
  41. }
  42. return defaultLookup(field)
  43. }
  44. const (
  45. ParamFilterClusters = "filterClusters"
  46. ParamFilterNodes = "filterNodes"
  47. ParamFilterNamespaces = "filterNamespaces"
  48. ParamFilterControllerKinds = "filterControllerKinds"
  49. ParamFilterControllers = "filterControllers"
  50. ParamFilterPods = "filterPods"
  51. ParamFilterContainers = "filterContainers"
  52. ParamFilterDepartments = "filterDepartments"
  53. ParamFilterEnvironments = "filterEnvironments"
  54. ParamFilterOwners = "filterOwners"
  55. ParamFilterProducts = "filterProducts"
  56. ParamFilterTeams = "filterTeams"
  57. ParamFilterAnnotations = "filterAnnotations"
  58. ParamFilterLabels = "filterLabels"
  59. ParamFilterServices = "filterServices"
  60. ParamFilterAccounts = "filterAccounts"
  61. ParamFilterCategories = "filterCategories"
  62. ParamFilterNames = "filterNames"
  63. ParamFilterProjects = "filterProjects"
  64. ParamFilterProviders = "filterProviders"
  65. ParamFilterProviderIDs = "filterProviderIDs"
  66. ParamFilterProviderIDsV2 = "filterProviderIds"
  67. ParamFilterRegions = "filterRegions"
  68. ParamFilterTypes = "filterTypes"
  69. )
  70. // ValidAssetFilterParams returns a list of all possible filter parameters
  71. func ValidAssetFilterParams() []string {
  72. return []string{
  73. ParamFilterAccounts,
  74. ParamFilterCategories,
  75. ParamFilterClusters,
  76. ParamFilterLabels,
  77. ParamFilterNames,
  78. ParamFilterProjects,
  79. ParamFilterProviders,
  80. ParamFilterProviderIDs,
  81. ParamFilterProviderIDsV2,
  82. ParamFilterRegions,
  83. ParamFilterServices,
  84. ParamFilterTypes,
  85. }
  86. }
  87. // AllocationPropToV1FilterParamKey maps allocation string property
  88. // representations to v1 filter param keys for legacy filter config support
  89. // (e.g. reports). Example mapping: "cluster" -> "filterClusters"
  90. var AllocationPropToV1FilterParamKey = map[string]string{
  91. kubecost.AllocationClusterProp: ParamFilterClusters,
  92. kubecost.AllocationNodeProp: ParamFilterNodes,
  93. kubecost.AllocationNamespaceProp: ParamFilterNamespaces,
  94. kubecost.AllocationControllerProp: ParamFilterControllers,
  95. kubecost.AllocationControllerKindProp: ParamFilterControllerKinds,
  96. kubecost.AllocationPodProp: ParamFilterPods,
  97. kubecost.AllocationLabelProp: ParamFilterLabels,
  98. kubecost.AllocationServiceProp: ParamFilterServices,
  99. kubecost.AllocationDepartmentProp: ParamFilterDepartments,
  100. kubecost.AllocationEnvironmentProp: ParamFilterEnvironments,
  101. kubecost.AllocationOwnerProp: ParamFilterOwners,
  102. kubecost.AllocationProductProp: ParamFilterProducts,
  103. kubecost.AllocationTeamProp: ParamFilterTeams,
  104. }
  105. // Map to store Kubecost Asset property to Asset Filter types.
  106. // AssetPropToV1FilterParamKey maps asset string property representations to v1
  107. // filter param keys for legacy filter config support (e.g. reports). Example
  108. // mapping: "category" -> "filterCategories"
  109. var AssetPropToV1FilterParamKey = map[kubecost.AssetProperty]string{
  110. kubecost.AssetNameProp: ParamFilterNames,
  111. kubecost.AssetTypeProp: ParamFilterTypes,
  112. kubecost.AssetAccountProp: ParamFilterAccounts,
  113. kubecost.AssetCategoryProp: ParamFilterCategories,
  114. kubecost.AssetClusterProp: ParamFilterClusters,
  115. kubecost.AssetProjectProp: ParamFilterProjects,
  116. kubecost.AssetProviderProp: ParamFilterProviders,
  117. kubecost.AssetProviderIDProp: ParamFilterProviderIDs,
  118. kubecost.AssetServiceProp: ParamFilterServices,
  119. }
  120. // AllHTTPParamKeys returns all HTTP GET parameters used for v1 filters. It is
  121. // intended to help validate HTTP queries in handlers to help avoid e.g.
  122. // spelling errors.
  123. func AllHTTPParamKeys() []string {
  124. return []string{
  125. ParamFilterClusters,
  126. ParamFilterNodes,
  127. ParamFilterNamespaces,
  128. ParamFilterControllerKinds,
  129. ParamFilterControllers,
  130. ParamFilterPods,
  131. ParamFilterContainers,
  132. ParamFilterDepartments,
  133. ParamFilterEnvironments,
  134. ParamFilterOwners,
  135. ParamFilterProducts,
  136. ParamFilterTeams,
  137. ParamFilterAnnotations,
  138. ParamFilterLabels,
  139. ParamFilterServices,
  140. }
  141. }
  142. // AllocationFilterFromParamsV1 takes a set of HTTP query parameters and
  143. // converts them to an AllocationFilter, which is a structured in-Go
  144. // representation of a set of filters.
  145. //
  146. // The HTTP query parameters are the "v1" filters attached to the Allocation
  147. // API: "filterNamespaces=", "filterNodes=", etc.
  148. //
  149. // It takes an optional LabelConfig, which if provided enables "label-mapped"
  150. // filters like "filterDepartments".
  151. //
  152. // It takes an optional ClusterMap, which if provided enables cluster name
  153. // filtering. This turns all `filterClusters=foo` arguments into the equivalent
  154. // of `clusterID = "foo" OR clusterName = "foo"`.
  155. func AllocationFilterFromParamsV1(
  156. params AllocationFilterV1,
  157. labelConfig *kubecost.LabelConfig,
  158. clusterMap clusters.ClusterMap,
  159. ) filter.Filter {
  160. var filterOps []ast.FilterNode
  161. // ClusterMap does not provide a cluster name -> cluster ID mapping in the
  162. // interface, probably because there could be multiple IDs with the same
  163. // name. However, V1 filter logic demands that the parameters to
  164. // filterClusters= be checked against both cluster ID AND cluster name.
  165. //
  166. // To support expected filterClusters= behavior, we construct a mapping
  167. // of cluster name -> cluster IDs (could be multiple IDs for the same name)
  168. // so that we can create AllocationFilters that use only ClusterIDEquals.
  169. //
  170. //
  171. // AllocationFilter intentionally does not support cluster name filters
  172. // because those should be considered presentation-layer only.
  173. clusterNameToIDs := map[string][]string{}
  174. if clusterMap != nil {
  175. cMap := clusterMap.AsMap()
  176. for _, info := range cMap {
  177. if info == nil {
  178. continue
  179. }
  180. if _, ok := clusterNameToIDs[info.Name]; ok {
  181. clusterNameToIDs[info.Name] = append(clusterNameToIDs[info.Name], info.ID)
  182. } else {
  183. clusterNameToIDs[info.Name] = []string{info.ID}
  184. }
  185. }
  186. }
  187. // The proliferation of > 0 guards in the function is to avoid constructing
  188. // empty filter structs. While it is functionally equivalent to add empty
  189. // filter structs (they evaluate to true always) there could be overhead
  190. // when calling Matches() repeatedly for no purpose.
  191. if len(params.Clusters) > 0 {
  192. var ops []ast.FilterNode
  193. // filter my cluster identifier
  194. ops = push(ops, filterV1SingleValueFromList(params.Clusters, afilter.FieldClusterID))
  195. for _, rawFilterValue := range params.Clusters {
  196. clusterNameFilter, wildcard := parseWildcardEnd(rawFilterValue)
  197. clusterIDsToFilter := []string{}
  198. for clusterName := range clusterNameToIDs {
  199. if wildcard && strings.HasPrefix(clusterName, clusterNameFilter) {
  200. clusterIDsToFilter = append(clusterIDsToFilter, clusterNameToIDs[clusterName]...)
  201. } else if !wildcard && clusterName == clusterNameFilter {
  202. clusterIDsToFilter = append(clusterIDsToFilter, clusterNameToIDs[clusterName]...)
  203. }
  204. }
  205. for _, clusterID := range clusterIDsToFilter {
  206. ops = append(ops, &ast.EqualOp{
  207. Left: ast.Identifier{
  208. Field: afilter.DefaultFieldByName(afilter.FieldClusterID),
  209. Key: "",
  210. },
  211. Right: clusterID,
  212. })
  213. }
  214. }
  215. //
  216. clustersOp := opsToOr(ops)
  217. filterOps = push(filterOps, clustersOp)
  218. }
  219. if len(params.Nodes) > 0 {
  220. filterOps = push(filterOps, filterV1SingleValueFromList(params.Nodes, afilter.FieldNode))
  221. }
  222. if len(params.Namespaces) > 0 {
  223. filterOps = push(filterOps, filterV1SingleValueFromList(params.Namespaces, afilter.FieldNamespace))
  224. }
  225. if len(params.ControllerKinds) > 0 {
  226. filterOps = push(filterOps, filterV1SingleValueFromList(params.ControllerKinds, afilter.FieldControllerKind))
  227. }
  228. // filterControllers= accepts controllerkind:controllername filters, e.g.
  229. // "deployment:kubecost-cost-analyzer"
  230. //
  231. // Thus, we have to make a custom OR filter for this condition.
  232. if len(params.Controllers) > 0 {
  233. var ops []ast.FilterNode
  234. for _, rawFilterValue := range params.Controllers {
  235. split := strings.Split(rawFilterValue, ":")
  236. if len(split) == 1 {
  237. filterValue, wildcard := parseWildcardEnd(split[0])
  238. subFilter := toEqualOp(afilter.FieldControllerName, "", filterValue, wildcard)
  239. ops = append(ops, subFilter)
  240. } else if len(split) == 2 {
  241. kindFilterVal := split[0]
  242. nameFilterVal, wildcard := parseWildcardEnd(split[1])
  243. kindFilter := toEqualOp(afilter.FieldControllerKind, "", kindFilterVal, false)
  244. nameFilter := toEqualOp(afilter.FieldControllerName, "", nameFilterVal, wildcard)
  245. // The controller name AND the controller kind must match
  246. ops = append(ops, &ast.AndOp{
  247. Operands: []ast.FilterNode{
  248. kindFilter,
  249. nameFilter,
  250. },
  251. })
  252. } else {
  253. log.Warnf("illegal filter for controller: %s", rawFilterValue)
  254. }
  255. }
  256. controllersOp := opsToOr(ops)
  257. filterOps = push(filterOps, controllersOp)
  258. }
  259. if len(params.Pods) > 0 {
  260. filterOps = push(filterOps, filterV1SingleValueFromList(params.Pods, afilter.FieldPod))
  261. }
  262. if len(params.Containers) > 0 {
  263. filterOps = push(filterOps, filterV1SingleValueFromList(params.Containers, afilter.FieldContainer))
  264. }
  265. // Label-mapped queries require a label config to be present.
  266. if labelConfig != nil {
  267. if len(params.Departments) > 0 {
  268. filterOps = push(filterOps, filterV1LabelAliasMappedFromList(params.Departments, labelConfig.DepartmentLabel))
  269. }
  270. if len(params.Environments) > 0 {
  271. filterOps = push(filterOps, filterV1LabelAliasMappedFromList(params.Environments, labelConfig.EnvironmentLabel))
  272. }
  273. if len(params.Owners) > 0 {
  274. filterOps = push(filterOps, filterV1LabelAliasMappedFromList(params.Owners, labelConfig.OwnerLabel))
  275. }
  276. if len(params.Products) > 0 {
  277. filterOps = push(filterOps, filterV1LabelAliasMappedFromList(params.Products, labelConfig.ProductLabel))
  278. }
  279. if len(params.Teams) > 0 {
  280. filterOps = push(filterOps, filterV1LabelAliasMappedFromList(params.Teams, labelConfig.TeamLabel))
  281. }
  282. } else {
  283. log.Debugf("No label config is available. Not creating filters for label-mapped 'fields'.")
  284. }
  285. if len(params.Annotations) > 0 {
  286. filterOps = push(filterOps, filterV1DoubleValueFromList(params.Annotations, afilter.FieldAnnotation))
  287. }
  288. if len(params.Labels) > 0 {
  289. filterOps = push(filterOps, filterV1DoubleValueFromList(params.Labels, afilter.FieldLabel))
  290. }
  291. if len(params.Services) > 0 {
  292. var ops []ast.FilterNode
  293. // filterServices= is the only filter that uses the "contains" operator.
  294. for _, filterValue := range params.Services {
  295. // TODO: wildcard support
  296. filterValue, wildcard := parseWildcardEnd(filterValue)
  297. subFilter := toContainsOp(afilter.FieldServices, "", filterValue, wildcard)
  298. ops = append(ops, subFilter)
  299. }
  300. serviceOps := opsToOr(ops)
  301. filterOps = push(filterOps, serviceOps)
  302. }
  303. andFilter := opsToAnd(filterOps)
  304. if andFilter == nil {
  305. return &ast.VoidOp{} // no filter
  306. }
  307. return andFilter
  308. }
  309. func AllocationSharerFromParamsV1(params AllocationFilterV1) filter.Filter {
  310. var filterOps []ast.FilterNode
  311. if len(params.Namespaces) > 0 {
  312. filterOps = push(filterOps, filterV1SingleValueFromList(params.Namespaces, afilter.FieldNamespace))
  313. }
  314. if len(params.Labels) > 0 {
  315. filterOps = push(filterOps, filterV1DoubleValueFromList(params.Labels, afilter.FieldLabel))
  316. }
  317. return opsToAnd(filterOps)
  318. }
  319. func AssetFilterFromParamsV1(
  320. qp mapper.PrimitiveMapReader,
  321. clusterMap clusters.ClusterMap,
  322. ) filter.Filter {
  323. var filterOps []ast.FilterNode
  324. // ClusterMap does not provide a cluster name -> cluster ID mapping in the
  325. // interface, probably because there could be multiple IDs with the same
  326. // name. However, V1 filter logic demands that the parameters to
  327. // filterClusters= be checked against both cluster ID AND cluster name.
  328. //
  329. // To support expected filterClusters= behavior, we construct a mapping
  330. // of cluster name -> cluster IDs (could be multiple IDs for the same name)
  331. // so that we can create AllocationFilters that use only ClusterIDEquals.
  332. //
  333. //
  334. // AllocationFilter intentionally does not support cluster name filters
  335. // because those should be considered presentation-layer only.
  336. clusterNameToIDs := map[string][]string{}
  337. if clusterMap != nil {
  338. cMap := clusterMap.AsMap()
  339. for _, info := range cMap {
  340. if info == nil {
  341. continue
  342. }
  343. if _, ok := clusterNameToIDs[info.Name]; ok {
  344. clusterNameToIDs[info.Name] = append(clusterNameToIDs[info.Name], info.ID)
  345. } else {
  346. clusterNameToIDs[info.Name] = []string{info.ID}
  347. }
  348. }
  349. }
  350. // The proliferation of > 0 guards in the function is to avoid constructing
  351. // empty filter structs. While it is functionally equivalent to add empty
  352. // filter structs (they evaluate to true always) there could be overhead
  353. // when calling Matches() repeatedly for no purpose.
  354. if filterClusters := qp.GetList(ParamFilterClusters, ","); len(filterClusters) > 0 {
  355. var ops []ast.FilterNode
  356. // filter my cluster identifier
  357. ops = push(ops, filterV1SingleValueFromList(filterClusters, assetfilter.FieldClusterID))
  358. for _, rawFilterValue := range filterClusters {
  359. clusterNameFilter, wildcard := parseWildcardEnd(rawFilterValue)
  360. clusterIDsToFilter := []string{}
  361. for clusterName := range clusterNameToIDs {
  362. if wildcard && strings.HasPrefix(clusterName, clusterNameFilter) {
  363. clusterIDsToFilter = append(clusterIDsToFilter, clusterNameToIDs[clusterName]...)
  364. } else if !wildcard && clusterName == clusterNameFilter {
  365. clusterIDsToFilter = append(clusterIDsToFilter, clusterNameToIDs[clusterName]...)
  366. }
  367. }
  368. for _, clusterID := range clusterIDsToFilter {
  369. ops = append(ops, &ast.EqualOp{
  370. Left: ast.Identifier{
  371. Field: assetfilter.DefaultFieldByName(assetfilter.FieldClusterID),
  372. Key: "",
  373. },
  374. Right: clusterID,
  375. })
  376. }
  377. }
  378. clustersOp := opsToOr(ops)
  379. filterOps = push(filterOps, clustersOp)
  380. }
  381. if raw := qp.GetList(ParamFilterAccounts, ","); len(raw) > 0 {
  382. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldAccount))
  383. }
  384. if raw := qp.GetList(ParamFilterCategories, ","); len(raw) > 0 {
  385. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldCategory))
  386. }
  387. if raw := qp.GetList(ParamFilterNames, ","); len(raw) > 0 {
  388. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldName))
  389. }
  390. if raw := qp.GetList(ParamFilterProjects, ","); len(raw) > 0 {
  391. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldProject))
  392. }
  393. if raw := qp.GetList(ParamFilterProviders, ","); len(raw) > 0 {
  394. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldProvider))
  395. }
  396. if raw := GetList(ParamFilterProviderIDs, ParamFilterProviderIDsV2, qp); len(raw) > 0 {
  397. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldProviderID))
  398. }
  399. if raw := qp.GetList(ParamFilterServices, ","); len(raw) > 0 {
  400. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldService))
  401. }
  402. if raw := qp.GetList(ParamFilterTypes, ","); len(raw) > 0 {
  403. // Types have a special situation where we allow users to enter them
  404. // capitalized or uncapitalized
  405. for i := range raw {
  406. raw[i] = strings.ToLower(raw[i])
  407. }
  408. filterOps = push(filterOps, filterV1SingleValueFromList(raw, assetfilter.FieldType))
  409. }
  410. if raw := qp.GetList(ParamFilterLabels, ","); len(raw) > 0 {
  411. filterOps = push(filterOps, filterV1DoubleValueFromList(raw, assetfilter.FieldLabel))
  412. }
  413. if raw := qp.GetList(ParamFilterRegions, ","); len(raw) > 0 {
  414. filterOps = push(filterOps, filterV1SingleLabelKeyFromList(raw, "label_topology_kubernetes_io_region", assetfilter.FieldLabel))
  415. }
  416. andFilter := opsToAnd(filterOps)
  417. if andFilter == nil {
  418. return &ast.VoidOp{} // no filter
  419. }
  420. return andFilter
  421. }
  422. // filterV1SingleValueFromList creates an OR of equality filters for a given
  423. // filter field.
  424. //
  425. // The v1 query language (e.g. "filterNamespaces=XYZ,ABC") uses OR within
  426. // a field (e.g. namespace = XYZ OR namespace = ABC)
  427. func filterV1SingleValueFromList[T ~string](rawFilterValues []string, filterField T) ast.FilterNode {
  428. var ops []ast.FilterNode
  429. for _, filterValue := range rawFilterValues {
  430. filterValue = strings.TrimSpace(filterValue)
  431. filterValue, wildcard := parseWildcardEnd(filterValue)
  432. subFilter := toEqualOp(filterField, "", filterValue, wildcard)
  433. ops = append(ops, subFilter)
  434. }
  435. return opsToOr(ops)
  436. }
  437. func filterV1SingleLabelKeyFromList[T ~string](rawFilterValues []string, labelName string, labelField T) ast.FilterNode {
  438. var ops []ast.FilterNode
  439. labelName = prom.SanitizeLabelName(labelName)
  440. for _, filterValue := range rawFilterValues {
  441. filterValue = strings.TrimSpace(filterValue)
  442. filterValue, wildcard := parseWildcardEnd(filterValue)
  443. subFilter := toEqualOp(labelField, labelName, filterValue, wildcard)
  444. ops = append(ops, subFilter)
  445. }
  446. return opsToOr(ops)
  447. }
  448. // filterV1LabelAliasMappedFromList is like filterV1SingleValueFromList but is
  449. // explicitly for labels and annotations because "label-mapped" filters (like filterTeams=)
  450. // are actually label filters with a fixed label key.
  451. func filterV1LabelAliasMappedFromList(rawFilterValues []string, labelName string) ast.FilterNode {
  452. var ops []ast.FilterNode
  453. labelName = prom.SanitizeLabelName(labelName)
  454. for _, filterValue := range rawFilterValues {
  455. filterValue = strings.TrimSpace(filterValue)
  456. filterValue, wildcard := parseWildcardEnd(filterValue)
  457. subFilter := toAllocationAliasOp(labelName, filterValue, wildcard)
  458. ops = append(ops, subFilter)
  459. }
  460. return opsToOr(ops)
  461. }
  462. // filterV1DoubleValueFromList creates an OR of key:value equality filters for
  463. // colon-split filter values.
  464. //
  465. // The v1 query language (e.g. "filterLabels=app:foo,l2:bar") uses OR within
  466. // a field (e.g. label[app] = foo OR label[l2] = bar)
  467. func filterV1DoubleValueFromList[T ~string](rawFilterValuesUnsplit []string, filterField T) ast.FilterNode {
  468. var ops []ast.FilterNode
  469. for _, unsplit := range rawFilterValuesUnsplit {
  470. if unsplit != "" {
  471. split := strings.Split(unsplit, ":")
  472. if len(split) != 2 {
  473. log.Warnf("illegal key/value filter (ignoring): %s", unsplit)
  474. continue
  475. }
  476. labelName := prom.SanitizeLabelName(strings.TrimSpace(split[0]))
  477. val := strings.TrimSpace(split[1])
  478. val, wildcard := parseWildcardEnd(val)
  479. subFilter := toEqualOp(filterField, labelName, val, wildcard)
  480. ops = append(ops, subFilter)
  481. }
  482. }
  483. return opsToOr(ops)
  484. }
  485. // parseWildcardEnd checks if the given filter value is wildcarded, meaning
  486. // it ends in "*". If it does, it removes the suffix and returns the cleaned
  487. // string and true. Otherwise, it returns the same filter and false.
  488. //
  489. // parseWildcardEnd("kube*") = "kube", true
  490. // parseWildcardEnd("kube") = "kube", false
  491. func parseWildcardEnd(rawFilterValue string) (string, bool) {
  492. return strings.TrimSuffix(rawFilterValue, "*"), strings.HasSuffix(rawFilterValue, "*")
  493. }
  494. func push(a []ast.FilterNode, item ast.FilterNode) []ast.FilterNode {
  495. if item == nil {
  496. return a
  497. }
  498. return append(a, item)
  499. }
  500. func opsToOr(ops []ast.FilterNode) ast.FilterNode {
  501. if len(ops) == 0 {
  502. return nil
  503. }
  504. if len(ops) == 1 {
  505. return ops[0]
  506. }
  507. return &ast.OrOp{
  508. Operands: ops,
  509. }
  510. }
  511. func opsToAnd(ops []ast.FilterNode) ast.FilterNode {
  512. if len(ops) == 0 {
  513. return nil
  514. }
  515. if len(ops) == 1 {
  516. return ops[0]
  517. }
  518. return &ast.AndOp{
  519. Operands: ops,
  520. }
  521. }
  522. func toEqualOp[T ~string](field T, key string, value string, wildcard bool) ast.FilterNode {
  523. left := ast.Identifier{
  524. Field: DefaultFieldByName(field),
  525. Key: key,
  526. }
  527. right := value
  528. if wildcard {
  529. return &ast.ContainsPrefixOp{
  530. Left: left,
  531. Right: right,
  532. }
  533. }
  534. return &ast.EqualOp{
  535. Left: left,
  536. Right: right,
  537. }
  538. }
  539. func toContainsOp[T ~string](field T, key string, value string, wildcard bool) ast.FilterNode {
  540. left := ast.Identifier{
  541. Field: DefaultFieldByName(field),
  542. Key: key,
  543. }
  544. right := value
  545. if wildcard {
  546. return &ast.ContainsPrefixOp{
  547. Left: left,
  548. Right: right,
  549. }
  550. }
  551. return &ast.ContainsOp{
  552. Left: left,
  553. Right: right,
  554. }
  555. }
  556. func toAllocationAliasOp(labelName string, filterValue string, wildcard bool) *ast.OrOp {
  557. // labels.Contains(labelName)
  558. labelContainsKey := &ast.ContainsOp{
  559. Left: ast.Identifier{
  560. Field: afilter.DefaultFieldByName(afilter.FieldLabel),
  561. Key: "",
  562. },
  563. Right: labelName,
  564. }
  565. // annotations.Contains(labelName)
  566. annotationContainsKey := &ast.ContainsOp{
  567. Left: ast.Identifier{
  568. Field: afilter.DefaultFieldByName(afilter.FieldAnnotation),
  569. Key: "",
  570. },
  571. Right: labelName,
  572. }
  573. // labels[labelName] equals/startswith filterValue
  574. var labelSubFilter ast.FilterNode
  575. if wildcard {
  576. labelSubFilter = &ast.ContainsPrefixOp{
  577. Left: ast.Identifier{
  578. Field: afilter.DefaultFieldByName(afilter.FieldLabel),
  579. Key: labelName,
  580. },
  581. Right: filterValue,
  582. }
  583. } else {
  584. labelSubFilter = &ast.EqualOp{
  585. Left: ast.Identifier{
  586. Field: afilter.DefaultFieldByName(afilter.FieldLabel),
  587. Key: labelName,
  588. },
  589. Right: filterValue,
  590. }
  591. }
  592. // annotations[labelName] equals/startswith filterValue
  593. var annotationSubFilter ast.FilterNode
  594. if wildcard {
  595. annotationSubFilter = &ast.ContainsPrefixOp{
  596. Left: ast.Identifier{
  597. Field: afilter.DefaultFieldByName(afilter.FieldAnnotation),
  598. Key: labelName,
  599. },
  600. Right: filterValue,
  601. }
  602. } else {
  603. annotationSubFilter = &ast.EqualOp{
  604. Left: ast.Identifier{
  605. Field: afilter.DefaultFieldByName(afilter.FieldAnnotation),
  606. Key: labelName,
  607. },
  608. Right: filterValue,
  609. }
  610. }
  611. // Logically, this is equivalent to:
  612. // (labels.Contains(labelName) && labels[labelName] = filterValue) ||
  613. // (!labels.Contains(labelName) && annotations.Contains(labelName) && annotations[labelName] = filterValue)
  614. return &ast.OrOp{
  615. Operands: []ast.FilterNode{
  616. &ast.AndOp{
  617. Operands: []ast.FilterNode{
  618. labelContainsKey,
  619. labelSubFilter,
  620. },
  621. },
  622. &ast.AndOp{
  623. Operands: []ast.FilterNode{
  624. &ast.NotOp{
  625. Operand: ast.Clone(labelContainsKey),
  626. },
  627. annotationContainsKey,
  628. annotationSubFilter,
  629. },
  630. },
  631. },
  632. }
  633. }
  634. // GetList provides a list of values from the first key if they exist, otherwise, it returns
  635. // the values from the second key.
  636. func GetList(primaryKey, secondaryKey string, qp mapper.PrimitiveMapReader) []string {
  637. if raw := qp.GetList(primaryKey, ","); len(raw) > 0 {
  638. return raw
  639. }
  640. return qp.GetList(secondaryKey, ",")
  641. }