oc_service.py 11 KB

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