allocationprops.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719
  1. package opencost
  2. import (
  3. "fmt"
  4. "sort"
  5. "strings"
  6. "github.com/opencost/opencost/core/pkg/log"
  7. "github.com/opencost/opencost/core/pkg/util/promutil"
  8. )
  9. // AllocationProperty represents a specific property on an allocation, which
  10. // provides utility for extracting custom property metadata.
  11. type AllocationProperty string
  12. // IsLabel returns true if the allocation property has a label prefix
  13. func (apt *AllocationProperty) IsLabel() bool {
  14. return strings.HasPrefix(string(*apt), "label:")
  15. }
  16. // GetLabel returns the label string associated with the label property if it exists.
  17. // Otherwise, empty string is returned.
  18. func (apt *AllocationProperty) GetLabel() string {
  19. if apt.IsLabel() {
  20. return strings.TrimSpace(strings.TrimPrefix(string(*apt), "label:"))
  21. }
  22. return ""
  23. }
  24. // IsAnnotation returns true if the allocation property has an annotation prefix
  25. func (apt *AllocationProperty) IsAnnotation() bool {
  26. return strings.HasPrefix(string(*apt), "annotation:")
  27. }
  28. // GetAnnotation returns the annotation string associated with the property if it exists.
  29. // Otherwise, empty string is returned.
  30. func (apt *AllocationProperty) GetAnnotation() string {
  31. if apt.IsAnnotation() {
  32. return strings.TrimSpace(strings.TrimPrefix(string(*apt), "annotation:"))
  33. }
  34. return ""
  35. }
  36. // IsAliasedLabel returns true if the allocation property corresponds to an aliased label
  37. func (apt *AllocationProperty) IsAliasedLabel() bool {
  38. if apt == nil {
  39. return false
  40. }
  41. return *apt == AllocationDepartmentProp ||
  42. *apt == AllocationEnvironmentProp ||
  43. *apt == AllocationOwnerProp ||
  44. *apt == AllocationProductProp ||
  45. *apt == AllocationTeamProp
  46. }
  47. // GetAliasedLabelDefault returns the corresponding default aliased label name
  48. func (apt *AllocationProperty) GetAliasedLabelDefault() string {
  49. switch *apt {
  50. case AllocationDepartmentProp:
  51. return "department"
  52. case AllocationEnvironmentProp:
  53. return "env"
  54. case AllocationOwnerProp:
  55. return "owner"
  56. case AllocationProductProp:
  57. return "app"
  58. case AllocationTeamProp:
  59. return "team"
  60. default:
  61. return ""
  62. }
  63. }
  64. const (
  65. AllocationNilProp AllocationProperty = ""
  66. AllocationClusterProp = "cluster"
  67. AllocationNodeProp = "node"
  68. AllocationContainerProp = "container"
  69. AllocationControllerProp = "controller"
  70. AllocationControllerKindProp = "controllerKind"
  71. AllocationNamespaceProp = "namespace"
  72. AllocationPodProp = "pod"
  73. AllocationProviderIDProp = "providerID"
  74. AllocationServiceProp = "service"
  75. AllocationLabelProp = "label"
  76. AllocationAnnotationProp = "annotation"
  77. AllocationNamespaceLabelProp = "namespaceLabel"
  78. AllocationDeploymentProp = "deployment"
  79. AllocationStatefulSetProp = "statefulset"
  80. AllocationDaemonSetProp = "daemonset"
  81. AllocationJobProp = "job"
  82. AllocationDepartmentProp = "department"
  83. AllocationEnvironmentProp = "environment"
  84. AllocationOwnerProp = "owner"
  85. AllocationProductProp = "product"
  86. AllocationTeamProp = "team"
  87. )
  88. func ParseProperties(props []string) ([]AllocationProperty, error) {
  89. properties := []AllocationProperty{}
  90. added := make(map[AllocationProperty]struct{})
  91. for _, prop := range props {
  92. property, err := ParseProperty(prop)
  93. if err != nil {
  94. return nil, fmt.Errorf("Failed to parse property: %w", err)
  95. }
  96. if _, ok := added[property]; !ok {
  97. added[property] = struct{}{}
  98. properties = append(properties, property)
  99. }
  100. }
  101. return properties, nil
  102. }
  103. func ParseProperty(text string) (AllocationProperty, error) {
  104. switch strings.TrimSpace(strings.ToLower(text)) {
  105. case "cluster":
  106. return AllocationClusterProp, nil
  107. case "node":
  108. return AllocationNodeProp, nil
  109. case "container":
  110. return AllocationContainerProp, nil
  111. case "controller":
  112. return AllocationControllerProp, nil
  113. case "controllerkind":
  114. return AllocationControllerKindProp, nil
  115. case "namespace":
  116. return AllocationNamespaceProp, nil
  117. case "pod":
  118. return AllocationPodProp, nil
  119. case "providerid":
  120. return AllocationProviderIDProp, nil
  121. case "service":
  122. return AllocationServiceProp, nil
  123. case "label":
  124. return AllocationLabelProp, nil
  125. case "annotation":
  126. return AllocationAnnotationProp, nil
  127. case "deployment":
  128. return AllocationDeploymentProp, nil
  129. case "daemonset":
  130. return AllocationDaemonSetProp, nil
  131. case "statefulset":
  132. return AllocationStatefulSetProp, nil
  133. case "job":
  134. return AllocationJobProp, nil
  135. case "department":
  136. return AllocationDepartmentProp, nil
  137. case "environment":
  138. return AllocationEnvironmentProp, nil
  139. case "owner":
  140. return AllocationOwnerProp, nil
  141. case "product":
  142. return AllocationProductProp, nil
  143. case "team":
  144. return AllocationTeamProp, nil
  145. }
  146. if strings.HasPrefix(text, "label:") {
  147. label := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "label:")))
  148. return AllocationProperty(fmt.Sprintf("label:%s", label)), nil
  149. }
  150. if strings.HasPrefix(text, "annotation:") {
  151. annotation := promutil.SanitizeLabelName(strings.TrimSpace(strings.TrimPrefix(text, "annotation:")))
  152. return AllocationProperty(fmt.Sprintf("annotation:%s", annotation)), nil
  153. }
  154. return AllocationNilProp, fmt.Errorf("invalid allocation property: %s", text)
  155. }
  156. // AllocationProperties describes a set of Kubernetes objects.
  157. type AllocationProperties struct {
  158. Cluster string `json:"cluster,omitempty"`
  159. Node string `json:"node,omitempty"`
  160. Container string `json:"container,omitempty"`
  161. Controller string `json:"controller,omitempty"`
  162. ControllerKind string `json:"controllerKind,omitempty"`
  163. Namespace string `json:"namespace,omitempty"`
  164. Pod string `json:"pod,omitempty"`
  165. Services []string `json:"services,omitempty"`
  166. ProviderID string `json:"providerID,omitempty"`
  167. Labels AllocationLabels `json:"labels,omitempty"`
  168. Annotations AllocationAnnotations `json:"annotations,omitempty"`
  169. NamespaceLabels AllocationLabels `json:"namespaceLabels,omitempty"` // @bingen:field[version=17]
  170. NamespaceAnnotations AllocationAnnotations `json:"namespaceAnnotations,omitempty"` // @bingen:field[version=17]
  171. // When set to true, maintain the intersection of all labels + annotations
  172. // in the aggregated AllocationProperties object
  173. AggregatedMetadata bool `json:"-"` //@bingen:field[ignore]
  174. }
  175. // AllocationLabels is a schema-free mapping of key/value pairs that can be
  176. // attributed to an Allocation
  177. type AllocationLabels map[string]string
  178. // AllocationAnnotations is a schema-free mapping of key/value pairs that can be
  179. // attributed to an Allocation
  180. type AllocationAnnotations map[string]string
  181. func (p *AllocationProperties) Clone() *AllocationProperties {
  182. if p == nil {
  183. return nil
  184. }
  185. clone := &AllocationProperties{}
  186. clone.Cluster = p.Cluster
  187. clone.Node = p.Node
  188. clone.Container = p.Container
  189. clone.Controller = p.Controller
  190. clone.ControllerKind = p.ControllerKind
  191. clone.Namespace = p.Namespace
  192. clone.Pod = p.Pod
  193. clone.ProviderID = p.ProviderID
  194. var services []string
  195. services = append(services, p.Services...)
  196. clone.Services = services
  197. labels := make(map[string]string, len(p.Labels))
  198. for k, v := range p.Labels {
  199. labels[k] = v
  200. }
  201. clone.Labels = labels
  202. nsLabels := make(map[string]string, len(p.NamespaceLabels))
  203. for k, v := range p.NamespaceLabels {
  204. nsLabels[k] = v
  205. }
  206. clone.NamespaceLabels = nsLabels
  207. annotations := make(map[string]string, len(p.Annotations))
  208. for k, v := range p.Annotations {
  209. annotations[k] = v
  210. }
  211. clone.Annotations = annotations
  212. nsAnnotations := make(map[string]string, len(p.NamespaceAnnotations))
  213. for k, v := range p.NamespaceAnnotations {
  214. nsAnnotations[k] = v
  215. }
  216. clone.NamespaceAnnotations = nsAnnotations
  217. clone.AggregatedMetadata = p.AggregatedMetadata
  218. return clone
  219. }
  220. func (p *AllocationProperties) Equal(that *AllocationProperties) bool {
  221. if p == nil || that == nil {
  222. return false
  223. }
  224. if p.Cluster != that.Cluster {
  225. return false
  226. }
  227. if p.Node != that.Node {
  228. return false
  229. }
  230. if p.Container != that.Container {
  231. return false
  232. }
  233. if p.Controller != that.Controller {
  234. return false
  235. }
  236. if p.ControllerKind != that.ControllerKind {
  237. return false
  238. }
  239. if p.Namespace != that.Namespace {
  240. return false
  241. }
  242. if p.Pod != that.Pod {
  243. return false
  244. }
  245. if p.ProviderID != that.ProviderID {
  246. return false
  247. }
  248. pLabels := p.Labels
  249. thatLabels := that.Labels
  250. if len(pLabels) == len(thatLabels) {
  251. for k, pv := range pLabels {
  252. tv, ok := thatLabels[k]
  253. if !ok || tv != pv {
  254. return false
  255. }
  256. }
  257. } else {
  258. return false
  259. }
  260. pNamespaceLabels := p.NamespaceLabels
  261. thatNamespaceLabels := that.NamespaceLabels
  262. if len(pNamespaceLabels) == len(thatNamespaceLabels) {
  263. for k, pv := range pNamespaceLabels {
  264. tv, ok := thatNamespaceLabels[k]
  265. if !ok || tv != pv {
  266. return false
  267. }
  268. }
  269. } else {
  270. return false
  271. }
  272. pAnnotations := p.Annotations
  273. thatAnnotations := that.Annotations
  274. if len(pAnnotations) == len(thatAnnotations) {
  275. for k, pv := range pAnnotations {
  276. tv, ok := thatAnnotations[k]
  277. if !ok || tv != pv {
  278. return false
  279. }
  280. }
  281. } else {
  282. return false
  283. }
  284. pNamespaceAnnotations := p.NamespaceAnnotations
  285. thatNamespaceAnnotations := that.NamespaceAnnotations
  286. if len(pNamespaceAnnotations) == len(thatNamespaceAnnotations) {
  287. for k, pv := range pNamespaceAnnotations {
  288. tv, ok := thatNamespaceAnnotations[k]
  289. if !ok || tv != pv {
  290. return false
  291. }
  292. }
  293. } else {
  294. return false
  295. }
  296. pServices := p.Services
  297. thatServices := that.Services
  298. if len(pServices) == len(thatServices) {
  299. sort.Strings(pServices)
  300. sort.Strings(thatServices)
  301. for i, pv := range pServices {
  302. tv := thatServices[i]
  303. if tv != pv {
  304. return false
  305. }
  306. }
  307. } else {
  308. return false
  309. }
  310. return true
  311. }
  312. // GenerateKey generates a string that represents the key by which the
  313. // AllocationProperties should be aggregated, given the properties defined by
  314. // the aggregateBy parameter and the given label configuration.
  315. func (p *AllocationProperties) GenerateKey(aggregateBy []string, labelConfig *LabelConfig) string {
  316. if p == nil {
  317. return ""
  318. }
  319. if labelConfig == nil {
  320. labelConfig = NewLabelConfig()
  321. }
  322. // Names will ultimately be joined into a single name, which uniquely
  323. // identifies allocations.
  324. names := []string{}
  325. for _, agg := range aggregateBy {
  326. switch true {
  327. case agg == AllocationClusterProp:
  328. names = append(names, p.Cluster)
  329. case agg == AllocationNodeProp:
  330. names = append(names, p.Node)
  331. case agg == AllocationNamespaceProp:
  332. names = append(names, p.Namespace)
  333. case agg == AllocationControllerKindProp:
  334. controllerKind := p.ControllerKind
  335. if controllerKind == "" {
  336. // Indicate that allocation has no controller
  337. controllerKind = UnallocatedSuffix
  338. }
  339. names = append(names, controllerKind)
  340. case agg == AllocationDaemonSetProp || agg == AllocationStatefulSetProp || agg == AllocationDeploymentProp || agg == AllocationJobProp:
  341. controller := p.Controller
  342. if agg != p.ControllerKind || controller == "" {
  343. // The allocation does not have the specified controller kind
  344. controller = UnallocatedSuffix
  345. }
  346. names = append(names, controller)
  347. case agg == AllocationControllerProp:
  348. controller := p.Controller
  349. if controller == "" {
  350. // Indicate that allocation has no controller
  351. controller = UnallocatedSuffix
  352. } else if p.ControllerKind != "" {
  353. controller = fmt.Sprintf("%s:%s", p.ControllerKind, controller)
  354. }
  355. names = append(names, controller)
  356. case agg == AllocationPodProp:
  357. names = append(names, p.Pod)
  358. case agg == AllocationContainerProp:
  359. names = append(names, p.Container)
  360. case agg == AllocationServiceProp:
  361. services := p.Services
  362. if len(services) == 0 {
  363. // Indicate that allocation has no services
  364. names = append(names, UnallocatedSuffix)
  365. } else {
  366. // Unmounted load balancers lead to __unmounted__ Allocations whose
  367. // services field is populated. If we don't have a special case, the
  368. // __unmounted__ Allocation will be transformed into a regular Allocation,
  369. // causing issues with AggregateBy and drilldown
  370. if p.Pod == UnmountedSuffix || p.Namespace == UnmountedSuffix || p.Container == UnmountedSuffix {
  371. names = append(names, UnmountedSuffix)
  372. } else {
  373. // This just uses the first service
  374. for _, service := range services {
  375. names = append(names, service)
  376. break
  377. }
  378. }
  379. }
  380. case strings.HasPrefix(agg, "label:"):
  381. labels := p.Labels
  382. if labels == nil {
  383. names = append(names, UnallocatedSuffix)
  384. } else {
  385. labelName := labelConfig.Sanitize(strings.TrimPrefix(agg, "label:"))
  386. if labelValue, ok := labels[labelName]; ok {
  387. names = append(names, fmt.Sprintf("%s", labelValue))
  388. } else {
  389. names = append(names, UnallocatedSuffix)
  390. }
  391. }
  392. case strings.HasPrefix(agg, "annotation:"):
  393. annotations := p.Annotations
  394. if annotations == nil {
  395. names = append(names, UnallocatedSuffix)
  396. } else {
  397. annotationName := labelConfig.Sanitize(strings.TrimPrefix(agg, "annotation:"))
  398. if annotationValue, ok := annotations[annotationName]; ok {
  399. names = append(names, fmt.Sprintf("%s", annotationValue))
  400. } else {
  401. names = append(names, UnallocatedSuffix)
  402. }
  403. }
  404. case agg == AllocationDepartmentProp:
  405. labels := p.Labels
  406. annotations := p.Annotations
  407. if labels == nil && annotations == nil {
  408. names = append(names, UnallocatedSuffix)
  409. } else {
  410. labelNames := strings.Split(labelConfig.DepartmentLabel, ",")
  411. for _, labelName := range labelNames {
  412. labelName = labelConfig.Sanitize(labelName)
  413. if labelValue, ok := labels[labelName]; ok {
  414. names = append(names, labelValue)
  415. } else if annotationValue, ok := annotations[labelName]; ok {
  416. names = append(names, annotationValue)
  417. } else {
  418. names = append(names, UnallocatedSuffix)
  419. }
  420. }
  421. }
  422. case agg == AllocationEnvironmentProp:
  423. labels := p.Labels
  424. annotations := p.Annotations
  425. if labels == nil && annotations == nil {
  426. names = append(names, UnallocatedSuffix)
  427. } else {
  428. labelNames := strings.Split(labelConfig.EnvironmentLabel, ",")
  429. for _, labelName := range labelNames {
  430. labelName = labelConfig.Sanitize(labelName)
  431. if labelValue, ok := labels[labelName]; ok {
  432. names = append(names, labelValue)
  433. } else if annotationValue, ok := annotations[labelName]; ok {
  434. names = append(names, annotationValue)
  435. } else {
  436. names = append(names, UnallocatedSuffix)
  437. }
  438. }
  439. }
  440. case agg == AllocationOwnerProp:
  441. labels := p.Labels
  442. annotations := p.Annotations
  443. if labels == nil && annotations == nil {
  444. names = append(names, UnallocatedSuffix)
  445. } else {
  446. labelNames := strings.Split(labelConfig.OwnerLabel, ",")
  447. for _, labelName := range labelNames {
  448. labelName = labelConfig.Sanitize(labelName)
  449. if labelValue, ok := labels[labelName]; ok {
  450. names = append(names, labelValue)
  451. } else if annotationValue, ok := annotations[labelName]; ok {
  452. names = append(names, annotationValue)
  453. } else {
  454. names = append(names, UnallocatedSuffix)
  455. }
  456. }
  457. }
  458. case agg == AllocationProductProp:
  459. labels := p.Labels
  460. annotations := p.Annotations
  461. if labels == nil && annotations == nil {
  462. names = append(names, UnallocatedSuffix)
  463. } else {
  464. labelNames := strings.Split(labelConfig.ProductLabel, ",")
  465. for _, labelName := range labelNames {
  466. labelName = labelConfig.Sanitize(labelName)
  467. if labelValue, ok := labels[labelName]; ok {
  468. names = append(names, labelValue)
  469. } else if annotationValue, ok := annotations[labelName]; ok {
  470. names = append(names, annotationValue)
  471. } else {
  472. names = append(names, UnallocatedSuffix)
  473. }
  474. }
  475. }
  476. case agg == AllocationTeamProp:
  477. labels := p.Labels
  478. annotations := p.Annotations
  479. if labels == nil && annotations == nil {
  480. names = append(names, UnallocatedSuffix)
  481. } else {
  482. labelNames := strings.Split(labelConfig.TeamLabel, ",")
  483. for _, labelName := range labelNames {
  484. labelName = labelConfig.Sanitize(labelName)
  485. if labelValue, ok := labels[labelName]; ok {
  486. names = append(names, labelValue)
  487. } else if annotationValue, ok := annotations[labelName]; ok {
  488. names = append(names, annotationValue)
  489. } else {
  490. names = append(names, UnallocatedSuffix)
  491. }
  492. }
  493. }
  494. default:
  495. // This case should never be reached, as input up until this point
  496. // should be checked and rejected if invalid. But if we do get a
  497. // value we don't recognize, log a warning.
  498. log.Warnf("generateKey: illegal aggregation parameter: %s", agg)
  499. }
  500. }
  501. return strings.Join(names, "/")
  502. }
  503. // Intersection returns an *AllocationProperties which contains all matching fields between the calling and parameter AllocationProperties
  504. // nillable slices and maps are left as nil
  505. func (p *AllocationProperties) Intersection(that *AllocationProperties) *AllocationProperties {
  506. if p == nil || that == nil {
  507. return nil
  508. }
  509. intersectionProps := &AllocationProperties{}
  510. if p.Cluster == that.Cluster {
  511. intersectionProps.Cluster = p.Cluster
  512. }
  513. if p.Node == that.Node {
  514. intersectionProps.Node = p.Node
  515. }
  516. if p.Container == that.Container {
  517. intersectionProps.Container = p.Container
  518. }
  519. if p.Controller == that.Controller {
  520. intersectionProps.Controller = p.Controller
  521. }
  522. if p.ControllerKind == that.ControllerKind {
  523. intersectionProps.ControllerKind = p.ControllerKind
  524. }
  525. if p.Namespace == that.Namespace {
  526. intersectionProps.Namespace = p.Namespace
  527. // CORE-140: In the case that the namespace is the same, also copy over the namespaceLabels and annotations
  528. // Note - assume that if the namespace is the same on both, then namespace label/annotation sets
  529. // will be the same, so just carry one set over
  530. if p.Container == UnmountedSuffix {
  531. // This logic is designed to effectively ignore the unmounted/unallocated objects
  532. // and just copy over the labels from the other, 'legitimate' allocation.
  533. intersectionProps.NamespaceLabels = copyStringMap(that.NamespaceLabels)
  534. intersectionProps.NamespaceAnnotations = copyStringMap(that.NamespaceAnnotations)
  535. } else {
  536. intersectionProps.NamespaceLabels = copyStringMap(p.NamespaceLabels)
  537. intersectionProps.NamespaceAnnotations = copyStringMap(p.NamespaceAnnotations)
  538. }
  539. // ignore the incoming labels from unallocated or unmounted special case pods
  540. if p.AggregatedMetadata || that.AggregatedMetadata {
  541. intersectionProps.AggregatedMetadata = true
  542. // When aggregating by metadata, we maintain the intersection of the labels/annotations
  543. // of the two AllocationProperties objects being intersected here.
  544. // Special case unallocated/unmounted Allocations never have any labels or annotations.
  545. // As a result, they have the effect of always clearing out the intersection,
  546. // regardless if all the other actual allocations/etc have them.
  547. // This logic is designed to effectively ignore the unmounted/unallocated objects
  548. // and just copy over the labels from the other object - we only take the intersection
  549. // of 'legitimate' allocations.
  550. if p.Container == UnmountedSuffix {
  551. intersectionProps.Annotations = that.Annotations
  552. intersectionProps.Labels = that.Labels
  553. } else if that.Container == UnmountedSuffix {
  554. intersectionProps.Annotations = p.Annotations
  555. intersectionProps.Labels = p.Labels
  556. } else {
  557. intersectionProps.Annotations = mapIntersection(p.Annotations, that.Annotations)
  558. intersectionProps.Labels = mapIntersection(p.Labels, that.Labels)
  559. }
  560. }
  561. }
  562. if p.Pod == that.Pod {
  563. intersectionProps.Pod = p.Pod
  564. }
  565. if p.ProviderID == that.ProviderID {
  566. intersectionProps.ProviderID = p.ProviderID
  567. }
  568. return intersectionProps
  569. }
  570. func copyStringMap(original map[string]string) map[string]string {
  571. copy := make(map[string]string)
  572. for key, value := range original {
  573. copy[key] = value
  574. }
  575. return copy
  576. }
  577. func mapIntersection(map1, map2 map[string]string) map[string]string {
  578. result := make(map[string]string)
  579. for key, value := range map1 {
  580. if value2, ok := map2[key]; ok {
  581. if value2 == value {
  582. result[key] = value
  583. }
  584. }
  585. }
  586. return result
  587. }
  588. func (p *AllocationProperties) String() string {
  589. if p == nil {
  590. return "<nil>"
  591. }
  592. strs := []string{}
  593. if p.Cluster != "" {
  594. strs = append(strs, "Cluster:"+p.Cluster)
  595. }
  596. if p.Node != "" {
  597. strs = append(strs, "Node:"+p.Node)
  598. }
  599. if p.Container != "" {
  600. strs = append(strs, "Container:"+p.Container)
  601. }
  602. if p.Controller != "" {
  603. strs = append(strs, "Controller:"+p.Controller)
  604. }
  605. if p.ControllerKind != "" {
  606. strs = append(strs, "ControllerKind:"+p.ControllerKind)
  607. }
  608. if p.Namespace != "" {
  609. strs = append(strs, "Namespace:"+p.Namespace)
  610. }
  611. if p.Pod != "" {
  612. strs = append(strs, "Pod:"+p.Pod)
  613. }
  614. if p.ProviderID != "" {
  615. strs = append(strs, "ProviderID:"+p.ProviderID)
  616. }
  617. if len(p.Services) > 0 {
  618. strs = append(strs, "Services:"+strings.Join(p.Services, ";"))
  619. }
  620. var labelStrs []string
  621. for k, prop := range p.Labels {
  622. labelStrs = append(labelStrs, fmt.Sprintf("%s:%s", k, prop))
  623. }
  624. strs = append(strs, fmt.Sprintf("Labels:{%s}", strings.Join(labelStrs, ",")))
  625. var nsLabelStrs []string
  626. for k, prop := range p.NamespaceLabels {
  627. nsLabelStrs = append(nsLabelStrs, fmt.Sprintf("%s:%s", k, prop))
  628. }
  629. strs = append(strs, fmt.Sprintf("NamespaceLabels:{%s}", strings.Join(nsLabelStrs, ",")))
  630. var annotationStrs []string
  631. for k, prop := range p.Annotations {
  632. annotationStrs = append(annotationStrs, fmt.Sprintf("%s:%s", k, prop))
  633. }
  634. strs = append(strs, fmt.Sprintf("Annotations:{%s}", strings.Join(annotationStrs, ",")))
  635. var nsAnnotationStrs []string
  636. for k, prop := range p.NamespaceAnnotations {
  637. nsAnnotationStrs = append(nsAnnotationStrs, fmt.Sprintf("%s:%s", k, prop))
  638. }
  639. strs = append(strs, fmt.Sprintf("NamespaceAnnotations:{%s}", strings.Join(nsAnnotationStrs, ",")))
  640. return fmt.Sprintf("{%s}", strings.Join(strs, "; "))
  641. }