oc_volume.py 71 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143
  1. #!/usr/bin/env python
  2. # pylint: disable=missing-docstring
  3. # flake8: noqa: T001
  4. # ___ ___ _ _ ___ ___ _ _____ ___ ___
  5. # / __| __| \| | __| _ \ /_\_ _| __| \
  6. # | (_ | _|| .` | _|| / / _ \| | | _|| |) |
  7. # \___|___|_|\_|___|_|_\/_/_\_\_|_|___|___/_ _____
  8. # | \ / _ \ | \| |/ _ \_ _| | __| \_ _|_ _|
  9. # | |) | (_) | | .` | (_) || | | _|| |) | | | |
  10. # |___/ \___/ |_|\_|\___/ |_| |___|___/___| |_|
  11. #
  12. # Copyright 2016 Red Hat, Inc. and/or its affiliates
  13. # and other contributors as indicated by the @author tags.
  14. #
  15. # Licensed under the Apache License, Version 2.0 (the "License");
  16. # you may not use this file except in compliance with the License.
  17. # You may obtain a copy of the License at
  18. #
  19. # http://www.apache.org/licenses/LICENSE-2.0
  20. #
  21. # Unless required by applicable law or agreed to in writing, software
  22. # distributed under the License is distributed on an "AS IS" BASIS,
  23. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24. # See the License for the specific language governing permissions and
  25. # limitations under the License.
  26. #
  27. # -*- -*- -*- Begin included fragment: lib/import.py -*- -*- -*-
  28. '''
  29. OpenShiftCLI class that wraps the oc commands in a subprocess
  30. '''
  31. # pylint: disable=too-many-lines
  32. from __future__ import print_function
  33. import atexit
  34. import copy
  35. import fcntl
  36. import json
  37. import time
  38. import os
  39. import re
  40. import shutil
  41. import subprocess
  42. import tempfile
  43. # pylint: disable=import-error
  44. try:
  45. import ruamel.yaml as yaml
  46. except ImportError:
  47. import yaml
  48. from ansible.module_utils.basic import AnsibleModule
  49. # -*- -*- -*- End included fragment: lib/import.py -*- -*- -*-
  50. # -*- -*- -*- Begin included fragment: doc/volume -*- -*- -*-
  51. DOCUMENTATION = '''
  52. ---
  53. module: oc_volume
  54. short_description: Create, modify, and idempotently manage openshift volumes.
  55. description:
  56. - Modify openshift volumes programmatically.
  57. options:
  58. state:
  59. description:
  60. - State controls the action that will be taken with resource
  61. - 'present' will create or update and object to the desired state
  62. - 'absent' will ensure volumes are removed
  63. - 'list' will read the volumes
  64. default: present
  65. choices: ["present", "absent", "list"]
  66. aliases: []
  67. kubeconfig:
  68. description:
  69. - The path for the kubeconfig file to use for authentication
  70. required: false
  71. default: /etc/origin/master/admin.kubeconfig
  72. aliases: []
  73. debug:
  74. description:
  75. - Turn on debug output.
  76. required: false
  77. default: False
  78. aliases: []
  79. name:
  80. description:
  81. - Name of the object that is being queried.
  82. required: false
  83. default: None
  84. aliases: []
  85. vol_name:
  86. description:
  87. - Name of the volume that is being queried.
  88. required: false
  89. default: None
  90. aliases: []
  91. namespace:
  92. description:
  93. - The name of the namespace where the object lives
  94. required: false
  95. default: default
  96. aliases: []
  97. kind:
  98. description:
  99. - The kind of object that can be managed.
  100. default: dc
  101. choices:
  102. - dc
  103. - rc
  104. - pods
  105. aliases: []
  106. mount_type:
  107. description:
  108. - The type of volume to be used
  109. required: false
  110. default: None
  111. choices:
  112. - emptydir
  113. - hostpath
  114. - secret
  115. - pvc
  116. - configmap
  117. aliases: []
  118. mount_path:
  119. description:
  120. - The path to where the mount will be attached
  121. required: false
  122. default: None
  123. aliases: []
  124. secret_name:
  125. description:
  126. - The name of the secret. Used when mount_type is secret.
  127. required: false
  128. default: None
  129. aliases: []
  130. claim_size:
  131. description:
  132. - The size in GB of the pv claim. e.g. 100G
  133. required: false
  134. default: None
  135. aliases: []
  136. claim_name:
  137. description:
  138. - The name of the pv claim
  139. required: false
  140. default: None
  141. aliases: []
  142. configmap_name:
  143. description:
  144. - The name of the configmap
  145. required: false
  146. default: None
  147. aliases: []
  148. author:
  149. - "Kenny Woodson <kwoodson@redhat.com>"
  150. extends_documentation_fragment: []
  151. '''
  152. EXAMPLES = '''
  153. - name: attach storage volumes to deploymentconfig
  154. oc_volume:
  155. namespace: logging
  156. kind: dc
  157. name: name_of_the_dc
  158. mount_type: pvc
  159. claim_name: loggingclaim
  160. claim_size: 100G
  161. vol_name: logging-storage
  162. run_once: true
  163. '''
  164. # -*- -*- -*- End included fragment: doc/volume -*- -*- -*-
  165. # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  166. class YeditException(Exception): # pragma: no cover
  167. ''' Exception class for Yedit '''
  168. pass
  169. # pylint: disable=too-many-public-methods,too-many-instance-attributes
  170. class Yedit(object): # pragma: no cover
  171. ''' Class to modify yaml files '''
  172. re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$"
  173. re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z{}/_-]+)"
  174. com_sep = set(['.', '#', '|', ':'])
  175. # pylint: disable=too-many-arguments
  176. def __init__(self,
  177. filename=None,
  178. content=None,
  179. content_type='yaml',
  180. separator='.',
  181. backup_ext=None,
  182. backup=False):
  183. self.content = content
  184. self._separator = separator
  185. self.filename = filename
  186. self.__yaml_dict = content
  187. self.content_type = content_type
  188. self.backup = backup
  189. if backup_ext is None:
  190. self.backup_ext = ".{}".format(time.strftime("%Y%m%dT%H%M%S"))
  191. else:
  192. self.backup_ext = backup_ext
  193. self.load(content_type=self.content_type)
  194. if self.__yaml_dict is None:
  195. self.__yaml_dict = {}
  196. @property
  197. def separator(self):
  198. ''' getter method for separator '''
  199. return self._separator
  200. @separator.setter
  201. def separator(self, inc_sep):
  202. ''' setter method for separator '''
  203. self._separator = inc_sep
  204. @property
  205. def yaml_dict(self):
  206. ''' getter method for yaml_dict '''
  207. return self.__yaml_dict
  208. @yaml_dict.setter
  209. def yaml_dict(self, value):
  210. ''' setter method for yaml_dict '''
  211. self.__yaml_dict = value
  212. @staticmethod
  213. def parse_key(key, sep='.'):
  214. '''parse the key allowing the appropriate separator'''
  215. common_separators = list(Yedit.com_sep - set([sep]))
  216. return re.findall(Yedit.re_key.format(''.join(common_separators)), key)
  217. @staticmethod
  218. def valid_key(key, sep='.'):
  219. '''validate the incoming key'''
  220. common_separators = list(Yedit.com_sep - set([sep]))
  221. if not re.match(Yedit.re_valid_key.format(''.join(common_separators)), key):
  222. return False
  223. return True
  224. # pylint: disable=too-many-return-statements,too-many-branches
  225. @staticmethod
  226. def remove_entry(data, key, index=None, value=None, sep='.'):
  227. ''' remove data at location key '''
  228. if key == '' and isinstance(data, dict):
  229. if value is not None:
  230. data.pop(value)
  231. elif index is not None:
  232. raise YeditException("remove_entry for a dictionary does not have an index {}".format(index))
  233. else:
  234. data.clear()
  235. return True
  236. elif key == '' and isinstance(data, list):
  237. ind = None
  238. if value is not None:
  239. try:
  240. ind = data.index(value)
  241. except ValueError:
  242. return False
  243. elif index is not None:
  244. ind = index
  245. else:
  246. del data[:]
  247. if ind is not None:
  248. data.pop(ind)
  249. return True
  250. if not (key and Yedit.valid_key(key, sep)) and \
  251. isinstance(data, (list, dict)):
  252. return None
  253. key_indexes = Yedit.parse_key(key, sep)
  254. for arr_ind, dict_key in key_indexes[:-1]:
  255. if dict_key and isinstance(data, dict):
  256. data = data.get(dict_key)
  257. elif (arr_ind and isinstance(data, list) and
  258. int(arr_ind) <= len(data) - 1):
  259. data = data[int(arr_ind)]
  260. else:
  261. return None
  262. # process last index for remove
  263. # expected list entry
  264. if key_indexes[-1][0]:
  265. if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  266. del data[int(key_indexes[-1][0])]
  267. return True
  268. # expected dict entry
  269. elif key_indexes[-1][1]:
  270. if isinstance(data, dict):
  271. del data[key_indexes[-1][1]]
  272. return True
  273. @staticmethod
  274. def add_entry(data, key, item=None, sep='.'):
  275. ''' Get an item from a dictionary with key notation a.b.c
  276. d = {'a': {'b': 'c'}}}
  277. key = a#b
  278. return c
  279. '''
  280. if key == '':
  281. pass
  282. elif (not (key and Yedit.valid_key(key, sep)) and
  283. isinstance(data, (list, dict))):
  284. return None
  285. key_indexes = Yedit.parse_key(key, sep)
  286. for arr_ind, dict_key in key_indexes[:-1]:
  287. if dict_key:
  288. if isinstance(data, dict) and dict_key in data and data[dict_key]: # noqa: E501
  289. data = data[dict_key]
  290. continue
  291. elif data and not isinstance(data, dict):
  292. raise YeditException("Unexpected item type found while going through key " +
  293. "path: {} (at key: {})".format(key, dict_key))
  294. data[dict_key] = {}
  295. data = data[dict_key]
  296. elif (arr_ind and isinstance(data, list) and
  297. int(arr_ind) <= len(data) - 1):
  298. data = data[int(arr_ind)]
  299. else:
  300. raise YeditException("Unexpected item type found while going through key path: {}".format(key))
  301. if key == '':
  302. data = item
  303. # process last index for add
  304. # expected list entry
  305. elif key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  306. data[int(key_indexes[-1][0])] = item
  307. # expected dict entry
  308. elif key_indexes[-1][1] and isinstance(data, dict):
  309. data[key_indexes[-1][1]] = item
  310. # didn't add/update to an existing list, nor add/update key to a dict
  311. # so we must have been provided some syntax like a.b.c[<int>] = "data" for a
  312. # non-existent array
  313. else:
  314. raise YeditException("Error adding to object at path: {}".format(key))
  315. return data
  316. @staticmethod
  317. def get_entry(data, key, sep='.'):
  318. ''' Get an item from a dictionary with key notation a.b.c
  319. d = {'a': {'b': 'c'}}}
  320. key = a.b
  321. return c
  322. '''
  323. if key == '':
  324. pass
  325. elif (not (key and Yedit.valid_key(key, sep)) and
  326. isinstance(data, (list, dict))):
  327. return None
  328. key_indexes = Yedit.parse_key(key, sep)
  329. for arr_ind, dict_key in key_indexes:
  330. if dict_key and isinstance(data, dict):
  331. data = data.get(dict_key)
  332. elif (arr_ind and isinstance(data, list) and
  333. int(arr_ind) <= len(data) - 1):
  334. data = data[int(arr_ind)]
  335. else:
  336. return None
  337. return data
  338. @staticmethod
  339. def _write(filename, contents):
  340. ''' Actually write the file contents to disk. This helps with mocking. '''
  341. tmp_filename = filename + '.yedit'
  342. with open(tmp_filename, 'w') as yfd:
  343. fcntl.flock(yfd, fcntl.LOCK_EX | fcntl.LOCK_NB)
  344. yfd.write(contents)
  345. fcntl.flock(yfd, fcntl.LOCK_UN)
  346. os.rename(tmp_filename, filename)
  347. def write(self):
  348. ''' write to file '''
  349. if not self.filename:
  350. raise YeditException('Please specify a filename.')
  351. if self.backup and self.file_exists():
  352. shutil.copy(self.filename, '{}{}'.format(self.filename, self.backup_ext))
  353. # Try to set format attributes if supported
  354. try:
  355. self.yaml_dict.fa.set_block_style()
  356. except AttributeError:
  357. pass
  358. # Try to use RoundTripDumper if supported.
  359. if self.content_type == 'yaml':
  360. try:
  361. Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper))
  362. except AttributeError:
  363. Yedit._write(self.filename, yaml.safe_dump(self.yaml_dict, default_flow_style=False))
  364. elif self.content_type == 'json':
  365. Yedit._write(self.filename, json.dumps(self.yaml_dict, indent=4, sort_keys=True))
  366. else:
  367. raise YeditException('Unsupported content_type: {}.'.format(self.content_type) +
  368. 'Please specify a content_type of yaml or json.')
  369. return (True, self.yaml_dict)
  370. def read(self):
  371. ''' read from file '''
  372. # check if it exists
  373. if self.filename is None or not self.file_exists():
  374. return None
  375. contents = None
  376. with open(self.filename) as yfd:
  377. contents = yfd.read()
  378. return contents
  379. def file_exists(self):
  380. ''' return whether file exists '''
  381. if os.path.exists(self.filename):
  382. return True
  383. return False
  384. def load(self, content_type='yaml'):
  385. ''' return yaml file '''
  386. contents = self.read()
  387. if not contents and not self.content:
  388. return None
  389. if self.content:
  390. if isinstance(self.content, dict):
  391. self.yaml_dict = self.content
  392. return self.yaml_dict
  393. elif isinstance(self.content, str):
  394. contents = self.content
  395. # check if it is yaml
  396. try:
  397. if content_type == 'yaml' and contents:
  398. # Try to set format attributes if supported
  399. try:
  400. self.yaml_dict.fa.set_block_style()
  401. except AttributeError:
  402. pass
  403. # Try to use RoundTripLoader if supported.
  404. try:
  405. self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader)
  406. except AttributeError:
  407. self.yaml_dict = yaml.safe_load(contents)
  408. # Try to set format attributes if supported
  409. try:
  410. self.yaml_dict.fa.set_block_style()
  411. except AttributeError:
  412. pass
  413. elif content_type == 'json' and contents:
  414. self.yaml_dict = json.loads(contents)
  415. except yaml.YAMLError as err:
  416. # Error loading yaml or json
  417. raise YeditException('Problem with loading yaml file. {}'.format(err))
  418. return self.yaml_dict
  419. def get(self, key):
  420. ''' get a specified key'''
  421. try:
  422. entry = Yedit.get_entry(self.yaml_dict, key, self.separator)
  423. except KeyError:
  424. entry = None
  425. return entry
  426. def pop(self, path, key_or_item):
  427. ''' remove a key, value pair from a dict or an item for a list'''
  428. try:
  429. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  430. except KeyError:
  431. entry = None
  432. if entry is None:
  433. return (False, self.yaml_dict)
  434. if isinstance(entry, dict):
  435. # AUDIT:maybe-no-member makes sense due to fuzzy types
  436. # pylint: disable=maybe-no-member
  437. if key_or_item in entry:
  438. entry.pop(key_or_item)
  439. return (True, self.yaml_dict)
  440. return (False, self.yaml_dict)
  441. elif isinstance(entry, list):
  442. # AUDIT:maybe-no-member makes sense due to fuzzy types
  443. # pylint: disable=maybe-no-member
  444. ind = None
  445. try:
  446. ind = entry.index(key_or_item)
  447. except ValueError:
  448. return (False, self.yaml_dict)
  449. entry.pop(ind)
  450. return (True, self.yaml_dict)
  451. return (False, self.yaml_dict)
  452. def delete(self, path, index=None, value=None):
  453. ''' remove path from a dict'''
  454. try:
  455. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  456. except KeyError:
  457. entry = None
  458. if entry is None:
  459. return (False, self.yaml_dict)
  460. result = Yedit.remove_entry(self.yaml_dict, path, index, value, self.separator)
  461. if not result:
  462. return (False, self.yaml_dict)
  463. return (True, self.yaml_dict)
  464. def exists(self, path, value):
  465. ''' check if value exists at path'''
  466. try:
  467. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  468. except KeyError:
  469. entry = None
  470. if isinstance(entry, list):
  471. if value in entry:
  472. return True
  473. return False
  474. elif isinstance(entry, dict):
  475. if isinstance(value, dict):
  476. rval = False
  477. for key, val in value.items():
  478. if entry[key] != val:
  479. rval = False
  480. break
  481. else:
  482. rval = True
  483. return rval
  484. return value in entry
  485. return entry == value
  486. def append(self, path, value):
  487. '''append value to a list'''
  488. try:
  489. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  490. except KeyError:
  491. entry = None
  492. if entry is None:
  493. self.put(path, [])
  494. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  495. if not isinstance(entry, list):
  496. return (False, self.yaml_dict)
  497. # AUDIT:maybe-no-member makes sense due to loading data from
  498. # a serialized format.
  499. # pylint: disable=maybe-no-member
  500. entry.append(value)
  501. return (True, self.yaml_dict)
  502. # pylint: disable=too-many-arguments
  503. def update(self, path, value, index=None, curr_value=None):
  504. ''' put path, value into a dict '''
  505. try:
  506. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  507. except KeyError:
  508. entry = None
  509. if isinstance(entry, dict):
  510. # AUDIT:maybe-no-member makes sense due to fuzzy types
  511. # pylint: disable=maybe-no-member
  512. if not isinstance(value, dict):
  513. raise YeditException('Cannot replace key, value entry in dict with non-dict type. ' +
  514. 'value=[{}] type=[{}]'.format(value, type(value)))
  515. entry.update(value)
  516. return (True, self.yaml_dict)
  517. elif isinstance(entry, list):
  518. # AUDIT:maybe-no-member makes sense due to fuzzy types
  519. # pylint: disable=maybe-no-member
  520. ind = None
  521. if curr_value:
  522. try:
  523. ind = entry.index(curr_value)
  524. except ValueError:
  525. return (False, self.yaml_dict)
  526. elif index is not None:
  527. ind = index
  528. if ind is not None and entry[ind] != value:
  529. entry[ind] = value
  530. return (True, self.yaml_dict)
  531. # see if it exists in the list
  532. try:
  533. ind = entry.index(value)
  534. except ValueError:
  535. # doesn't exist, append it
  536. entry.append(value)
  537. return (True, self.yaml_dict)
  538. # already exists, return
  539. if ind is not None:
  540. return (False, self.yaml_dict)
  541. return (False, self.yaml_dict)
  542. def put(self, path, value):
  543. ''' put path, value into a dict '''
  544. try:
  545. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  546. except KeyError:
  547. entry = None
  548. if entry == value:
  549. return (False, self.yaml_dict)
  550. # deepcopy didn't work
  551. # Try to use ruamel.yaml and fallback to pyyaml
  552. try:
  553. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  554. default_flow_style=False),
  555. yaml.RoundTripLoader)
  556. except AttributeError:
  557. tmp_copy = copy.deepcopy(self.yaml_dict)
  558. # set the format attributes if available
  559. try:
  560. tmp_copy.fa.set_block_style()
  561. except AttributeError:
  562. pass
  563. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  564. if result is None:
  565. return (False, self.yaml_dict)
  566. # When path equals "" it is a special case.
  567. # "" refers to the root of the document
  568. # Only update the root path (entire document) when its a list or dict
  569. if path == '':
  570. if isinstance(result, list) or isinstance(result, dict):
  571. self.yaml_dict = result
  572. return (True, self.yaml_dict)
  573. return (False, self.yaml_dict)
  574. self.yaml_dict = tmp_copy
  575. return (True, self.yaml_dict)
  576. def create(self, path, value):
  577. ''' create a yaml file '''
  578. if not self.file_exists():
  579. # deepcopy didn't work
  580. # Try to use ruamel.yaml and fallback to pyyaml
  581. try:
  582. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  583. default_flow_style=False),
  584. yaml.RoundTripLoader)
  585. except AttributeError:
  586. tmp_copy = copy.deepcopy(self.yaml_dict)
  587. # set the format attributes if available
  588. try:
  589. tmp_copy.fa.set_block_style()
  590. except AttributeError:
  591. pass
  592. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  593. if result is not None:
  594. self.yaml_dict = tmp_copy
  595. return (True, self.yaml_dict)
  596. return (False, self.yaml_dict)
  597. @staticmethod
  598. def get_curr_value(invalue, val_type):
  599. '''return the current value'''
  600. if invalue is None:
  601. return None
  602. curr_value = invalue
  603. if val_type == 'yaml':
  604. curr_value = yaml.safe_load(str(invalue))
  605. elif val_type == 'json':
  606. curr_value = json.loads(invalue)
  607. return curr_value
  608. @staticmethod
  609. def parse_value(inc_value, vtype=''):
  610. '''determine value type passed'''
  611. true_bools = ['y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE',
  612. 'on', 'On', 'ON', ]
  613. false_bools = ['n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE',
  614. 'off', 'Off', 'OFF']
  615. # It came in as a string but you didn't specify value_type as string
  616. # we will convert to bool if it matches any of the above cases
  617. if isinstance(inc_value, str) and 'bool' in vtype:
  618. if inc_value not in true_bools and inc_value not in false_bools:
  619. raise YeditException('Not a boolean type. str=[{}] vtype=[{}]'.format(inc_value, vtype))
  620. elif isinstance(inc_value, bool) and 'str' in vtype:
  621. inc_value = str(inc_value)
  622. # There is a special case where '' will turn into None after yaml loading it so skip
  623. if isinstance(inc_value, str) and inc_value == '':
  624. pass
  625. # If vtype is not str then go ahead and attempt to yaml load it.
  626. elif isinstance(inc_value, str) and 'str' not in vtype:
  627. try:
  628. inc_value = yaml.safe_load(inc_value)
  629. except Exception:
  630. raise YeditException('Could not determine type of incoming value. ' +
  631. 'value=[{}] vtype=[{}]'.format(type(inc_value), vtype))
  632. return inc_value
  633. @staticmethod
  634. def process_edits(edits, yamlfile):
  635. '''run through a list of edits and process them one-by-one'''
  636. results = []
  637. for edit in edits:
  638. value = Yedit.parse_value(edit['value'], edit.get('value_type', ''))
  639. if edit.get('action') == 'update':
  640. # pylint: disable=line-too-long
  641. curr_value = Yedit.get_curr_value(
  642. Yedit.parse_value(edit.get('curr_value')),
  643. edit.get('curr_value_format'))
  644. rval = yamlfile.update(edit['key'],
  645. value,
  646. edit.get('index'),
  647. curr_value)
  648. elif edit.get('action') == 'append':
  649. rval = yamlfile.append(edit['key'], value)
  650. else:
  651. rval = yamlfile.put(edit['key'], value)
  652. if rval[0]:
  653. results.append({'key': edit['key'], 'edit': rval[1]})
  654. return {'changed': len(results) > 0, 'results': results}
  655. # pylint: disable=too-many-return-statements,too-many-branches
  656. @staticmethod
  657. def run_ansible(params):
  658. '''perform the idempotent crud operations'''
  659. yamlfile = Yedit(filename=params['src'],
  660. backup=params['backup'],
  661. content_type=params['content_type'],
  662. backup_ext=params['backup_ext'],
  663. separator=params['separator'])
  664. state = params['state']
  665. if params['src']:
  666. rval = yamlfile.load()
  667. if yamlfile.yaml_dict is None and state != 'present':
  668. return {'failed': True,
  669. 'msg': 'Error opening file [{}]. Verify that the '.format(params['src']) +
  670. 'file exists, that it is has correct permissions, and is valid yaml.'}
  671. if state == 'list':
  672. if params['content']:
  673. content = Yedit.parse_value(params['content'], params['content_type'])
  674. yamlfile.yaml_dict = content
  675. if params['key']:
  676. rval = yamlfile.get(params['key'])
  677. return {'changed': False, 'result': rval, 'state': state}
  678. elif state == 'absent':
  679. if params['content']:
  680. content = Yedit.parse_value(params['content'], params['content_type'])
  681. yamlfile.yaml_dict = content
  682. if params['update']:
  683. rval = yamlfile.pop(params['key'], params['value'])
  684. else:
  685. rval = yamlfile.delete(params['key'], params['index'], params['value'])
  686. if rval[0] and params['src']:
  687. yamlfile.write()
  688. return {'changed': rval[0], 'result': rval[1], 'state': state}
  689. elif state == 'present':
  690. # check if content is different than what is in the file
  691. if params['content']:
  692. content = Yedit.parse_value(params['content'], params['content_type'])
  693. # We had no edits to make and the contents are the same
  694. if yamlfile.yaml_dict == content and \
  695. params['value'] is None:
  696. return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
  697. yamlfile.yaml_dict = content
  698. # If we were passed a key, value then
  699. # we enapsulate it in a list and process it
  700. # Key, Value passed to the module : Converted to Edits list #
  701. edits = []
  702. _edit = {}
  703. if params['value'] is not None:
  704. _edit['value'] = params['value']
  705. _edit['value_type'] = params['value_type']
  706. _edit['key'] = params['key']
  707. if params['update']:
  708. _edit['action'] = 'update'
  709. _edit['curr_value'] = params['curr_value']
  710. _edit['curr_value_format'] = params['curr_value_format']
  711. _edit['index'] = params['index']
  712. elif params['append']:
  713. _edit['action'] = 'append'
  714. edits.append(_edit)
  715. elif params['edits'] is not None:
  716. edits = params['edits']
  717. if edits:
  718. results = Yedit.process_edits(edits, yamlfile)
  719. # if there were changes and a src provided to us we need to write
  720. if results['changed'] and params['src']:
  721. yamlfile.write()
  722. return {'changed': results['changed'], 'result': results['results'], 'state': state}
  723. # no edits to make
  724. if params['src']:
  725. # pylint: disable=redefined-variable-type
  726. rval = yamlfile.write()
  727. return {'changed': rval[0],
  728. 'result': rval[1],
  729. 'state': state}
  730. # We were passed content but no src, key or value, or edits. Return contents in memory
  731. return {'changed': False, 'result': yamlfile.yaml_dict, 'state': state}
  732. return {'failed': True, 'msg': 'Unkown state passed'}
  733. # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  734. # -*- -*- -*- Begin included fragment: lib/base.py -*- -*- -*-
  735. # pylint: disable=too-many-lines
  736. # noqa: E301,E302,E303,T001
  737. class OpenShiftCLIError(Exception):
  738. '''Exception class for openshiftcli'''
  739. pass
  740. ADDITIONAL_PATH_LOOKUPS = ['/usr/local/bin', os.path.expanduser('~/bin')]
  741. def locate_oc_binary():
  742. ''' Find and return oc binary file '''
  743. # https://github.com/openshift/openshift-ansible/issues/3410
  744. # oc can be in /usr/local/bin in some cases, but that may not
  745. # be in $PATH due to ansible/sudo
  746. paths = os.environ.get("PATH", os.defpath).split(os.pathsep) + ADDITIONAL_PATH_LOOKUPS
  747. oc_binary = 'oc'
  748. # Use shutil.which if it is available, otherwise fallback to a naive path search
  749. try:
  750. which_result = shutil.which(oc_binary, path=os.pathsep.join(paths))
  751. if which_result is not None:
  752. oc_binary = which_result
  753. except AttributeError:
  754. for path in paths:
  755. if os.path.exists(os.path.join(path, oc_binary)):
  756. oc_binary = os.path.join(path, oc_binary)
  757. break
  758. return oc_binary
  759. # pylint: disable=too-few-public-methods
  760. class OpenShiftCLI(object):
  761. ''' Class to wrap the command line tools '''
  762. def __init__(self,
  763. namespace,
  764. kubeconfig='/etc/origin/master/admin.kubeconfig',
  765. verbose=False,
  766. all_namespaces=False):
  767. ''' Constructor for OpenshiftCLI '''
  768. self.namespace = namespace
  769. self.verbose = verbose
  770. self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
  771. self.all_namespaces = all_namespaces
  772. self.oc_binary = locate_oc_binary()
  773. # Pylint allows only 5 arguments to be passed.
  774. # pylint: disable=too-many-arguments
  775. def _replace_content(self, resource, rname, content, edits=None, force=False, sep='.'):
  776. ''' replace the current object with the content '''
  777. res = self._get(resource, rname)
  778. if not res['results']:
  779. return res
  780. fname = Utils.create_tmpfile(rname + '-')
  781. yed = Yedit(fname, res['results'][0], separator=sep)
  782. updated = False
  783. if content is not None:
  784. changes = []
  785. for key, value in content.items():
  786. changes.append(yed.put(key, value))
  787. if any([change[0] for change in changes]):
  788. updated = True
  789. elif edits is not None:
  790. results = Yedit.process_edits(edits, yed)
  791. if results['changed']:
  792. updated = True
  793. if updated:
  794. yed.write()
  795. atexit.register(Utils.cleanup, [fname])
  796. return self._replace(fname, force)
  797. return {'returncode': 0, 'updated': False}
  798. def _replace(self, fname, force=False):
  799. '''replace the current object with oc replace'''
  800. # We are removing the 'resourceVersion' to handle
  801. # a race condition when modifying oc objects
  802. yed = Yedit(fname)
  803. results = yed.delete('metadata.resourceVersion')
  804. if results[0]:
  805. yed.write()
  806. cmd = ['replace', '-f', fname]
  807. if force:
  808. cmd.append('--force')
  809. return self.openshift_cmd(cmd)
  810. def _create_from_content(self, rname, content):
  811. '''create a temporary file and then call oc create on it'''
  812. fname = Utils.create_tmpfile(rname + '-')
  813. yed = Yedit(fname, content=content)
  814. yed.write()
  815. atexit.register(Utils.cleanup, [fname])
  816. return self._create(fname)
  817. def _create(self, fname):
  818. '''call oc create on a filename'''
  819. return self.openshift_cmd(['create', '-f', fname])
  820. def _delete(self, resource, name=None, selector=None):
  821. '''call oc delete on a resource'''
  822. cmd = ['delete', resource]
  823. if selector is not None:
  824. cmd.append('--selector={}'.format(selector))
  825. elif name is not None:
  826. cmd.append(name)
  827. else:
  828. raise OpenShiftCLIError('Either name or selector is required when calling delete.')
  829. return self.openshift_cmd(cmd)
  830. def _process(self, template_name, create=False, params=None, template_data=None): # noqa: E501
  831. '''process a template
  832. template_name: the name of the template to process
  833. create: whether to send to oc create after processing
  834. params: the parameters for the template
  835. template_data: the incoming template's data; instead of a file
  836. '''
  837. cmd = ['process']
  838. if template_data:
  839. cmd.extend(['-f', '-'])
  840. else:
  841. cmd.append(template_name)
  842. if params:
  843. param_str = ["{}={}".format(key, str(value).replace("'", r'"')) for key, value in params.items()]
  844. cmd.append('-p')
  845. cmd.extend(param_str)
  846. results = self.openshift_cmd(cmd, output=True, input_data=template_data)
  847. if results['returncode'] != 0 or not create:
  848. return results
  849. fname = Utils.create_tmpfile(template_name + '-')
  850. yed = Yedit(fname, results['results'])
  851. yed.write()
  852. atexit.register(Utils.cleanup, [fname])
  853. return self.openshift_cmd(['create', '-f', fname])
  854. def _get(self, resource, name=None, selector=None, field_selector=None):
  855. '''return a resource by name '''
  856. cmd = ['get', resource]
  857. if selector is not None:
  858. cmd.append('--selector={}'.format(selector))
  859. if field_selector is not None:
  860. cmd.append('--field-selector={}'.format(field_selector))
  861. # Name cannot be used with selector or field_selector.
  862. if selector is None and field_selector is None and name is not None:
  863. cmd.append(name)
  864. cmd.extend(['-o', 'json'])
  865. rval = self.openshift_cmd(cmd, output=True)
  866. # Ensure results are retuned in an array
  867. if 'items' in rval:
  868. rval['results'] = rval['items']
  869. elif not isinstance(rval['results'], list):
  870. rval['results'] = [rval['results']]
  871. return rval
  872. def _schedulable(self, node=None, selector=None, schedulable=True):
  873. ''' perform oadm manage-node scheduable '''
  874. cmd = ['manage-node']
  875. if node:
  876. cmd.extend(node)
  877. else:
  878. cmd.append('--selector={}'.format(selector))
  879. cmd.append('--schedulable={}'.format(schedulable))
  880. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') # noqa: E501
  881. def _list_pods(self, node=None, selector=None, pod_selector=None):
  882. ''' perform oadm list pods
  883. node: the node in which to list pods
  884. selector: the label selector filter if provided
  885. pod_selector: the pod selector filter if provided
  886. '''
  887. cmd = ['manage-node']
  888. if node:
  889. cmd.extend(node)
  890. else:
  891. cmd.append('--selector={}'.format(selector))
  892. if pod_selector:
  893. cmd.append('--pod-selector={}'.format(pod_selector))
  894. cmd.extend(['--list-pods', '-o', 'json'])
  895. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  896. # pylint: disable=too-many-arguments
  897. def _evacuate(self, node=None, selector=None, pod_selector=None, dry_run=False, grace_period=None, force=False):
  898. ''' perform oadm manage-node evacuate '''
  899. cmd = ['manage-node']
  900. if node:
  901. cmd.extend(node)
  902. else:
  903. cmd.append('--selector={}'.format(selector))
  904. if dry_run:
  905. cmd.append('--dry-run')
  906. if pod_selector:
  907. cmd.append('--pod-selector={}'.format(pod_selector))
  908. if grace_period:
  909. cmd.append('--grace-period={}'.format(int(grace_period)))
  910. if force:
  911. cmd.append('--force')
  912. cmd.append('--evacuate')
  913. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  914. def _version(self):
  915. ''' return the openshift version'''
  916. return self.openshift_cmd(['version'], output=True, output_type='raw')
  917. def _import_image(self, url=None, name=None, tag=None):
  918. ''' perform image import '''
  919. cmd = ['import-image']
  920. image = '{0}'.format(name)
  921. if tag:
  922. image += ':{0}'.format(tag)
  923. cmd.append(image)
  924. if url:
  925. cmd.append('--from={0}/{1}'.format(url, image))
  926. cmd.append('-n{0}'.format(self.namespace))
  927. cmd.append('--confirm')
  928. return self.openshift_cmd(cmd)
  929. def _run(self, cmds, input_data):
  930. ''' Actually executes the command. This makes mocking easier. '''
  931. curr_env = os.environ.copy()
  932. curr_env.update({'KUBECONFIG': self.kubeconfig})
  933. proc = subprocess.Popen(cmds,
  934. stdin=subprocess.PIPE,
  935. stdout=subprocess.PIPE,
  936. stderr=subprocess.PIPE,
  937. env=curr_env)
  938. stdout, stderr = proc.communicate(input_data)
  939. return proc.returncode, stdout.decode('utf-8'), stderr.decode('utf-8')
  940. # pylint: disable=too-many-arguments,too-many-branches
  941. def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
  942. '''Base command for oc '''
  943. cmds = [self.oc_binary]
  944. if oadm:
  945. cmds.append('adm')
  946. cmds.extend(cmd)
  947. if self.all_namespaces:
  948. cmds.extend(['--all-namespaces'])
  949. elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501
  950. cmds.extend(['-n', self.namespace])
  951. if self.verbose:
  952. print(' '.join(cmds))
  953. try:
  954. returncode, stdout, stderr = self._run(cmds, input_data)
  955. except OSError as ex:
  956. returncode, stdout, stderr = 1, '', 'Failed to execute {}: {}'.format(subprocess.list2cmdline(cmds), ex)
  957. rval = {"returncode": returncode,
  958. "cmd": ' '.join(cmds)}
  959. if output_type == 'json':
  960. rval['results'] = {}
  961. if output and stdout:
  962. try:
  963. rval['results'] = json.loads(stdout)
  964. except ValueError as verr:
  965. if "No JSON object could be decoded" in verr.args:
  966. rval['err'] = verr.args
  967. elif output_type == 'raw':
  968. rval['results'] = stdout if output else ''
  969. if self.verbose:
  970. print("STDOUT: {0}".format(stdout))
  971. print("STDERR: {0}".format(stderr))
  972. if 'err' in rval or returncode != 0:
  973. rval.update({"stderr": stderr,
  974. "stdout": stdout})
  975. return rval
  976. class Utils(object): # pragma: no cover
  977. ''' utilities for openshiftcli modules '''
  978. @staticmethod
  979. def _write(filename, contents):
  980. ''' Actually write the file contents to disk. This helps with mocking. '''
  981. with open(filename, 'w') as sfd:
  982. sfd.write(str(contents))
  983. @staticmethod
  984. def create_tmp_file_from_contents(rname, data, ftype='yaml'):
  985. ''' create a file in tmp with name and contents'''
  986. tmp = Utils.create_tmpfile(prefix=rname)
  987. if ftype == 'yaml':
  988. # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage
  989. # pylint: disable=no-member
  990. if hasattr(yaml, 'RoundTripDumper'):
  991. Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper))
  992. else:
  993. Utils._write(tmp, yaml.safe_dump(data, default_flow_style=False))
  994. elif ftype == 'json':
  995. Utils._write(tmp, json.dumps(data))
  996. else:
  997. Utils._write(tmp, data)
  998. # Register cleanup when module is done
  999. atexit.register(Utils.cleanup, [tmp])
  1000. return tmp
  1001. @staticmethod
  1002. def create_tmpfile_copy(inc_file):
  1003. '''create a temporary copy of a file'''
  1004. tmpfile = Utils.create_tmpfile('lib_openshift-')
  1005. Utils._write(tmpfile, open(inc_file).read())
  1006. # Cleanup the tmpfile
  1007. atexit.register(Utils.cleanup, [tmpfile])
  1008. return tmpfile
  1009. @staticmethod
  1010. def create_tmpfile(prefix='tmp'):
  1011. ''' Generates and returns a temporary file name '''
  1012. with tempfile.NamedTemporaryFile(prefix=prefix, delete=False) as tmp:
  1013. return tmp.name
  1014. @staticmethod
  1015. def create_tmp_files_from_contents(content, content_type=None):
  1016. '''Turn an array of dict: filename, content into a files array'''
  1017. if not isinstance(content, list):
  1018. content = [content]
  1019. files = []
  1020. for item in content:
  1021. path = Utils.create_tmp_file_from_contents(item['path'] + '-',
  1022. item['data'],
  1023. ftype=content_type)
  1024. files.append({'name': os.path.basename(item['path']),
  1025. 'path': path})
  1026. return files
  1027. @staticmethod
  1028. def cleanup(files):
  1029. '''Clean up on exit '''
  1030. for sfile in files:
  1031. if os.path.exists(sfile):
  1032. if os.path.isdir(sfile):
  1033. shutil.rmtree(sfile)
  1034. elif os.path.isfile(sfile):
  1035. os.remove(sfile)
  1036. @staticmethod
  1037. def exists(results, _name):
  1038. ''' Check to see if the results include the name '''
  1039. if not results:
  1040. return False
  1041. if Utils.find_result(results, _name):
  1042. return True
  1043. return False
  1044. @staticmethod
  1045. def find_result(results, _name):
  1046. ''' Find the specified result by name'''
  1047. rval = None
  1048. for result in results:
  1049. if 'metadata' in result and result['metadata']['name'] == _name:
  1050. rval = result
  1051. break
  1052. return rval
  1053. @staticmethod
  1054. def get_resource_file(sfile, sfile_type='yaml'):
  1055. ''' return the service file '''
  1056. contents = None
  1057. with open(sfile) as sfd:
  1058. contents = sfd.read()
  1059. if sfile_type == 'yaml':
  1060. # AUDIT:no-member makes sense here due to ruamel.YAML/PyYAML usage
  1061. # pylint: disable=no-member
  1062. if hasattr(yaml, 'RoundTripLoader'):
  1063. contents = yaml.load(contents, yaml.RoundTripLoader)
  1064. else:
  1065. contents = yaml.safe_load(contents)
  1066. elif sfile_type == 'json':
  1067. contents = json.loads(contents)
  1068. return contents
  1069. @staticmethod
  1070. def filter_versions(stdout):
  1071. ''' filter the oc version output '''
  1072. version_dict = {}
  1073. version_search = ['oc', 'openshift', 'kubernetes']
  1074. for line in stdout.strip().split('\n'):
  1075. for term in version_search:
  1076. if not line:
  1077. continue
  1078. if line.startswith(term):
  1079. version_dict[term] = line.split()[-1]
  1080. # horrible hack to get openshift version in Openshift 3.2
  1081. # By default "oc version in 3.2 does not return an "openshift" version
  1082. if "openshift" not in version_dict:
  1083. version_dict["openshift"] = version_dict["oc"]
  1084. return version_dict
  1085. @staticmethod
  1086. def add_custom_versions(versions):
  1087. ''' create custom versions strings '''
  1088. versions_dict = {}
  1089. for tech, version in versions.items():
  1090. # clean up "-" from version
  1091. if "-" in version:
  1092. version = version.split("-")[0]
  1093. if version.startswith('v'):
  1094. version = version[1:] # Remove the 'v' prefix
  1095. versions_dict[tech + '_numeric'] = version.split('+')[0]
  1096. # "3.3.0.33" is what we have, we want "3.3"
  1097. versions_dict[tech + '_short'] = "{}.{}".format(*version.split('.'))
  1098. return versions_dict
  1099. @staticmethod
  1100. def openshift_installed():
  1101. ''' check if openshift is installed '''
  1102. import rpm
  1103. transaction_set = rpm.TransactionSet()
  1104. rpmquery = transaction_set.dbMatch("name", "atomic-openshift")
  1105. return rpmquery.count() > 0
  1106. # Disabling too-many-branches. This is a yaml dictionary comparison function
  1107. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  1108. @staticmethod
  1109. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  1110. ''' Given a user defined definition, compare it with the results given back by our query. '''
  1111. # Currently these values are autogenerated and we do not need to check them
  1112. skip = ['metadata', 'status']
  1113. if skip_keys:
  1114. skip.extend(skip_keys)
  1115. for key, value in result_def.items():
  1116. if key in skip:
  1117. continue
  1118. # Both are lists
  1119. if isinstance(value, list):
  1120. if key not in user_def:
  1121. if debug:
  1122. print('User data does not have key [%s]' % key)
  1123. print('User data: %s' % user_def)
  1124. return False
  1125. if not isinstance(user_def[key], list):
  1126. if debug:
  1127. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  1128. return False
  1129. if len(user_def[key]) != len(value):
  1130. if debug:
  1131. print("List lengths are not equal.")
  1132. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  1133. print("user_def: %s" % user_def[key])
  1134. print("value: %s" % value)
  1135. return False
  1136. for values in zip(user_def[key], value):
  1137. if isinstance(values[0], dict) and isinstance(values[1], dict):
  1138. if debug:
  1139. print('sending list - list')
  1140. print(type(values[0]))
  1141. print(type(values[1]))
  1142. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  1143. if not result:
  1144. print('list compare returned false')
  1145. return False
  1146. elif value != user_def[key]:
  1147. if debug:
  1148. print('value should be identical')
  1149. print(user_def[key])
  1150. print(value)
  1151. return False
  1152. # recurse on a dictionary
  1153. elif isinstance(value, dict):
  1154. if key not in user_def:
  1155. if debug:
  1156. print("user_def does not have key [%s]" % key)
  1157. return False
  1158. if not isinstance(user_def[key], dict):
  1159. if debug:
  1160. print("dict returned false: not instance of dict")
  1161. return False
  1162. # before passing ensure keys match
  1163. api_values = set(value.keys()) - set(skip)
  1164. user_values = set(user_def[key].keys()) - set(skip)
  1165. if api_values != user_values:
  1166. if debug:
  1167. print("keys are not equal in dict")
  1168. print(user_values)
  1169. print(api_values)
  1170. return False
  1171. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  1172. if not result:
  1173. if debug:
  1174. print("dict returned false")
  1175. print(result)
  1176. return False
  1177. # Verify each key, value pair is the same
  1178. else:
  1179. if key not in user_def or value != user_def[key]:
  1180. if debug:
  1181. print("value not equal; user_def does not have key")
  1182. print(key)
  1183. print(value)
  1184. if key in user_def:
  1185. print(user_def[key])
  1186. return False
  1187. if debug:
  1188. print('returning true')
  1189. return True
  1190. class OpenShiftCLIConfig(object):
  1191. '''Generic Config'''
  1192. def __init__(self, rname, namespace, kubeconfig, options):
  1193. self.kubeconfig = kubeconfig
  1194. self.name = rname
  1195. self.namespace = namespace
  1196. self._options = options
  1197. @property
  1198. def config_options(self):
  1199. ''' return config options '''
  1200. return self._options
  1201. def to_option_list(self, ascommalist=''):
  1202. '''return all options as a string
  1203. if ascommalist is set to the name of a key, and
  1204. the value of that key is a dict, format the dict
  1205. as a list of comma delimited key=value pairs'''
  1206. return self.stringify(ascommalist)
  1207. def stringify(self, ascommalist=''):
  1208. ''' return the options hash as cli params in a string
  1209. if ascommalist is set to the name of a key, and
  1210. the value of that key is a dict, format the dict
  1211. as a list of comma delimited key=value pairs '''
  1212. rval = []
  1213. for key in sorted(self.config_options.keys()):
  1214. data = self.config_options[key]
  1215. if data['include'] \
  1216. and (data['value'] is not None or isinstance(data['value'], int)):
  1217. if key == ascommalist:
  1218. val = ','.join(['{}={}'.format(kk, vv) for kk, vv in sorted(data['value'].items())])
  1219. else:
  1220. val = data['value']
  1221. rval.append('--{}={}'.format(key.replace('_', '-'), val))
  1222. return rval
  1223. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1224. # -*- -*- -*- Begin included fragment: lib/deploymentconfig.py -*- -*- -*-
  1225. # pylint: disable=too-many-public-methods
  1226. class DeploymentConfig(Yedit):
  1227. ''' Class to model an openshift DeploymentConfig'''
  1228. default_deployment_config = '''
  1229. apiVersion: v1
  1230. kind: DeploymentConfig
  1231. metadata:
  1232. name: default_dc
  1233. namespace: default
  1234. spec:
  1235. replicas: 0
  1236. selector:
  1237. default_dc: default_dc
  1238. strategy:
  1239. resources: {}
  1240. rollingParams:
  1241. intervalSeconds: 1
  1242. maxSurge: 0
  1243. maxUnavailable: 25%
  1244. timeoutSeconds: 600
  1245. updatePercent: -25
  1246. updatePeriodSeconds: 1
  1247. type: Rolling
  1248. template:
  1249. metadata:
  1250. spec:
  1251. containers:
  1252. - env:
  1253. - name: default
  1254. value: default
  1255. image: default
  1256. imagePullPolicy: IfNotPresent
  1257. name: default_dc
  1258. ports:
  1259. - containerPort: 8000
  1260. hostPort: 8000
  1261. protocol: TCP
  1262. name: default_port
  1263. resources: {}
  1264. terminationMessagePath: /dev/termination-log
  1265. dnsPolicy: ClusterFirst
  1266. hostNetwork: true
  1267. nodeSelector:
  1268. type: compute
  1269. restartPolicy: Always
  1270. securityContext: {}
  1271. serviceAccount: default
  1272. serviceAccountName: default
  1273. terminationGracePeriodSeconds: 30
  1274. triggers:
  1275. - type: ConfigChange
  1276. '''
  1277. replicas_path = "spec.replicas"
  1278. env_path = "spec.template.spec.containers[0].env"
  1279. volumes_path = "spec.template.spec.volumes"
  1280. container_path = "spec.template.spec.containers"
  1281. volume_mounts_path = "spec.template.spec.containers[0].volumeMounts"
  1282. def __init__(self, content=None):
  1283. ''' Constructor for deploymentconfig '''
  1284. if not content:
  1285. content = DeploymentConfig.default_deployment_config
  1286. super(DeploymentConfig, self).__init__(content=content)
  1287. def add_env_value(self, key, value):
  1288. ''' add key, value pair to env array '''
  1289. rval = False
  1290. env = self.get_env_vars()
  1291. if env:
  1292. env.append({'name': key, 'value': value})
  1293. rval = True
  1294. else:
  1295. result = self.put(DeploymentConfig.env_path, {'name': key, 'value': value})
  1296. rval = result[0]
  1297. return rval
  1298. def exists_env_value(self, key, value):
  1299. ''' return whether a key, value pair exists '''
  1300. results = self.get_env_vars()
  1301. if not results:
  1302. return False
  1303. for result in results:
  1304. if result['name'] == key and result['value'] == value:
  1305. return True
  1306. return False
  1307. def exists_env_key(self, key):
  1308. ''' return whether a key, value pair exists '''
  1309. results = self.get_env_vars()
  1310. if not results:
  1311. return False
  1312. for result in results:
  1313. if result['name'] == key:
  1314. return True
  1315. return False
  1316. def get_env_var(self, key):
  1317. '''return a environment variables '''
  1318. results = self.get(DeploymentConfig.env_path) or []
  1319. if not results:
  1320. return None
  1321. for env_var in results:
  1322. if env_var['name'] == key:
  1323. return env_var
  1324. return None
  1325. def get_env_vars(self):
  1326. '''return a environment variables '''
  1327. return self.get(DeploymentConfig.env_path) or []
  1328. def delete_env_var(self, keys):
  1329. '''delete a list of keys '''
  1330. if not isinstance(keys, list):
  1331. keys = [keys]
  1332. env_vars_array = self.get_env_vars()
  1333. modified = False
  1334. idx = None
  1335. for key in keys:
  1336. for env_idx, env_var in enumerate(env_vars_array):
  1337. if env_var['name'] == key:
  1338. idx = env_idx
  1339. break
  1340. if idx:
  1341. modified = True
  1342. del env_vars_array[idx]
  1343. if modified:
  1344. return True
  1345. return False
  1346. def update_env_var(self, key, value):
  1347. '''place an env in the env var list'''
  1348. env_vars_array = self.get_env_vars()
  1349. idx = None
  1350. for env_idx, env_var in enumerate(env_vars_array):
  1351. if env_var['name'] == key:
  1352. idx = env_idx
  1353. break
  1354. if idx:
  1355. env_vars_array[idx]['value'] = value
  1356. else:
  1357. self.add_env_value(key, value)
  1358. return True
  1359. def exists_volume_mount(self, volume_mount):
  1360. ''' return whether a volume mount exists '''
  1361. exist_volume_mounts = self.get_volume_mounts()
  1362. if not exist_volume_mounts:
  1363. return False
  1364. volume_mount_found = False
  1365. for exist_volume_mount in exist_volume_mounts:
  1366. if exist_volume_mount['name'] == volume_mount['name']:
  1367. volume_mount_found = True
  1368. break
  1369. return volume_mount_found
  1370. def exists_volume(self, volume):
  1371. ''' return whether a volume exists '''
  1372. exist_volumes = self.get_volumes()
  1373. volume_found = False
  1374. for exist_volume in exist_volumes:
  1375. if exist_volume['name'] == volume['name']:
  1376. volume_found = True
  1377. break
  1378. return volume_found
  1379. def find_volume_by_name(self, volume, mounts=False):
  1380. ''' return the index of a volume '''
  1381. volumes = []
  1382. if mounts:
  1383. volumes = self.get_volume_mounts()
  1384. else:
  1385. volumes = self.get_volumes()
  1386. for exist_volume in volumes:
  1387. if exist_volume['name'] == volume['name']:
  1388. return exist_volume
  1389. return None
  1390. def get_replicas(self):
  1391. ''' return replicas setting '''
  1392. return self.get(DeploymentConfig.replicas_path)
  1393. def get_volume_mounts(self):
  1394. '''return volume mount information '''
  1395. return self.get_volumes(mounts=True)
  1396. def get_volumes(self, mounts=False):
  1397. '''return volume mount information '''
  1398. if mounts:
  1399. return self.get(DeploymentConfig.volume_mounts_path) or []
  1400. return self.get(DeploymentConfig.volumes_path) or []
  1401. def delete_volume_by_name(self, volume):
  1402. '''delete a volume '''
  1403. modified = False
  1404. exist_volume_mounts = self.get_volume_mounts()
  1405. exist_volumes = self.get_volumes()
  1406. del_idx = None
  1407. for idx, exist_volume in enumerate(exist_volumes):
  1408. if 'name' in exist_volume and exist_volume['name'] == volume['name']:
  1409. del_idx = idx
  1410. break
  1411. if del_idx != None:
  1412. del exist_volumes[del_idx]
  1413. modified = True
  1414. del_idx = None
  1415. for idx, exist_volume_mount in enumerate(exist_volume_mounts):
  1416. if 'name' in exist_volume_mount and exist_volume_mount['name'] == volume['name']:
  1417. del_idx = idx
  1418. break
  1419. if del_idx != None:
  1420. del exist_volume_mounts[idx]
  1421. modified = True
  1422. return modified
  1423. def add_volume_mount(self, volume_mount):
  1424. ''' add a volume or volume mount to the proper location '''
  1425. exist_volume_mounts = self.get_volume_mounts()
  1426. if not exist_volume_mounts and volume_mount:
  1427. self.put(DeploymentConfig.volume_mounts_path, [volume_mount])
  1428. else:
  1429. exist_volume_mounts.append(volume_mount)
  1430. def add_volume(self, volume):
  1431. ''' add a volume or volume mount to the proper location '''
  1432. exist_volumes = self.get_volumes()
  1433. if not volume:
  1434. return
  1435. if not exist_volumes:
  1436. self.put(DeploymentConfig.volumes_path, [volume])
  1437. else:
  1438. exist_volumes.append(volume)
  1439. def update_replicas(self, replicas):
  1440. ''' update replicas value '''
  1441. self.put(DeploymentConfig.replicas_path, replicas)
  1442. def update_volume(self, volume):
  1443. '''place an env in the env var list'''
  1444. exist_volumes = self.get_volumes()
  1445. if not volume:
  1446. return False
  1447. # update the volume
  1448. update_idx = None
  1449. for idx, exist_vol in enumerate(exist_volumes):
  1450. if exist_vol['name'] == volume['name']:
  1451. update_idx = idx
  1452. break
  1453. if update_idx != None:
  1454. exist_volumes[update_idx] = volume
  1455. else:
  1456. self.add_volume(volume)
  1457. return True
  1458. def update_volume_mount(self, volume_mount):
  1459. '''place an env in the env var list'''
  1460. modified = False
  1461. exist_volume_mounts = self.get_volume_mounts()
  1462. if not volume_mount:
  1463. return False
  1464. # update the volume mount
  1465. for exist_vol_mount in exist_volume_mounts:
  1466. if exist_vol_mount['name'] == volume_mount['name']:
  1467. if 'mountPath' in exist_vol_mount and \
  1468. str(exist_vol_mount['mountPath']) != str(volume_mount['mountPath']):
  1469. exist_vol_mount['mountPath'] = volume_mount['mountPath']
  1470. modified = True
  1471. break
  1472. if not modified:
  1473. self.add_volume_mount(volume_mount)
  1474. modified = True
  1475. return modified
  1476. def needs_update_volume(self, volume, volume_mount):
  1477. ''' verify a volume update is needed '''
  1478. exist_volume = self.find_volume_by_name(volume)
  1479. exist_volume_mount = self.find_volume_by_name(volume, mounts=True)
  1480. results = []
  1481. results.append(exist_volume['name'] == volume['name'])
  1482. if 'secret' in volume:
  1483. results.append('secret' in exist_volume)
  1484. results.append(exist_volume['secret']['secretName'] == volume['secret']['secretName'])
  1485. results.append(exist_volume_mount['name'] == volume_mount['name'])
  1486. results.append(exist_volume_mount['mountPath'] == volume_mount['mountPath'])
  1487. elif 'emptyDir' in volume:
  1488. results.append(exist_volume_mount['name'] == volume['name'])
  1489. results.append(exist_volume_mount['mountPath'] == volume_mount['mountPath'])
  1490. elif 'persistentVolumeClaim' in volume:
  1491. pvc = 'persistentVolumeClaim'
  1492. results.append(pvc in exist_volume)
  1493. if results[-1]:
  1494. results.append(exist_volume[pvc]['claimName'] == volume[pvc]['claimName'])
  1495. if 'claimSize' in volume[pvc]:
  1496. results.append(exist_volume[pvc]['claimSize'] == volume[pvc]['claimSize'])
  1497. elif 'hostpath' in volume:
  1498. results.append('hostPath' in exist_volume)
  1499. results.append(exist_volume['hostPath']['path'] == volume_mount['mountPath'])
  1500. return not all(results)
  1501. def needs_update_replicas(self, replicas):
  1502. ''' verify whether a replica update is needed '''
  1503. current_reps = self.get(DeploymentConfig.replicas_path)
  1504. return not current_reps == replicas
  1505. # -*- -*- -*- End included fragment: lib/deploymentconfig.py -*- -*- -*-
  1506. # -*- -*- -*- Begin included fragment: lib/volume.py -*- -*- -*-
  1507. class Volume(object):
  1508. ''' Class to represent an openshift volume object'''
  1509. volume_mounts_path = {"pod": "spec.containers[0].volumeMounts",
  1510. "dc": "spec.template.spec.containers[0].volumeMounts",
  1511. "rc": "spec.template.spec.containers[0].volumeMounts",
  1512. }
  1513. volumes_path = {"pod": "spec.volumes",
  1514. "dc": "spec.template.spec.volumes",
  1515. "rc": "spec.template.spec.volumes",
  1516. }
  1517. @staticmethod
  1518. def create_volume_structure(volume_info):
  1519. ''' return a properly structured volume '''
  1520. volume_mount = None
  1521. volume = {'name': volume_info['name']}
  1522. volume_type = volume_info['type'].lower()
  1523. if volume_type == 'secret':
  1524. volume['secret'] = {}
  1525. volume[volume_info['type']] = {'secretName': volume_info['secret_name']}
  1526. volume_mount = {'mountPath': volume_info['path'],
  1527. 'name': volume_info['name']}
  1528. elif volume_type == 'emptydir':
  1529. volume['emptyDir'] = {}
  1530. volume_mount = {'mountPath': volume_info['path'],
  1531. 'name': volume_info['name']}
  1532. elif volume_type == 'pvc' or volume_type == 'persistentvolumeclaim':
  1533. volume['persistentVolumeClaim'] = {}
  1534. volume['persistentVolumeClaim']['claimName'] = volume_info['claimName']
  1535. volume['persistentVolumeClaim']['claimSize'] = volume_info['claimSize']
  1536. elif volume_type == 'hostpath':
  1537. volume['hostPath'] = {}
  1538. volume['hostPath']['path'] = volume_info['path']
  1539. elif volume_type == 'configmap':
  1540. volume['configMap'] = {}
  1541. volume['configMap']['name'] = volume_info['configmap_name']
  1542. volume_mount = {'mountPath': volume_info['path'],
  1543. 'name': volume_info['name']}
  1544. return (volume, volume_mount)
  1545. # -*- -*- -*- End included fragment: lib/volume.py -*- -*- -*-
  1546. # -*- -*- -*- Begin included fragment: class/oc_volume.py -*- -*- -*-
  1547. # pylint: disable=too-many-instance-attributes
  1548. class OCVolume(OpenShiftCLI):
  1549. ''' Class to wrap the oc command line tools '''
  1550. volume_mounts_path = {"pod": "spec.containers[0].volumeMounts",
  1551. "dc": "spec.template.spec.containers[0].volumeMounts",
  1552. "rc": "spec.template.spec.containers[0].volumeMounts",
  1553. }
  1554. volumes_path = {"pod": "spec.volumes",
  1555. "dc": "spec.template.spec.volumes",
  1556. "rc": "spec.template.spec.volumes",
  1557. }
  1558. # pylint allows 5
  1559. # pylint: disable=too-many-arguments
  1560. def __init__(self,
  1561. kind,
  1562. resource_name,
  1563. namespace,
  1564. vol_name,
  1565. mount_path,
  1566. mount_type,
  1567. secret_name,
  1568. claim_size,
  1569. claim_name,
  1570. configmap_name,
  1571. kubeconfig='/etc/origin/master/admin.kubeconfig',
  1572. verbose=False):
  1573. ''' Constructor for OCVolume '''
  1574. super(OCVolume, self).__init__(namespace, kubeconfig)
  1575. self.kind = kind
  1576. self.volume_info = {'name': vol_name,
  1577. 'secret_name': secret_name,
  1578. 'path': mount_path,
  1579. 'type': mount_type,
  1580. 'claimSize': claim_size,
  1581. 'claimName': claim_name,
  1582. 'configmap_name': configmap_name}
  1583. self.volume, self.volume_mount = Volume.create_volume_structure(self.volume_info)
  1584. self.name = resource_name
  1585. self.namespace = namespace
  1586. self.kubeconfig = kubeconfig
  1587. self.verbose = verbose
  1588. self._resource = None
  1589. @property
  1590. def resource(self):
  1591. ''' property function for resource var '''
  1592. if not self._resource:
  1593. self.get()
  1594. return self._resource
  1595. @resource.setter
  1596. def resource(self, data):
  1597. ''' setter function for resource var '''
  1598. self._resource = data
  1599. def exists(self):
  1600. ''' return whether a volume exists '''
  1601. volume_mount_found = False
  1602. volume_found = self.resource.exists_volume(self.volume)
  1603. if not self.volume_mount and volume_found:
  1604. return True
  1605. if self.volume_mount:
  1606. volume_mount_found = self.resource.exists_volume_mount(self.volume_mount)
  1607. if volume_found and self.volume_mount and volume_mount_found:
  1608. return True
  1609. return False
  1610. def get(self):
  1611. '''return volume information '''
  1612. vol = self._get(self.kind, self.name)
  1613. if vol['returncode'] == 0:
  1614. if self.kind == 'dc':
  1615. self.resource = DeploymentConfig(content=vol['results'][0])
  1616. vol['results'] = self.resource.get_volumes()
  1617. return vol
  1618. def delete(self):
  1619. '''remove a volume'''
  1620. self.resource.delete_volume_by_name(self.volume)
  1621. return self._replace_content(self.kind, self.name, self.resource.yaml_dict)
  1622. def put(self):
  1623. '''place volume into dc '''
  1624. self.resource.update_volume(self.volume)
  1625. self.resource.get_volumes()
  1626. self.resource.update_volume_mount(self.volume_mount)
  1627. return self._replace_content(self.kind, self.name, self.resource.yaml_dict)
  1628. def needs_update(self):
  1629. ''' verify an update is needed '''
  1630. return self.resource.needs_update_volume(self.volume, self.volume_mount)
  1631. # pylint: disable=too-many-branches,too-many-return-statements
  1632. @staticmethod
  1633. def run_ansible(params, check_mode=False):
  1634. '''run the oc_volume module'''
  1635. oc_volume = OCVolume(params['kind'],
  1636. params['name'],
  1637. params['namespace'],
  1638. params['vol_name'],
  1639. params['mount_path'],
  1640. params['mount_type'],
  1641. # secrets
  1642. params['secret_name'],
  1643. # pvc
  1644. params['claim_size'],
  1645. params['claim_name'],
  1646. # configmap
  1647. params['configmap_name'],
  1648. kubeconfig=params['kubeconfig'],
  1649. verbose=params['debug'])
  1650. state = params['state']
  1651. api_rval = oc_volume.get()
  1652. if api_rval['returncode'] != 0:
  1653. return {'failed': True, 'msg': api_rval}
  1654. #####
  1655. # Get
  1656. #####
  1657. if state == 'list':
  1658. return {'changed': False, 'results': api_rval['results'], 'state': state}
  1659. ########
  1660. # Delete
  1661. ########
  1662. if state == 'absent':
  1663. if oc_volume.exists():
  1664. if check_mode:
  1665. return {'changed': False, 'msg': 'CHECK_MODE: Would have performed a delete.'}
  1666. api_rval = oc_volume.delete()
  1667. if api_rval['returncode'] != 0:
  1668. return {'failed': True, 'msg': api_rval}
  1669. return {'changed': True, 'results': api_rval, 'state': state}
  1670. return {'changed': False, 'state': state}
  1671. if state == 'present':
  1672. ########
  1673. # Create
  1674. ########
  1675. if not oc_volume.exists():
  1676. if check_mode:
  1677. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
  1678. # Create it here
  1679. api_rval = oc_volume.put()
  1680. if api_rval['returncode'] != 0:
  1681. return {'failed': True, 'msg': api_rval}
  1682. # return the created object
  1683. api_rval = oc_volume.get()
  1684. if api_rval['returncode'] != 0:
  1685. return {'failed': True, 'msg': api_rval}
  1686. return {'changed': True, 'results': api_rval, 'state': state}
  1687. ########
  1688. # Update
  1689. ########
  1690. if oc_volume.needs_update():
  1691. api_rval = oc_volume.put()
  1692. if api_rval['returncode'] != 0:
  1693. return {'failed': True, 'msg': api_rval}
  1694. # return the created object
  1695. api_rval = oc_volume.get()
  1696. if api_rval['returncode'] != 0:
  1697. return {'failed': True, 'msg': api_rval}
  1698. return {'changed': True, 'results': api_rval, state: state}
  1699. return {'changed': False, 'results': api_rval, state: state}
  1700. return {'failed': True, 'msg': 'Unknown state passed. {}'.format(state)}
  1701. # -*- -*- -*- End included fragment: class/oc_volume.py -*- -*- -*-
  1702. # -*- -*- -*- Begin included fragment: ansible/oc_volume.py -*- -*- -*-
  1703. def main():
  1704. '''
  1705. ansible oc module for volumes
  1706. '''
  1707. module = AnsibleModule(
  1708. argument_spec=dict(
  1709. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  1710. state=dict(default='present', type='str',
  1711. choices=['present', 'absent', 'list']),
  1712. debug=dict(default=False, type='bool'),
  1713. kind=dict(default='dc', choices=['dc', 'rc', 'pods'], type='str'),
  1714. namespace=dict(default='default', type='str'),
  1715. vol_name=dict(default=None, type='str'),
  1716. name=dict(default=None, type='str'),
  1717. mount_type=dict(default=None,
  1718. choices=['emptydir', 'hostpath', 'secret', 'pvc', 'configmap'],
  1719. type='str'),
  1720. mount_path=dict(default=None, type='str'),
  1721. # secrets require a name
  1722. secret_name=dict(default=None, type='str'),
  1723. # pvc requires a size
  1724. claim_size=dict(default=None, type='str'),
  1725. claim_name=dict(default=None, type='str'),
  1726. # configmap requires a name
  1727. configmap_name=dict(default=None, type='str'),
  1728. ),
  1729. supports_check_mode=True,
  1730. )
  1731. rval = OCVolume.run_ansible(module.params, module.check_mode)
  1732. if 'failed' in rval:
  1733. module.fail_json(**rval)
  1734. module.exit_json(**rval)
  1735. if __name__ == '__main__':
  1736. main()
  1737. # -*- -*- -*- End included fragment: ansible/oc_volume.py -*- -*- -*-