relation.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396
  1. package grapher
  2. import (
  3. "strconv"
  4. )
  5. // Relation describes the relationship between k8s components. Type is one of CostrolRel, LabelRel, AnnotationsRel, SpecRel.
  6. // Source and Target contains the ID of the k8s component that is either the giver or recipient of a relationship.
  7. // All relations are bi-directional in that each object contains both the incoming and outbound relationships.
  8. type Relation struct {
  9. Source int
  10. Target int
  11. }
  12. // ControlRel describes the relationship between a controller and its children pod.
  13. type ControlRel struct {
  14. Relation
  15. Replicas int
  16. Template map[string]interface{}
  17. }
  18. // LabelRel connects objects with spec.selector with pods that have corresponding metadata.labels.
  19. type LabelRel struct {
  20. Relation
  21. }
  22. // SpecRel connects objects via various spec properties.
  23. type SpecRel struct {
  24. Relation
  25. }
  26. // ParsedObjs has methods GetControlRel and GetLabelRel that updates its objects array.
  27. type ParsedObjs struct {
  28. Objects []Object
  29. PodSelectors []string
  30. }
  31. // Relations is embedded into the Object struct and contains arrays of the three types of relationships.
  32. type Relations struct {
  33. ControlRels []ControlRel
  34. LabelRels []LabelRel
  35. SpecRels []SpecRel
  36. }
  37. // MatchLabel is used to match Equality label selector.
  38. type MatchLabel struct {
  39. key string
  40. value string
  41. }
  42. // MatchExpression is used to match Set-based label selectors.
  43. type MatchExpression struct {
  44. key string
  45. operator string // In, NotIn, Exists, DoesNotExist are valid
  46. values []string
  47. }
  48. // =============== helpers for parsing relationships from YAML ===============
  49. // GetControlRel generates relationships and children objects for common k8s controller types.
  50. // Note that this only includes controllers whose children are 1) pods and 2) do not have its own YAML.
  51. // i.e. Children relies entirely on the parent's template. Controllers like CronJob are excluded because its children are not pods.
  52. func (parsed *ParsedObjs) GetControlRel() {
  53. // First collect all children (Pods) that are not included in the yaml as top-level object.
  54. children := []Object{}
  55. selectors := []string{}
  56. for i, obj := range parsed.Objects {
  57. yaml := obj.RawYAML
  58. kind := getField(yaml, "kind")
  59. if kind == nil {
  60. kind = ""
  61. }
  62. switch kind.(string) {
  63. // Parse for all possible controller types
  64. case "Deployment", "StatefulSet", "ReplicaSet", "DaemonSet", "Job":
  65. rs := getField(yaml, "spec", "replicas")
  66. // replica defaults to 1 if unspecified
  67. if rs == nil {
  68. rs = 1
  69. }
  70. // Add Pods for controller objects
  71. template := getField(yaml, "spec", "template")
  72. if template == nil {
  73. continue
  74. }
  75. for j := 0; j < rs.(int); j++ {
  76. cid := len(parsed.Objects) + len(children)
  77. crel := ControlRel{
  78. Relation: Relation{
  79. Source: obj.ID,
  80. Target: cid,
  81. },
  82. Replicas: rs.(int),
  83. }
  84. pod := Object{
  85. ID: cid,
  86. Kind: "Pod",
  87. Name: obj.Name + "-" + strconv.Itoa(j), // tentative name pre-deploy
  88. Namespace: obj.Namespace,
  89. RawYAML: template.(map[string]interface{}),
  90. Relations: Relations{
  91. ControlRels: []ControlRel{
  92. crel,
  93. },
  94. },
  95. }
  96. children = append(children, pod)
  97. obj.Relations.ControlRels = append(obj.Relations.ControlRels, crel)
  98. parsed.Objects[i] = obj
  99. }
  100. // Get pod label selectors
  101. matchLabels, _ := aggregateLabelSelectors(yaml)
  102. // stringify selectors
  103. selector := ""
  104. for i, ml := range matchLabels {
  105. selector = selector + ml.key + "=" + ml.value
  106. if i != len(matchLabels)-1 {
  107. selector = selector + ","
  108. }
  109. }
  110. selectors = appendIfNotDuplicate(selectors, selector)
  111. }
  112. }
  113. // add children to the objects array at the end.
  114. parsed.Objects = append(parsed.Objects, children...)
  115. parsed.PodSelectors = selectors
  116. }
  117. // GetLabelRel is generates relationships between objects connected by selector-label.
  118. // It supports both Equality-based and Set-based operators with MatchLabels and MatchExpressions, respectively.
  119. func (parsed *ParsedObjs) GetLabelRel() {
  120. for i, o := range parsed.Objects {
  121. // Skip Pods
  122. yaml := o.RawYAML
  123. matchLabels, matchExpressions := aggregateLabelSelectors(yaml)
  124. // Find ID's of targets that match the label selector
  125. targetID := parsed.findLabelsBySelector(o.ID, matchLabels, matchExpressions)
  126. lrels := o.Relations.LabelRels
  127. for _, tid := range targetID {
  128. newrel := LabelRel{
  129. Relation{
  130. Source: o.ID,
  131. Target: tid,
  132. },
  133. }
  134. lrels = append(lrels, newrel)
  135. }
  136. parsed.Objects[i].Relations.LabelRels = lrels
  137. }
  138. }
  139. // GetSpecRel draws relationships between two objects that are tied via various fields in their spec.
  140. func (parsed *ParsedObjs) GetSpecRel() {
  141. for i, o := range parsed.Objects {
  142. tid := []int{}
  143. switch o.Kind {
  144. case "ClusterRoleBinding", "RoleBinding":
  145. tid = parsed.findRBACTargets(o.ID, o.RawYAML)
  146. case "Ingress":
  147. rules := getField(o.RawYAML, "spec", "rules")
  148. if rules == nil {
  149. rules = []interface{}{}
  150. }
  151. for _, r := range rules.([]interface{}) {
  152. paths := getField(r.(map[string]interface{}), "http", "paths")
  153. if paths == nil {
  154. paths = []interface{}{}
  155. }
  156. for _, p := range paths.([]interface{}) {
  157. // service and resource are mutually exclusive backend types.
  158. name := getField(p.(map[string]interface{}), "backend", "serviceName")
  159. kind := "Service"
  160. if name == nil {
  161. name = getField(p.(map[string]interface{}), "backend", "service", "name")
  162. }
  163. if name == nil {
  164. name = getField(p.(map[string]interface{}), "backend", "resource", "name")
  165. kind = getField(p.(map[string]interface{}), "backend", "resource", "kind").(string)
  166. }
  167. tid = parsed.findObjectByNameAndKind(o.ID, name, kind)
  168. }
  169. }
  170. case "StatefulSet":
  171. serviceName := getField(o.RawYAML, "spec", "serviceName")
  172. tid = append(tid, parsed.findObjectByNameAndKind(o.ID, serviceName, "Service")...)
  173. case "Pod":
  174. volume := getField(o.RawYAML, "spec", "volumes")
  175. imageSecrets := getField(o.RawYAML, "spec", "ImagePullSecrets")
  176. serviceAccount := getField(o.RawYAML, "spec", "serviceAccountName")
  177. if imageSecrets == nil {
  178. imageSecrets = []interface{}{}
  179. }
  180. if volume == nil {
  181. volume = []interface{}{}
  182. }
  183. for _, sec := range imageSecrets.([]interface{}) {
  184. tid = append(tid, parsed.findObjectByNameAndKind(o.ID, sec, "Secret")...)
  185. }
  186. tid = append(tid, parsed.findObjectByNameAndKind(o.ID, serviceAccount, "ServiceAccount")...)
  187. for _, v := range volume.([]interface{}) {
  188. vt := v.(map[string]interface{})
  189. configMap := getField(vt, "configMap", "name")
  190. pvc := getField(vt, "persistentVolumeClaim", "claimName")
  191. secret := getField(vt, "secret", "secretName")
  192. tid = append(tid, parsed.findObjectByNameAndKind(o.ID, configMap, "ConfigMap")...)
  193. tid = append(tid, parsed.findObjectByNameAndKind(o.ID, pvc, "PersistentVolumeClaim")...)
  194. tid = append(tid, parsed.findObjectByNameAndKind(o.ID, secret, "Secret")...)
  195. }
  196. }
  197. // Add edges to parent
  198. rels := o.Relations.SpecRels
  199. for _, id := range tid {
  200. newrel := SpecRel{
  201. Relation{
  202. Source: o.ID,
  203. Target: id,
  204. },
  205. }
  206. rels = append(rels, newrel)
  207. }
  208. parsed.Objects[i].Relations.SpecRels = rels
  209. }
  210. }
  211. // ControlRel helpers
  212. func appendIfNotDuplicate(selectors []string, selector string) []string {
  213. for _, e := range selectors {
  214. if selector == e {
  215. return selectors
  216. }
  217. }
  218. selectors = append(selectors, selector)
  219. return selectors
  220. }
  221. // LabelRel helpers
  222. func aggregateLabelSelectors(yaml map[string]interface{}) ([]MatchLabel, []MatchExpression) {
  223. matchLabels := []MatchLabel{}
  224. matchExpressions := []MatchExpression{}
  225. // First check for the outdated syntax (matchLabels were added in recent k8s version)
  226. if l := getField(yaml, "spec", "selector"); l != nil {
  227. simple := true
  228. if ml := getField(yaml, "spec", "selector", "matchLabels"); ml != nil {
  229. matchLabels = addMatchLabels(matchLabels, ml.(map[string]interface{}))
  230. simple = false
  231. }
  232. if me := getField(yaml, "spec", "selector", "matchExpressions"); me != nil {
  233. for _, o := range me.([]interface{}) {
  234. ot := o.(map[string]interface{})
  235. values := []string{}
  236. for _, arg := range ot["values"].([]interface{}) {
  237. values = append(values, arg.(string))
  238. }
  239. matchExpressions = append(matchExpressions, MatchExpression{
  240. key: ot["key"].(string),
  241. operator: ot["operator"].(string),
  242. values: values,
  243. })
  244. }
  245. simple = false
  246. }
  247. if simple {
  248. matchLabels = addMatchLabels(matchLabels, l.(map[string]interface{}))
  249. }
  250. }
  251. return matchLabels, matchExpressions
  252. }
  253. // SpecRel helpers
  254. func (parsed *ParsedObjs) findObjectByNameAndKind(parentID int, name interface{}, kind string) []int {
  255. targets := []int{}
  256. if name == nil {
  257. return targets
  258. }
  259. name = name.(string)
  260. for i, o := range parsed.Objects {
  261. newrel := SpecRel{
  262. Relation{
  263. Source: parentID,
  264. Target: o.ID,
  265. },
  266. }
  267. if o.Name == name && o.Kind == kind {
  268. // Add bidirectional link from children as well.
  269. parsed.Objects[i].Relations.SpecRels = append(parsed.Objects[i].Relations.SpecRels, newrel)
  270. targets = append(targets, o.ID)
  271. return targets
  272. }
  273. }
  274. return targets
  275. }
  276. func (parsed *ParsedObjs) findRBACTargets(parentID int, yaml map[string]interface{}) []int {
  277. roleRef := getField(yaml, "roleRef")
  278. subjects := getField(yaml, "subjects")
  279. rules := append(subjects.([]interface{}), roleRef)
  280. targets := []int{}
  281. for i, o := range parsed.Objects {
  282. for _, r := range rules {
  283. tr := r.(map[string]interface{})
  284. newrel := SpecRel{
  285. Relation{
  286. Source: parentID,
  287. Target: o.ID,
  288. },
  289. }
  290. // first consider case of targets added via subjects, which are namespace scoped.
  291. if tr["namespace"] != nil && o.Kind == tr["kind"] && o.Name == tr["name"] &&
  292. (o.Namespace == tr["namespace"] || o.Namespace == "default") {
  293. // Add bidirectional link from children as well.
  294. parsed.Objects[i].Relations.SpecRels = append(parsed.Objects[i].Relations.SpecRels, newrel)
  295. targets = append(targets, o.ID)
  296. } else if tr["namespace"] == nil && o.Kind == tr["kind"] && o.Name == tr["name"] {
  297. parsed.Objects[i].Relations.SpecRels = append(parsed.Objects[i].Relations.SpecRels, newrel)
  298. targets = append(targets, o.ID)
  299. }
  300. }
  301. }
  302. return targets
  303. }
  304. func addMatchLabels(matchLabels []MatchLabel, ml map[string]interface{}) []MatchLabel {
  305. for k, v := range ml {
  306. matchLabels = append(matchLabels, MatchLabel{
  307. key: k,
  308. value: v.(string),
  309. })
  310. }
  311. return matchLabels
  312. }
  313. // TODO: Implement MatchExpression for set based operations.
  314. func (parsed *ParsedObjs) findLabelsBySelector(parentID int, ml []MatchLabel, me []MatchExpression) []int {
  315. matchedObjs := []int{}
  316. for i, o := range parsed.Objects {
  317. // Only Pods can be selected by spec.selector
  318. if o.Kind != "Pod" {
  319. continue
  320. }
  321. // find Pods that match labels
  322. labels := getField(o.RawYAML, "metadata", "labels")
  323. match := 0
  324. for _, l := range ml {
  325. if labels.(map[string]interface{})[l.key] == l.value {
  326. match++
  327. }
  328. }
  329. // Returns only if labels meet all conditions of the selector.
  330. if match == len(ml) && match > 0 {
  331. newrel := LabelRel{
  332. Relation{
  333. Source: parentID,
  334. Target: o.ID,
  335. },
  336. }
  337. // Add bidirectional link from children as well.
  338. parsed.Objects[i].Relations.LabelRels = append(parsed.Objects[i].Relations.LabelRels, newrel)
  339. matchedObjs = append(matchedObjs, o.ID)
  340. }
  341. }
  342. return matchedObjs
  343. }