openshift_logging_facts.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. DOCUMENTATION = """
  2. ---
  3. module: openshift_logging_facts
  4. version_added: ""
  5. short_description: Gather facts about the OpenShift logging stack
  6. description:
  7. - Determine the current facts about the OpenShift logging stack (e.g. cluster size)
  8. options:
  9. author: Red Hat, Inc
  10. """
  11. EXAMPLES = """
  12. - action: opneshift_logging_facts
  13. """
  14. RETURN = """
  15. """
  16. import copy
  17. import json
  18. import exceptions
  19. import yaml
  20. from subprocess import *
  21. default_oc_options = ["-o","json"]
  22. #constants used for various labels and selectors
  23. COMPONENT_KEY="component"
  24. LOGGING_INFRA_KEY="logging-infra"
  25. #selectors for filtering resources
  26. DS_FLUENTD_SELECTOR=LOGGING_INFRA_KEY + "=" + "fluentd"
  27. LOGGING_SELECTOR=LOGGING_INFRA_KEY + "=" + "support"
  28. ROUTE_SELECTOR = "component=support,logging-infra=support,provider=openshift"
  29. COMPONENTS = ["kibana","curator","elasticsearch","fluentd", "kibana_ops", "curator_ops", "elasticsearch_ops"]
  30. class OCBaseCommand(object):
  31. def __init__(self, binary, kubeconfig, namespace):
  32. self.binary = binary
  33. self.kubeconfig = kubeconfig
  34. self.user = self.getSystemAdmin(self.kubeconfig)
  35. self.namespace = namespace
  36. def getSystemAdmin(self,kubeconfig):
  37. with open(kubeconfig,'r') as f:
  38. config = yaml.load(f)
  39. for user in config["users"]:
  40. if user["name"].startswith("system:admin"):
  41. return user["name"]
  42. raise Exception("Unable to find system:admin in: " + kubeconfig)
  43. def oc(self, sub, kind, namespace=None, name=None,addOptions=[]):
  44. cmd = [self.binary, sub, kind]
  45. if name != None:
  46. cmd = cmd + [name]
  47. if namespace != None:
  48. cmd = cmd + ["-n", namespace]
  49. cmd = cmd + ["--user="+self.user,"--config="+self.kubeconfig] + default_oc_options + addOptions
  50. try:
  51. process = Popen(cmd, stdout=PIPE, stderr=PIPE)
  52. out, err = process.communicate(cmd)
  53. if len(err) > 0:
  54. if 'not found' in err:
  55. return {'items':[]}
  56. if 'No resources found' in err:
  57. return {'items':[]}
  58. raise Exception(err)
  59. except Exception as e:
  60. err = "There was an exception trying to run the command '"+ " ".join(cmd) +"' " + str(e)
  61. raise Exception(err)
  62. return json.loads(out)
  63. class OpenshiftLoggingFacts(OCBaseCommand):
  64. name = "facts"
  65. def __init__(self, logger, binary, kubeconfig, namespace):
  66. super(OpenshiftLoggingFacts, self).__init__(binary, kubeconfig, namespace)
  67. self.logger = logger
  68. self.facts = dict()
  69. def defaultKeysFor(self, kind):
  70. for comp in COMPONENTS:
  71. self.addFactsFor(comp, kind)
  72. def addFactsFor(self, comp, kind, name=None, facts=None):
  73. if self.facts.has_key(comp) == False:
  74. self.facts[comp] = dict()
  75. if self.facts[comp].has_key(kind) == False:
  76. self.facts[comp][kind] = dict()
  77. if name:
  78. self.facts[comp][kind][name] = facts
  79. def factsForRoutes(self, namespace):
  80. self.defaultKeysFor("routes")
  81. routeList = self.oc("get","routes", namespace=namespace, addOptions=["-l",ROUTE_SELECTOR])
  82. if len(routeList["items"]) == 0:
  83. return None
  84. for route in routeList["items"]:
  85. name = route["metadata"]["name"]
  86. comp = self.comp(name)
  87. if comp != None:
  88. self.addFactsFor(comp, "routes", name, dict(host=route["spec"]["host"]))
  89. self.facts["agl_namespace"] = namespace
  90. def factsForDaemonsets(self, namespace):
  91. self.defaultKeysFor("daemonsets")
  92. dsList = self.oc("get", "daemonsets", namespace=namespace, addOptions=["-l",LOGGING_INFRA_KEY+"=fluentd"])
  93. if len(dsList["items"]) == 0:
  94. return
  95. for ds in dsList["items"]:
  96. name = ds["metadata"]["name"]
  97. comp = self.comp(name)
  98. spec = ds["spec"]["template"]["spec"]
  99. container = spec["containers"][0]
  100. result = dict(
  101. selector = ds["spec"]["selector"],
  102. image = container["image"],
  103. resources = container["resources"],
  104. nodeSelector = spec["nodeSelector"],
  105. serviceAccount = spec["serviceAccount"],
  106. terminationGracePeriodSeconds = spec["terminationGracePeriodSeconds"]
  107. )
  108. self.addFactsFor(comp, "daemonsets", name, result)
  109. def factsForPvcs(self, namespace):
  110. self.defaultKeysFor("pvcs")
  111. pvclist = self.oc("get", "pvc", namespace=namespace, addOptions=["-l",LOGGING_INFRA_KEY])
  112. if len(pvclist["items"]) == 0:
  113. return
  114. pvcs = []
  115. for pvc in pvclist["items"]:
  116. name = pvc["metadata"]["name"]
  117. comp = self.comp(name)
  118. self.addFactsFor(comp,"pvcs",name,dict())
  119. def factsForDeploymentConfigs(self, namespace):
  120. self.defaultKeysFor("deploymentconfigs")
  121. dclist = self.oc("get", "deploymentconfigs", namespace=namespace, addOptions=["-l",LOGGING_INFRA_KEY])
  122. if len(dclist["items"]) == 0:
  123. return
  124. dcs = dclist["items"]
  125. for dc in dcs:
  126. name = dc["metadata"]["name"]
  127. comp = self.comp(name)
  128. if comp != None:
  129. spec = dc["spec"]["template"]["spec"]
  130. facts = dict(
  131. selector = dc["spec"]["selector"],
  132. replicas = dc["spec"]["replicas"],
  133. serviceAccount = spec["serviceAccount"],
  134. containers = dict(),
  135. volumes = dict()
  136. )
  137. if spec.has_key("volumes"):
  138. for vol in spec["volumes"]:
  139. clone = copy.deepcopy(vol)
  140. clone.pop("name", None)
  141. facts["volumes"][vol["name"]] = clone
  142. for container in spec["containers"]:
  143. facts["containers"][container["name"]] = dict(
  144. image = container["image"],
  145. resources = container["resources"],
  146. )
  147. self.addFactsFor(comp,"deploymentconfigs",name,facts)
  148. def factsForServices(self, namespace):
  149. self.defaultKeysFor("services")
  150. servicelist = self.oc("get", "services", namespace=namespace, addOptions=["-l",LOGGING_SELECTOR])
  151. if len(servicelist["items"]) == 0:
  152. return
  153. for service in servicelist["items"]:
  154. name = service["metadata"]["name"]
  155. comp = self.comp(name)
  156. if comp != None:
  157. self.addFactsFor(comp, "services", name, dict())
  158. def factsForConfigMaps(self, namespace):
  159. self.defaultKeysFor("configmaps")
  160. aList = self.oc("get", "configmaps", namespace=namespace, addOptions=["-l",LOGGING_SELECTOR])
  161. if len(aList["items"]) == 0:
  162. return
  163. for item in aList["items"]:
  164. name = item["metadata"]["name"]
  165. comp = self.comp(name)
  166. if comp != None:
  167. self.addFactsFor(comp, "configmaps", name, item["data"])
  168. def factsForOAuthClients(self, namespace):
  169. self.defaultKeysFor("oauthclients")
  170. aList = self.oc("get", "oauthclients", namespace=namespace, addOptions=["-l",LOGGING_SELECTOR])
  171. if len(aList["items"]) == 0:
  172. return
  173. for item in aList["items"]:
  174. name = item["metadata"]["name"]
  175. comp = self.comp(name)
  176. if comp != None:
  177. result = dict(
  178. redirectURIs = item["redirectURIs"]
  179. )
  180. self.addFactsFor(comp, "oauthclients", name, result)
  181. def factsForSecrets(self, namespace):
  182. self.defaultKeysFor("secrets")
  183. aList = self.oc("get", "secrets", namespace=namespace)
  184. if len(aList["items"]) == 0:
  185. return
  186. for item in aList["items"]:
  187. name = item["metadata"]["name"]
  188. comp = self.comp(name)
  189. if comp != None and item["type"] == "Opaque":
  190. result = dict(
  191. keys = item["data"].keys()
  192. )
  193. self.addFactsFor(comp, "secrets", name, result)
  194. def factsForSCCs(self, namespace):
  195. self.defaultKeysFor("sccs")
  196. scc = self.oc("get", "scc", name="privileged")
  197. if len(scc["users"]) == 0:
  198. return
  199. for item in scc["users"]:
  200. comp = self.comp(item)
  201. if comp != None:
  202. self.addFactsFor(comp, "sccs", "privileged", dict())
  203. def factsForClusterRoleBindings(self, namespace):
  204. self.defaultKeysFor("clusterrolebindings")
  205. role = self.oc("get", "clusterrolebindings", name="cluster-readers")
  206. if "subjects" not in role or len(role["subjects"]) == 0:
  207. return
  208. for item in role["subjects"]:
  209. comp = self.comp(item["name"])
  210. if comp != None and namespace == item["namespace"]:
  211. self.addFactsFor(comp, "clusterrolebindings", "cluster-readers", dict())
  212. # this needs to end up nested under the service account...
  213. def factsForRoleBindings(self, namespace):
  214. self.defaultKeysFor("rolebindings")
  215. role = self.oc("get", "rolebindings", namespace=namespace, name="logging-elasticsearch-view-role")
  216. if "subjects" not in role or len(role["subjects"]) == 0:
  217. return
  218. for item in role["subjects"]:
  219. comp = self.comp(item["name"])
  220. if comp != None and namespace == item["namespace"]:
  221. self.addFactsFor(comp, "rolebindings", "logging-elasticsearch-view-role", dict())
  222. def comp(self, name):
  223. if name.startswith("logging-curator-ops"):
  224. return "curator_ops"
  225. elif name.startswith("logging-kibana-ops") or name.startswith("kibana-ops"):
  226. return "kibana_ops"
  227. elif name.startswith("logging-es-ops") or name.startswith("logging-elasticsearch-ops"):
  228. return "elasticsearch_ops"
  229. elif name.startswith("logging-curator"):
  230. return "curator"
  231. elif name.startswith("logging-kibana") or name.startswith("kibana"):
  232. return "kibana"
  233. elif name.startswith("logging-es") or name.startswith("logging-elasticsearch"):
  234. return "elasticsearch"
  235. elif name.startswith("logging-fluentd") or name.endswith("aggregated-logging-fluentd"):
  236. return "fluentd"
  237. else:
  238. return None
  239. def do(self):
  240. self.factsForRoutes(self.namespace)
  241. self.factsForDaemonsets(self.namespace)
  242. self.factsForDeploymentConfigs(self.namespace)
  243. self.factsForServices(self.namespace)
  244. self.factsForConfigMaps(self.namespace)
  245. self.factsForSCCs(self.namespace)
  246. self.factsForOAuthClients(self.namespace)
  247. self.factsForClusterRoleBindings(self.namespace)
  248. self.factsForRoleBindings(self.namespace)
  249. self.factsForSecrets(self.namespace)
  250. self.factsForPvcs(self.namespace)
  251. return self.facts
  252. def main():
  253. module = AnsibleModule(
  254. argument_spec=dict(
  255. admin_kubeconfig = {"required": True, "type": "str"},
  256. oc_bin = {"required": True, "type": "str"},
  257. openshift_logging_namespace = {"required": True, "type": "str"}
  258. ),
  259. supports_check_mode = False
  260. )
  261. try:
  262. cmd = OpenshiftLoggingFacts(module, module.params['oc_bin'], module.params['admin_kubeconfig'],module.params['openshift_logging_namespace'])
  263. module.exit_json(
  264. ansible_facts = {"openshift_logging_facts": cmd.do() }
  265. )
  266. except Exception as e:
  267. module.fail_json(msg=str(e))
  268. from ansible.module_utils.basic import *
  269. if __name__ == '__main__':
  270. main()