oo_filters.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  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("|fialed 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(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), dict):
  79. raise errors.AnsibleFilterError("|failed expects to filter on a dict")
  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 = [data[key] for key in keys]
  84. return retval
  85. @staticmethod
  86. def oo_prepend_strings_in_list(data, prepend):
  87. ''' This takes a list of strings and prepends a string to each item in the
  88. list
  89. Ex: data = ['cart', 'tree']
  90. prepend = 'apple-'
  91. returns ['apple-cart', 'apple-tree']
  92. '''
  93. if not issubclass(type(data), list):
  94. raise errors.AnsibleFilterError("|failed expects first param is a list")
  95. if not all(isinstance(x, basestring) for x in data):
  96. raise errors.AnsibleFilterError("|failed expects first param is a list"
  97. " of strings")
  98. retval = [prepend + s for s in data]
  99. return retval
  100. @staticmethod
  101. def oo_combine_key_value(data, joiner='='):
  102. '''Take a list of dict in the form of { 'key': 'value'} and
  103. arrange them as a list of strings ['key=value']
  104. '''
  105. if not issubclass(type(data), list):
  106. raise errors.AnsibleFilterError("|failed expects first param is a list")
  107. rval = []
  108. for item in data:
  109. rval.append("%s%s%s" % (item['key'], joiner, item['value']))
  110. return rval
  111. @staticmethod
  112. def oo_combine_dict(data, in_joiner='=', out_joiner=' '):
  113. '''Take a dict in the form of { 'key': 'value', 'key': 'value' } and
  114. arrange them as a string 'key=value key=value'
  115. '''
  116. if not issubclass(type(data), dict):
  117. raise errors.AnsibleFilterError("|failed expects first param is a dict")
  118. return out_joiner.join([in_joiner.join([k, v]) for k, v in data.items()])
  119. @staticmethod
  120. def oo_ami_selector(data, image_name):
  121. ''' This takes a list of amis and an image name and attempts to return
  122. the latest ami.
  123. '''
  124. if not issubclass(type(data), list):
  125. raise errors.AnsibleFilterError("|failed expects first param is a list")
  126. if not data:
  127. return None
  128. else:
  129. if image_name is None or not image_name.endswith('_*'):
  130. ami = sorted(data, key=itemgetter('name'), reverse=True)[0]
  131. return ami['ami_id']
  132. else:
  133. ami_info = [(ami, ami['name'].split('_')[-1]) for ami in data]
  134. ami = sorted(ami_info, key=itemgetter(1), reverse=True)[0][0]
  135. return ami['ami_id']
  136. @staticmethod
  137. def oo_ec2_volume_definition(data, host_type, docker_ephemeral=False):
  138. ''' This takes a dictionary of volume definitions and returns a valid ec2
  139. volume definition based on the host_type and the values in the
  140. dictionary.
  141. The dictionary should look similar to this:
  142. { 'master':
  143. { 'root':
  144. { 'volume_size': 10, 'device_type': 'gp2',
  145. 'iops': 500
  146. }
  147. },
  148. 'node':
  149. { 'root':
  150. { 'volume_size': 10, 'device_type': 'io1',
  151. 'iops': 1000
  152. },
  153. 'docker':
  154. { 'volume_size': 40, 'device_type': 'gp2',
  155. 'iops': 500, 'ephemeral': 'true'
  156. }
  157. }
  158. }
  159. '''
  160. if not issubclass(type(data), dict):
  161. raise errors.AnsibleFilterError("|failed expects first param is a dict")
  162. if host_type not in ['master', 'node', 'etcd']:
  163. raise errors.AnsibleFilterError("|failed expects etcd, master or node"
  164. " as the host type")
  165. root_vol = data[host_type]['root']
  166. root_vol['device_name'] = '/dev/sda1'
  167. root_vol['delete_on_termination'] = True
  168. if root_vol['device_type'] != 'io1':
  169. root_vol.pop('iops', None)
  170. if host_type == 'node':
  171. docker_vol = data[host_type]['docker']
  172. docker_vol['device_name'] = '/dev/xvdb'
  173. docker_vol['delete_on_termination'] = True
  174. if docker_vol['device_type'] != 'io1':
  175. docker_vol.pop('iops', None)
  176. if docker_ephemeral:
  177. docker_vol.pop('device_type', None)
  178. docker_vol.pop('delete_on_termination', None)
  179. docker_vol['ephemeral'] = 'ephemeral0'
  180. return [root_vol, docker_vol]
  181. elif host_type == 'etcd':
  182. etcd_vol = data[host_type]['etcd']
  183. etcd_vol['device_name'] = '/dev/xvdb'
  184. etcd_vol['delete_on_termination'] = True
  185. if etcd_vol['device_type'] != 'io1':
  186. etcd_vol.pop('iops', None)
  187. return [root_vol, etcd_vol]
  188. return [root_vol]
  189. @staticmethod
  190. def oo_split(string, separator=','):
  191. ''' This splits the input string into a list
  192. '''
  193. return string.split(separator)
  194. @staticmethod
  195. def oo_filter_list(data, filter_attr=None):
  196. ''' This returns a list, which contains all items where filter_attr
  197. evaluates to true
  198. Ex: data = [ { a: 1, b: True },
  199. { a: 3, b: False },
  200. { a: 5, b: True } ]
  201. filter_attr = 'b'
  202. returns [ { a: 1, b: True },
  203. { a: 5, b: True } ]
  204. '''
  205. if not issubclass(type(data), list):
  206. raise errors.AnsibleFilterError("|failed expects to filter on a list")
  207. if not issubclass(type(filter_attr), str):
  208. raise errors.AnsibleFilterError("|failed expects filter_attr is a str")
  209. # Gather up the values for the list of keys passed in
  210. return [x for x in data if x[filter_attr]]
  211. @staticmethod
  212. def oo_parse_heat_stack_outputs(data):
  213. ''' Formats the HEAT stack output into a usable form
  214. The goal is to transform something like this:
  215. +---------------+-------------------------------------------------+
  216. | Property | Value |
  217. +---------------+-------------------------------------------------+
  218. | capabilities | [] | |
  219. | creation_time | 2015-06-26T12:26:26Z | |
  220. | description | OpenShift cluster | |
  221. | … | … |
  222. | outputs | [ |
  223. | | { |
  224. | | "output_value": "value_A" |
  225. | | "description": "This is the value of Key_A" |
  226. | | "output_key": "Key_A" |
  227. | | }, |
  228. | | { |
  229. | | "output_value": [ |
  230. | | "value_B1", |
  231. | | "value_B2" |
  232. | | ], |
  233. | | "description": "This is the value of Key_B" |
  234. | | "output_key": "Key_B" |
  235. | | }, |
  236. | | ] |
  237. | parameters | { |
  238. | … | … |
  239. +---------------+-------------------------------------------------+
  240. into something like this:
  241. {
  242. "Key_A": "value_A",
  243. "Key_B": [
  244. "value_B1",
  245. "value_B2"
  246. ]
  247. }
  248. '''
  249. # Extract the “outputs” JSON snippet from the pretty-printed array
  250. in_outputs = False
  251. outputs = ''
  252. line_regex = re.compile(r'\|\s*(.*?)\s*\|\s*(.*?)\s*\|')
  253. for line in data['stdout_lines']:
  254. match = line_regex.match(line)
  255. if match:
  256. if match.group(1) == 'outputs':
  257. in_outputs = True
  258. elif match.group(1) != '':
  259. in_outputs = False
  260. if in_outputs:
  261. outputs += match.group(2)
  262. outputs = json.loads(outputs)
  263. # Revamp the “outputs” to put it in the form of a “Key: value” map
  264. revamped_outputs = {}
  265. for output in outputs:
  266. revamped_outputs[output['output_key']] = output['output_value']
  267. return revamped_outputs
  268. def filters(self):
  269. ''' returns a mapping of filters to methods '''
  270. return {
  271. "oo_select_keys": self.oo_select_keys,
  272. "oo_collect": self.oo_collect,
  273. "oo_flatten": self.oo_flatten,
  274. "oo_pdb": self.oo_pdb,
  275. "oo_prepend_strings_in_list": self.oo_prepend_strings_in_list,
  276. "oo_ami_selector": self.oo_ami_selector,
  277. "oo_ec2_volume_definition": self.oo_ec2_volume_definition,
  278. "oo_combine_key_value": self.oo_combine_key_value,
  279. "oo_combine_dict": self.oo_combine_dict,
  280. "oo_split": self.oo_split,
  281. "oo_filter_list": self.oo_filter_list,
  282. "oo_parse_heat_stack_outputs": self.oo_parse_heat_stack_outputs
  283. }