oc_deploymentconfig.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. #!/usr/bin/env python
  2. '''
  3. OpenShiftCLI class that wraps the oc commands in a subprocess
  4. '''
  5. import atexit
  6. import json
  7. import os
  8. import shutil
  9. import subprocess
  10. import yaml
  11. class OpenShiftCLI(object):
  12. ''' Class to wrap the oc command line tools '''
  13. def __init__(self,
  14. namespace,
  15. kubeconfig='/etc/origin/master/admin.kubeconfig',
  16. verbose=False):
  17. ''' Constructor for OpenshiftOC '''
  18. self.namespace = namespace
  19. self.verbose = verbose
  20. self.kubeconfig = kubeconfig
  21. def replace(self, fname, force=False):
  22. '''return all pods '''
  23. cmd = ['replace', '-f', fname]
  24. if force:
  25. cmd = ['replace', '--force', '-f', fname]
  26. return self.oc_cmd(cmd)
  27. def create(self, fname):
  28. '''return all pods '''
  29. return self.oc_cmd(['create', '-f', fname, '-n', self.namespace])
  30. def delete(self, resource, rname):
  31. '''return all pods '''
  32. return self.oc_cmd(['delete', resource, rname, '-n', self.namespace])
  33. def get(self, resource, rname=None):
  34. '''return a secret by name '''
  35. cmd = ['get', resource, '-o', 'json', '-n', self.namespace]
  36. if rname:
  37. cmd.append(rname)
  38. rval = self.oc_cmd(cmd, output=True)
  39. # Ensure results are retuned in an array
  40. if rval.has_key('items'):
  41. rval['results'] = rval['items']
  42. elif not isinstance(rval['results'], list):
  43. rval['results'] = [rval['results']]
  44. return rval
  45. def oc_cmd(self, cmd, output=False):
  46. '''Base command for oc '''
  47. #cmds = ['/usr/bin/oc', '--config', self.kubeconfig]
  48. cmds = ['/usr/bin/oc']
  49. cmds.extend(cmd)
  50. results = ''
  51. if self.verbose:
  52. print ' '.join(cmds)
  53. proc = subprocess.Popen(cmds,
  54. stdout=subprocess.PIPE,
  55. stderr=subprocess.PIPE,
  56. env={'KUBECONFIG': self.kubeconfig})
  57. proc.wait()
  58. if proc.returncode == 0:
  59. if output:
  60. try:
  61. results = json.loads(proc.stdout.read())
  62. except ValueError as err:
  63. if "No JSON object could be decoded" in err.message:
  64. results = err.message
  65. if self.verbose:
  66. print proc.stderr.read()
  67. print results
  68. print
  69. return {"returncode": proc.returncode, "results": results}
  70. return {"returncode": proc.returncode,
  71. "stderr": proc.stderr.read(),
  72. "stdout": proc.stdout.read(),
  73. "results": {}
  74. }
  75. class Utils(object):
  76. ''' utilities for openshiftcli modules '''
  77. @staticmethod
  78. def create_file(rname, data, ftype=None):
  79. ''' create a file in tmp with name and contents'''
  80. path = os.path.join('/tmp', rname)
  81. with open(path, 'w') as fds:
  82. if ftype == 'yaml':
  83. fds.write(yaml.dump(data, default_flow_style=False))
  84. elif ftype == 'json':
  85. fds.write(json.dumps(data))
  86. else:
  87. fds.write(data)
  88. # Register cleanup when module is done
  89. atexit.register(Utils.cleanup, [path])
  90. return path
  91. @staticmethod
  92. def create_files_from_contents(data):
  93. '''Turn an array of dict: filename, content into a files array'''
  94. files = []
  95. for sfile in data:
  96. path = Utils.create_file(sfile['path'], sfile['content'])
  97. files.append(path)
  98. return files
  99. @staticmethod
  100. def cleanup(files):
  101. '''Clean up on exit '''
  102. for sfile in files:
  103. if os.path.exists(sfile):
  104. if os.path.isdir(sfile):
  105. shutil.rmtree(sfile)
  106. elif os.path.isfile(sfile):
  107. os.remove(sfile)
  108. @staticmethod
  109. def exists(results, _name):
  110. ''' Check to see if the results include the name '''
  111. if not results:
  112. return False
  113. if Utils.find_result(results, _name):
  114. return True
  115. return False
  116. @staticmethod
  117. def find_result(results, _name):
  118. ''' Find the specified result by name'''
  119. rval = None
  120. for result in results:
  121. if result.has_key('metadata') and result['metadata']['name'] == _name:
  122. rval = result
  123. break
  124. return rval
  125. @staticmethod
  126. def get_resource_file(sfile, sfile_type='yaml'):
  127. ''' return the service file '''
  128. contents = None
  129. with open(sfile) as sfd:
  130. contents = sfd.read()
  131. if sfile_type == 'yaml':
  132. contents = yaml.load(contents)
  133. elif sfile_type == 'json':
  134. contents = json.loads(contents)
  135. return contents
  136. # Disabling too-many-branches. This is a yaml dictionary comparison function
  137. # pylint: disable=too-many-branches,too-many-return-statements
  138. @staticmethod
  139. def check_def_equal(user_def, result_def, debug=False):
  140. ''' Given a user defined definition, compare it with the results given back by our query. '''
  141. # Currently these values are autogenerated and we do not need to check them
  142. skip = ['creationTimestamp', 'selfLink', 'resourceVersion', 'uid', 'namespace']
  143. for key, value in result_def.items():
  144. if key in skip:
  145. continue
  146. # Both are lists
  147. if isinstance(value, list):
  148. if not isinstance(user_def[key], list):
  149. return False
  150. # lists should be identical
  151. if value != user_def[key]:
  152. return False
  153. # recurse on a dictionary
  154. elif isinstance(value, dict):
  155. if not isinstance(user_def[key], dict):
  156. if debug:
  157. print "dict returned false not instance of dict"
  158. return False
  159. # before passing ensure keys match
  160. api_values = set(value.keys()) - set(skip)
  161. user_values = set(user_def[key].keys()) - set(skip)
  162. if api_values != user_values:
  163. if debug:
  164. print api_values
  165. print user_values
  166. print "keys are not equal in dict"
  167. return False
  168. result = Utils.check_def_equal(user_def[key], value, debug=debug)
  169. if not result:
  170. if debug:
  171. print "dict returned false"
  172. return False
  173. # Verify each key, value pair is the same
  174. else:
  175. if not user_def.has_key(key) or value != user_def[key]:
  176. if debug:
  177. print "value not equal; user_def does not have key"
  178. print value
  179. print user_def[key]
  180. return False
  181. return True
  182. class DeploymentConfig(OpenShiftCLI):
  183. ''' Class to wrap the oc command line tools
  184. '''
  185. def __init__(self,
  186. namespace,
  187. dname=None,
  188. kubeconfig='/etc/origin/master/admin.kubeconfig',
  189. verbose=False):
  190. ''' Constructor for OpenshiftOC '''
  191. super(DeploymentConfig, self).__init__(namespace, kubeconfig)
  192. self.namespace = namespace
  193. self.name = dname
  194. self.kubeconfig = kubeconfig
  195. self.verbose = verbose
  196. def get_dc(self):
  197. '''return a deploymentconfig by name '''
  198. return self.get('dc', self.name)
  199. def delete_dc(self):
  200. '''return all pods '''
  201. return self.delete('dc', self.name)
  202. def new_dc(self, dfile):
  203. '''Create a deploymentconfig '''
  204. return self.create(dfile)
  205. def update_dc(self, dfile, force=False):
  206. '''run update dc
  207. This receives a list of file names and takes the first filename and calls replace.
  208. '''
  209. return self.replace(dfile, force)
  210. # pylint: disable=too-many-branches
  211. def main():
  212. '''
  213. ansible oc module for deploymentconfig
  214. '''
  215. module = AnsibleModule(
  216. argument_spec=dict(
  217. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  218. state=dict(default='present', type='str',
  219. choices=['present', 'absent', 'list']),
  220. debug=dict(default=False, type='bool'),
  221. namespace=dict(default='default', type='str'),
  222. name=dict(default=None, type='str'),
  223. deploymentconfig_file=dict(default=None, type='str'),
  224. input_type=dict(default='yaml', choices=['yaml', 'json'], type='str'),
  225. delete_after=dict(default=False, type='bool'),
  226. content=dict(default=None, type='dict'),
  227. force=dict(default=False, type='bool'),
  228. ),
  229. mutually_exclusive=[["contents", "deploymentconfig_file"]],
  230. supports_check_mode=True,
  231. )
  232. occmd = DeploymentConfig(module.params['namespace'],
  233. dname=module.params['name'],
  234. kubeconfig=module.params['kubeconfig'],
  235. verbose=module.params['debug'])
  236. state = module.params['state']
  237. api_rval = occmd.get_dc()
  238. #####
  239. # Get
  240. #####
  241. if state == 'list':
  242. module.exit_json(changed=False, results=api_rval['results'], state="list")
  243. if not module.params['name']:
  244. module.fail_json(msg='Please specify a name when state is absent|present.')
  245. ########
  246. # Delete
  247. ########
  248. if state == 'absent':
  249. if not Utils.exists(api_rval['results'], module.params['name']):
  250. module.exit_json(changed=False, state="absent")
  251. if module.check_mode:
  252. module.exit_json(change=False, msg='Would have performed a delete.')
  253. api_rval = occmd.delete_dc()
  254. module.exit_json(changed=True, results=api_rval, state="absent")
  255. if state == 'present':
  256. if module.params['deploymentconfig_file']:
  257. dfile = module.params['deploymentconfig_file']
  258. elif module.params['content']:
  259. dfile = Utils.create_file('dc', module.params['content'])
  260. else:
  261. module.fail_json(msg="Please specify content or deploymentconfig file.")
  262. ########
  263. # Create
  264. ########
  265. if not Utils.exists(api_rval['results'], module.params['name']):
  266. if module.check_mode:
  267. module.exit_json(change=False, msg='Would have performed a create.')
  268. api_rval = occmd.new_dc(dfile)
  269. # Remove files
  270. if module.params['deploymentconfig_file'] and module.params['delete_after']:
  271. Utils.cleanup([dfile])
  272. if api_rval['returncode'] != 0:
  273. module.fail_json(msg=api_rval)
  274. module.exit_json(changed=True, results=api_rval, state="present")
  275. ########
  276. # Update
  277. ########
  278. if Utils.check_def_equal(Utils.get_resource_file(dfile), api_rval['results'][0]):
  279. # Remove files
  280. if module.params['deploymentconfig_file'] and module.params['delete_after']:
  281. Utils.cleanup([dfile])
  282. module.exit_json(changed=False, results=api_rval['results'], state="present")
  283. if module.check_mode:
  284. module.exit_json(change=False, msg='Would have performed an update.')
  285. api_rval = occmd.update_dc(dfile, force=module.params['force'])
  286. # Remove files
  287. if module.params['deploymentconfig_file'] and module.params['delete_after']:
  288. Utils.cleanup([dfile])
  289. if api_rval['returncode'] != 0:
  290. module.fail_json(msg=api_rval)
  291. module.exit_json(changed=True, results=api_rval, state="present")
  292. module.exit_json(failed=True,
  293. changed=False,
  294. results='Unknown state passed. %s' % state,
  295. state="unknown")
  296. # pylint: disable=redefined-builtin, unused-wildcard-import, wildcard-import, locally-disabled
  297. # import module snippets. This are required
  298. from ansible.module_utils.basic import *
  299. main()