2
0

allocationprops.go 22 KB

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