You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

366 lines
10 KiB

  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. DOCUMENTATION = """
  4. ---
  5. module: kube
  6. short_description: Manage Kubernetes Cluster
  7. description:
  8. - Create, replace, remove, and stop resources within a Kubernetes Cluster
  9. version_added: "2.0"
  10. options:
  11. name:
  12. required: false
  13. default: null
  14. description:
  15. - The name associated with resource
  16. filename:
  17. required: false
  18. default: null
  19. description:
  20. - The path and filename of the resource(s) definition file(s).
  21. - To operate on several files this can accept a comma separated list of files or a list of files.
  22. aliases: [ 'files', 'file', 'filenames' ]
  23. kubectl:
  24. required: false
  25. default: null
  26. description:
  27. - The path to the kubectl bin
  28. namespace:
  29. required: false
  30. default: null
  31. description:
  32. - The namespace associated with the resource(s)
  33. resource:
  34. required: false
  35. default: null
  36. description:
  37. - The resource to perform an action on. pods (po), replicationControllers (rc), services (svc)
  38. label:
  39. required: false
  40. default: null
  41. description:
  42. - The labels used to filter specific resources.
  43. server:
  44. required: false
  45. default: null
  46. description:
  47. - The url for the API server that commands are executed against.
  48. kubeconfig:
  49. required: false
  50. default: null
  51. description:
  52. - The path to the kubeconfig.
  53. force:
  54. required: false
  55. default: false
  56. description:
  57. - A flag to indicate to force delete, replace, or stop.
  58. wait:
  59. required: false
  60. default: false
  61. description:
  62. - A flag to indicate to wait for resources to be created before continuing to the next step
  63. all:
  64. required: false
  65. default: false
  66. description:
  67. - A flag to indicate delete all, stop all, or all namespaces when checking exists.
  68. log_level:
  69. required: false
  70. default: 0
  71. description:
  72. - Indicates the level of verbosity of logging by kubectl.
  73. state:
  74. required: false
  75. choices: ['present', 'absent', 'latest', 'reloaded', 'stopped']
  76. default: present
  77. description:
  78. - present handles checking existence or creating if definition file provided,
  79. absent handles deleting resource(s) based on other options,
  80. latest handles creating or updating based on existence,
  81. reloaded handles updating resource(s) definition using definition file,
  82. stopped handles stopping resource(s) based on other options.
  83. recursive:
  84. required: false
  85. default: false
  86. description:
  87. - Process the directory used in -f, --filename recursively.
  88. Useful when you want to manage related manifests organized
  89. within the same directory.
  90. requirements:
  91. - kubectl
  92. author: "Kenny Jones (@kenjones-cisco)"
  93. """
  94. EXAMPLES = """
  95. - name: test nginx is present
  96. kube: name=nginx resource=rc state=present
  97. - name: test nginx is stopped
  98. kube: name=nginx resource=rc state=stopped
  99. - name: test nginx is absent
  100. kube: name=nginx resource=rc state=absent
  101. - name: test nginx is present
  102. kube: filename=/tmp/nginx.yml
  103. - name: test nginx and postgresql are present
  104. kube: files=/tmp/nginx.yml,/tmp/postgresql.yml
  105. - name: test nginx and postgresql are present
  106. kube:
  107. files:
  108. - /tmp/nginx.yml
  109. - /tmp/postgresql.yml
  110. """
  111. class KubeManager(object):
  112. def __init__(self, module):
  113. self.module = module
  114. self.kubectl = module.params.get('kubectl')
  115. if self.kubectl is None:
  116. self.kubectl = module.get_bin_path('kubectl', True)
  117. self.base_cmd = [self.kubectl]
  118. if module.params.get('server'):
  119. self.base_cmd.append('--server=' + module.params.get('server'))
  120. if module.params.get('kubeconfig'):
  121. self.base_cmd.append('--kubeconfig=' + module.params.get('kubeconfig'))
  122. if module.params.get('log_level'):
  123. self.base_cmd.append('--v=' + str(module.params.get('log_level')))
  124. if module.params.get('namespace'):
  125. self.base_cmd.append('--namespace=' + module.params.get('namespace'))
  126. self.all = module.params.get('all')
  127. self.force = module.params.get('force')
  128. self.wait = module.params.get('wait')
  129. self.name = module.params.get('name')
  130. self.filename = [f.strip() for f in module.params.get('filename') or []]
  131. self.resource = module.params.get('resource')
  132. self.label = module.params.get('label')
  133. self.recursive = module.params.get('recursive')
  134. def _execute(self, cmd):
  135. args = self.base_cmd + cmd
  136. try:
  137. rc, out, err = self.module.run_command(args)
  138. if rc != 0:
  139. self.module.fail_json(
  140. msg='error running kubectl (%s) command (rc=%d), out=\'%s\', err=\'%s\'' % (' '.join(args), rc, out, err))
  141. except Exception as exc:
  142. self.module.fail_json(
  143. msg='error running kubectl (%s) command: %s' % (' '.join(args), str(exc)))
  144. return out.splitlines()
  145. def _execute_nofail(self, cmd):
  146. args = self.base_cmd + cmd
  147. rc, out, err = self.module.run_command(args)
  148. if rc != 0:
  149. return None
  150. return out.splitlines()
  151. def create(self, check=True, force=True):
  152. if check and self.exists():
  153. return []
  154. cmd = ['apply']
  155. if force:
  156. cmd.append('--force')
  157. if self.wait:
  158. cmd.append('--wait')
  159. if self.recursive:
  160. cmd.append('--recursive={}'.format(self.recursive))
  161. if not self.filename:
  162. self.module.fail_json(msg='filename required to create')
  163. cmd.append('--filename=' + ','.join(self.filename))
  164. return self._execute(cmd)
  165. def replace(self, force=True):
  166. cmd = ['apply']
  167. if force:
  168. cmd.append('--force')
  169. if self.wait:
  170. cmd.append('--wait')
  171. if self.recursive:
  172. cmd.append('--recursive={}'.format(self.recursive))
  173. if not self.filename:
  174. self.module.fail_json(msg='filename required to reload')
  175. cmd.append('--filename=' + ','.join(self.filename))
  176. return self._execute(cmd)
  177. def delete(self):
  178. if not self.force and not self.exists():
  179. return []
  180. cmd = ['delete']
  181. if self.filename:
  182. cmd.append('--filename=' + ','.join(self.filename))
  183. if self.recursive:
  184. cmd.append('--recursive={}'.format(self.recursive))
  185. else:
  186. if not self.resource:
  187. self.module.fail_json(msg='resource required to delete without filename')
  188. cmd.append(self.resource)
  189. if self.name:
  190. cmd.append(self.name)
  191. if self.label:
  192. cmd.append('--selector=' + self.label)
  193. if self.all:
  194. cmd.append('--all')
  195. if self.force:
  196. cmd.append('--ignore-not-found')
  197. if self.recursive:
  198. cmd.append('--recursive={}'.format(self.recursive))
  199. return self._execute(cmd)
  200. def exists(self):
  201. cmd = ['get']
  202. if self.filename:
  203. cmd.append('--filename=' + ','.join(self.filename))
  204. if self.recursive:
  205. cmd.append('--recursive={}'.format(self.recursive))
  206. else:
  207. if not self.resource:
  208. self.module.fail_json(msg='resource required without filename')
  209. cmd.append(self.resource)
  210. if self.name:
  211. cmd.append(self.name)
  212. if self.label:
  213. cmd.append('--selector=' + self.label)
  214. if self.all:
  215. cmd.append('--all-namespaces')
  216. cmd.append('--no-headers')
  217. result = self._execute_nofail(cmd)
  218. if not result:
  219. return False
  220. return True
  221. # TODO: This is currently unused, perhaps convert to 'scale' with a replicas param?
  222. def stop(self):
  223. if not self.force and not self.exists():
  224. return []
  225. cmd = ['stop']
  226. if self.filename:
  227. cmd.append('--filename=' + ','.join(self.filename))
  228. if self.recursive:
  229. cmd.append('--recursive={}'.format(self.recursive))
  230. else:
  231. if not self.resource:
  232. self.module.fail_json(msg='resource required to stop without filename')
  233. cmd.append(self.resource)
  234. if self.name:
  235. cmd.append(self.name)
  236. if self.label:
  237. cmd.append('--selector=' + self.label)
  238. if self.all:
  239. cmd.append('--all')
  240. if self.force:
  241. cmd.append('--ignore-not-found')
  242. return self._execute(cmd)
  243. def main():
  244. module = AnsibleModule(
  245. argument_spec=dict(
  246. name=dict(),
  247. filename=dict(type='list', aliases=['files', 'file', 'filenames']),
  248. namespace=dict(),
  249. resource=dict(),
  250. label=dict(),
  251. server=dict(),
  252. kubeconfig=dict(),
  253. kubectl=dict(),
  254. force=dict(default=False, type='bool'),
  255. wait=dict(default=False, type='bool'),
  256. all=dict(default=False, type='bool'),
  257. log_level=dict(default=0, type='int'),
  258. state=dict(default='present', choices=['present', 'absent', 'latest', 'reloaded', 'stopped', 'exists']),
  259. recursive=dict(default=False, type='bool'),
  260. ),
  261. mutually_exclusive=[['filename', 'list']]
  262. )
  263. changed = False
  264. manager = KubeManager(module)
  265. state = module.params.get('state')
  266. if state == 'present':
  267. result = manager.create(check=False)
  268. elif state == 'absent':
  269. result = manager.delete()
  270. elif state == 'reloaded':
  271. result = manager.replace()
  272. elif state == 'stopped':
  273. result = manager.stop()
  274. elif state == 'latest':
  275. result = manager.replace()
  276. elif state == 'exists':
  277. result = manager.exists()
  278. module.exit_json(changed=changed,
  279. msg='%s' % result)
  280. else:
  281. module.fail_json(msg='Unrecognized state %s.' % state)
  282. module.exit_json(changed=changed,
  283. msg='success: %s' % (' '.join(result))
  284. )
  285. from ansible.module_utils.basic import * # noqa
  286. if __name__ == '__main__':
  287. main()