relation.go 10 KB

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