oc_volume.py 71 KB

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