oo_filters.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. # vim: expandtab:tabstop=4:shiftwidth=4
  4. '''
  5. Custom filters for use in openshift-ansible
  6. '''
  7. from ansible import errors
  8. from operator import itemgetter
  9. import pdb
  10. import re
  11. import json
  12. class FilterModule(object):
  13. ''' Custom ansible filters '''
  14. @staticmethod
  15. def oo_pdb(arg):
  16. ''' This pops you into a pdb instance where arg is the data passed in
  17. from the filter.
  18. Ex: "{{ hostvars | oo_pdb }}"
  19. '''
  20. pdb.set_trace()
  21. return arg
  22. @staticmethod
  23. def get_attr(data, attribute=None):
  24. ''' This looks up dictionary attributes of the form a.b.c and returns
  25. the value.
  26. Ex: data = {'a': {'b': {'c': 5}}}
  27. attribute = "a.b.c"
  28. returns 5
  29. '''
  30. if not attribute:
  31. raise errors.AnsibleFilterError("|failed expects attribute to be set")
  32. ptr = data
  33. for attr in attribute.split('.'):
  34. ptr = ptr[attr]
  35. return ptr
  36. @staticmethod
  37. def oo_flatten(data):
  38. ''' This filter plugin will flatten a list of lists
  39. '''
  40. if not issubclass(type(data), list):
  41. raise errors.AnsibleFilterError("|failed expects to flatten a List")
  42. return [item for sublist in data for item in sublist]
  43. @staticmethod
  44. def oo_collect(data, attribute=None, filters=None):
  45. ''' This takes a list of dict and collects all attributes specified into a
  46. list. If filter is specified then we will include all items that
  47. match _ALL_ of filters. If a dict entry is missing the key in a
  48. filter it will be excluded from the match.
  49. Ex: data = [ {'a':1, 'b':5, 'z': 'z'}, # True, return
  50. {'a':2, 'z': 'z'}, # True, return
  51. {'a':3, 'z': 'z'}, # True, return
  52. {'a':4, 'z': 'b'}, # FAILED, obj['z'] != obj['z']
  53. ]
  54. attribute = 'a'
  55. filters = {'z': 'z'}
  56. returns [1, 2, 3]
  57. '''
  58. if not issubclass(type(data), list):
  59. raise errors.AnsibleFilterError("|failed expects to filter on a List")
  60. if not attribute:
  61. raise errors.AnsibleFilterError("|failed expects attribute to be set")
  62. if filters is not None:
  63. if not issubclass(type(filters), dict):
  64. raise errors.AnsibleFilterError("|failed expects filter to be a"
  65. " dict")
  66. retval = [FilterModule.get_attr(d, attribute) for d in data if (
  67. all([d.get(key, None) == filters[key] for key in filters]))]
  68. else:
  69. retval = [FilterModule.get_attr(d, attribute) for d in data]
  70. return retval
  71. @staticmethod
  72. def oo_select_keys_from_list(data, keys):
  73. ''' This returns a list, which contains the value portions for the keys
  74. Ex: data = { 'a':1, 'b':2, 'c':3 }
  75. keys = ['a', 'c']
  76. returns [1, 3]
  77. '''
  78. if not issubclass(type(data), list):
  79. raise errors.AnsibleFilterError("|failed expects to filter on a list")
  80. if not issubclass(type(keys), list):
  81. raise errors.AnsibleFilterError("|failed expects first param is a list")
  82. # Gather up the values for the list of keys passed in
  83. retval = [FilterModule.oo_select_keys(item, keys) for item in data]
  84. return FilterModule.oo_flatten(retval)
  85. @staticmethod
  86. def oo_select_keys(data, keys):
  87. ''' This returns a list, which contains the value portions for the keys
  88. Ex: data = { 'a':1, 'b':2, 'c':3 }
  89. keys = ['a', 'c']
  90. returns [1, 3]
  91. '''
  92. if not issubclass(type(data), dict):
  93. raise errors.AnsibleFilterError("|failed expects to filter on a dict")
  94. if not issubclass(type(keys), list):
  95. raise errors.AnsibleFilterError("|failed expects first param is a list")
  96. # Gather up the values for the list of keys passed in
  97. retval = [data[key] for key in keys if data.has_key(key)]
  98. return retval
  99. @staticmethod
  100. def oo_prepend_strings_in_list(data, prepend):
  101. ''' This takes a list of strings and prepends a string to each item in the
  102. list
  103. Ex: data = ['cart', 'tree']
  104. prepend = 'apple-'
  105. returns ['apple-cart', 'apple-tree']
  106. '''
  107. if not issubclass(type(data), list):
  108. raise errors.AnsibleFilterError("|failed expects first param is a list")
  109. if not all(isinstance(x, basestring) for x in data):
  110. raise errors.AnsibleFilterError("|failed expects first param is a list"
  111. " of strings")
  112. retval = [prepend + s for s in data]
  113. return retval
  114. @staticmethod
  115. def oo_combine_key_value(data, joiner='='):
  116. '''Take a list of dict in the form of { 'key': 'value'} and
  117. arrange them as a list of strings ['key=value']
  118. '''
  119. if not issubclass(type(data), list):
  120. raise errors.AnsibleFilterError("|failed expects first param is a list")
  121. rval = []
  122. for item in data:
  123. rval.append("%s%s%s" % (item['key'], joiner, item['value']))
  124. return rval
  125. @staticmethod
  126. def oo_combine_dict(data, in_joiner='=', out_joiner=' '):
  127. '''Take a dict in the form of { 'key': 'value', 'key': 'value' } and
  128. arrange them as a string 'key=value key=value'
  129. '''
  130. if not issubclass(type(data), dict):
  131. raise errors.AnsibleFilterError("|failed expects first param is a dict")
  132. return out_joiner.join([in_joiner.join([k, v]) for k, v in data.items()])
  133. @staticmethod
  134. def oo_ami_selector(data, image_name):
  135. ''' This takes a list of amis and an image name and attempts to return
  136. the latest ami.
  137. '''
  138. if not issubclass(type(data), list):
  139. raise errors.AnsibleFilterError("|failed expects first param is a list")
  140. if not data:
  141. return None
  142. else:
  143. if image_name is None or not image_name.endswith('_*'):
  144. ami = sorted(data, key=itemgetter('name'), reverse=True)[0]
  145. return ami['ami_id']
  146. else:
  147. ami_info = [(ami, ami['name'].split('_')[-1]) for ami in data]
  148. ami = sorted(ami_info, key=itemgetter(1), reverse=True)[0][0]
  149. return ami['ami_id']
  150. @staticmethod
  151. def oo_ec2_volume_definition(data, host_type, docker_ephemeral=False):
  152. ''' This takes a dictionary of volume definitions and returns a valid ec2
  153. volume definition based on the host_type and the values in the
  154. dictionary.
  155. The dictionary should look similar to this:
  156. { 'master':
  157. { 'root':
  158. { 'volume_size': 10, 'device_type': 'gp2',
  159. 'iops': 500
  160. }
  161. },
  162. 'node':
  163. { 'root':
  164. { 'volume_size': 10, 'device_type': 'io1',
  165. 'iops': 1000
  166. },
  167. 'docker':
  168. { 'volume_size': 40, 'device_type': 'gp2',
  169. 'iops': 500, 'ephemeral': 'true'
  170. }
  171. }
  172. }
  173. '''
  174. if not issubclass(type(data), dict):
  175. raise errors.AnsibleFilterError("|failed expects first param is a dict")
  176. if host_type not in ['master', 'node', 'etcd']:
  177. raise errors.AnsibleFilterError("|failed expects etcd, master or node"
  178. " as the host type")
  179. root_vol = data[host_type]['root']
  180. root_vol['device_name'] = '/dev/sda1'
  181. root_vol['delete_on_termination'] = True
  182. if root_vol['device_type'] != 'io1':
  183. root_vol.pop('iops', None)
  184. if host_type == 'node':
  185. docker_vol = data[host_type]['docker']
  186. docker_vol['device_name'] = '/dev/xvdb'
  187. docker_vol['delete_on_termination'] = True
  188. if docker_vol['device_type'] != 'io1':
  189. docker_vol.pop('iops', None)
  190. if docker_ephemeral:
  191. docker_vol.pop('device_type', None)
  192. docker_vol.pop('delete_on_termination', None)
  193. docker_vol['ephemeral'] = 'ephemeral0'
  194. return [root_vol, docker_vol]
  195. elif host_type == 'etcd':
  196. etcd_vol = data[host_type]['etcd']
  197. etcd_vol['device_name'] = '/dev/xvdb'
  198. etcd_vol['delete_on_termination'] = True
  199. if etcd_vol['device_type'] != 'io1':
  200. etcd_vol.pop('iops', None)
  201. return [root_vol, etcd_vol]
  202. return [root_vol]
  203. @staticmethod
  204. def oo_split(string, separator=','):
  205. ''' This splits the input string into a list
  206. '''
  207. return string.split(separator)
  208. @staticmethod
  209. def oo_filter_list(data, filter_attr=None):
  210. ''' This returns a list, which contains all items where filter_attr
  211. evaluates to true
  212. Ex: data = [ { a: 1, b: True },
  213. { a: 3, b: False },
  214. { a: 5, b: True } ]
  215. filter_attr = 'b'
  216. returns [ { a: 1, b: True },
  217. { a: 5, b: True } ]
  218. '''
  219. if not issubclass(type(data), list):
  220. raise errors.AnsibleFilterError("|failed expects to filter on a list")
  221. if not issubclass(type(filter_attr), str):
  222. raise errors.AnsibleFilterError("|failed expects filter_attr is a str")
  223. # Gather up the values for the list of keys passed in
  224. return [x for x in data if x[filter_attr]]
  225. @staticmethod
  226. def oo_parse_heat_stack_outputs(data):
  227. ''' Formats the HEAT stack output into a usable form
  228. The goal is to transform something like this:
  229. +---------------+-------------------------------------------------+
  230. | Property | Value |
  231. +---------------+-------------------------------------------------+
  232. | capabilities | [] | |
  233. | creation_time | 2015-06-26T12:26:26Z | |
  234. | description | OpenShift cluster | |
  235. | … | … |
  236. | outputs | [ |
  237. | | { |
  238. | | "output_value": "value_A" |
  239. | | "description": "This is the value of Key_A" |
  240. | | "output_key": "Key_A" |
  241. | | }, |
  242. | | { |
  243. | | "output_value": [ |
  244. | | "value_B1", |
  245. | | "value_B2" |
  246. | | ], |
  247. | | "description": "This is the value of Key_B" |
  248. | | "output_key": "Key_B" |
  249. | | }, |
  250. | | ] |
  251. | parameters | { |
  252. | … | … |
  253. +---------------+-------------------------------------------------+
  254. into something like this:
  255. {
  256. "Key_A": "value_A",
  257. "Key_B": [
  258. "value_B1",
  259. "value_B2"
  260. ]
  261. }
  262. '''
  263. # Extract the “outputs” JSON snippet from the pretty-printed array
  264. in_outputs = False
  265. outputs = ''
  266. line_regex = re.compile(r'\|\s*(.*?)\s*\|\s*(.*?)\s*\|')
  267. for line in data['stdout_lines']:
  268. match = line_regex.match(line)
  269. if match:
  270. if match.group(1) == 'outputs':
  271. in_outputs = True
  272. elif match.group(1) != '':
  273. in_outputs = False
  274. if in_outputs:
  275. outputs += match.group(2)
  276. outputs = json.loads(outputs)
  277. # Revamp the “outputs” to put it in the form of a “Key: value” map
  278. revamped_outputs = {}
  279. for output in outputs:
  280. revamped_outputs[output['output_key']] = output['output_value']
  281. return revamped_outputs
  282. def filters(self):
  283. ''' returns a mapping of filters to methods '''
  284. return {
  285. "oo_select_keys": self.oo_select_keys,
  286. "oo_select_keys_from_list": self.oo_select_keys_from_list,
  287. "oo_collect": self.oo_collect,
  288. "oo_flatten": self.oo_flatten,
  289. "oo_pdb": self.oo_pdb,
  290. "oo_prepend_strings_in_list": self.oo_prepend_strings_in_list,
  291. "oo_ami_selector": self.oo_ami_selector,
  292. "oo_ec2_volume_definition": self.oo_ec2_volume_definition,
  293. "oo_combine_key_value": self.oo_combine_key_value,
  294. "oo_combine_dict": self.oo_combine_dict,
  295. "oo_split": self.oo_split,
  296. "oo_filter_list": self.oo_filter_list,
  297. "oo_parse_heat_stack_outputs": self.oo_parse_heat_stack_outputs
  298. }