oadm_router.py 95 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905
  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 json
  36. import os
  37. import re
  38. import shutil
  39. import subprocess
  40. import tempfile
  41. # pylint: disable=import-error
  42. try:
  43. import ruamel.yaml as yaml
  44. except ImportError:
  45. import yaml
  46. from ansible.module_utils.basic import AnsibleModule
  47. # -*- -*- -*- End included fragment: lib/import.py -*- -*- -*-
  48. # -*- -*- -*- Begin included fragment: doc/router -*- -*- -*-
  49. DOCUMENTATION = '''
  50. ---
  51. module: oadm_router
  52. short_description: Module to manage openshift router
  53. description:
  54. - Manage openshift router programmatically.
  55. options:
  56. state:
  57. description:
  58. - Whether to create or delete the router
  59. - present - create the router
  60. - absent - remove the router
  61. required: false
  62. default: present
  63. choices:
  64. - present
  65. - absent
  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. - The name of the router
  82. required: false
  83. default: router
  84. aliases: []
  85. namespace:
  86. description:
  87. - The namespace where to manage the router.
  88. required: false
  89. default: default
  90. aliases: []
  91. credentials:
  92. description:
  93. - Path to a .kubeconfig file that will contain the credentials the registry should use to contact the master.
  94. required: false
  95. default: None
  96. aliases: []
  97. images:
  98. description:
  99. - The image to base this router on - ${component} will be replaced with --type
  100. required: 'openshift3/ose-${component}:${version}'
  101. default: None
  102. aliases: []
  103. latest_images:
  104. description:
  105. - If true, attempt to use the latest image for the registry instead of the latest release.
  106. required: false
  107. default: False
  108. aliases: []
  109. labels:
  110. description:
  111. - A set of labels to uniquely identify the registry and its components.
  112. required: false
  113. default: None
  114. aliases: []
  115. ports:
  116. description:
  117. - A list of strings in the 'port:port' format
  118. required: False
  119. default:
  120. - 80:80
  121. - 443:443
  122. aliases: []
  123. replicas:
  124. description:
  125. - The replication factor of the registry; commonly 2 when high availability is desired.
  126. required: False
  127. default: 1
  128. aliases: []
  129. selector:
  130. description:
  131. - Selector used to filter nodes on deployment. Used to run routers on a specific set of nodes.
  132. required: False
  133. default: None
  134. aliases: []
  135. service_account:
  136. description:
  137. - Name of the service account to use to run the router pod.
  138. required: False
  139. default: router
  140. aliases: []
  141. router_type:
  142. description:
  143. - The router image to use - if you specify --images this flag may be ignored.
  144. required: false
  145. default: haproxy-router
  146. aliases: []
  147. external_host:
  148. description:
  149. - If the underlying router implementation connects with an external host, this is the external host's hostname.
  150. required: false
  151. default: None
  152. aliases: []
  153. external_host_vserver:
  154. description:
  155. - If the underlying router implementation uses virtual servers, this is the name of the virtual server for HTTP connections.
  156. required: false
  157. default: None
  158. aliases: []
  159. external_host_insecure:
  160. description:
  161. - If the underlying router implementation connects with an external host
  162. - over a secure connection, this causes the router to skip strict certificate verification with the external host.
  163. required: false
  164. default: False
  165. aliases: []
  166. external_host_partition_path:
  167. description:
  168. - If the underlying router implementation uses partitions for control boundaries, this is the path to use for that partition.
  169. required: false
  170. default: None
  171. aliases: []
  172. external_host_username:
  173. description:
  174. - If the underlying router implementation connects with an external host, this is the username for authenticating with the external host.
  175. required: false
  176. default: None
  177. aliases: []
  178. external_host_password:
  179. description:
  180. - If the underlying router implementation connects with an external host, this is the password for authenticating with the external host.
  181. required: false
  182. default: None
  183. aliases: []
  184. external_host_private_key:
  185. description:
  186. - If the underlying router implementation requires an SSH private key, this is the path to the private key file.
  187. required: false
  188. default: None
  189. aliases: []
  190. expose_metrics:
  191. description:
  192. - This is a hint to run an extra container in the pod to expose metrics - the image
  193. - will either be set depending on the router implementation or provided with --metrics-image.
  194. required: false
  195. default: False
  196. aliases: []
  197. metrics_image:
  198. description:
  199. - If expose_metrics is specified this is the image to use to run a sidecar container
  200. - in the pod exposing metrics. If not set and --expose-metrics is true the image will
  201. - depend on router implementation.
  202. required: false
  203. default: None
  204. aliases: []
  205. author:
  206. - "Kenny Woodson <kwoodson@redhat.com>"
  207. extends_documentation_fragment: []
  208. '''
  209. EXAMPLES = '''
  210. - name: create routers
  211. oadm_router:
  212. name: router
  213. service_account: router
  214. replicas: 2
  215. namespace: default
  216. selector: type=infra
  217. cert_file: /etc/origin/master/named_certificates/router.crt
  218. key_file: /etc/origin/master/named_certificates/router.key
  219. cacert_file: /etc/origin/master/named_certificates/router.ca
  220. edits:
  221. - key: spec.strategy.rollingParams
  222. value:
  223. intervalSeconds: 1
  224. maxSurge: 50%
  225. maxUnavailable: 50%
  226. timeoutSeconds: 600
  227. updatePeriodSeconds: 1
  228. action: put
  229. - key: spec.template.spec.containers[0].resources.limits.memory
  230. value: 2G
  231. action: update
  232. - key: spec.template.spec.containers[0].resources.requests.memory
  233. value: 1G
  234. action: update
  235. - key: spec.template.spec.containers[0].env
  236. value:
  237. name: EXTENDED_VALIDATION
  238. value: 'false'
  239. action: update
  240. register: router_out
  241. run_once: True
  242. '''
  243. # -*- -*- -*- End included fragment: doc/router -*- -*- -*-
  244. # -*- -*- -*- Begin included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  245. # noqa: E301,E302
  246. class YeditException(Exception):
  247. ''' Exception class for Yedit '''
  248. pass
  249. # pylint: disable=too-many-public-methods
  250. class Yedit(object):
  251. ''' Class to modify yaml files '''
  252. re_valid_key = r"(((\[-?\d+\])|([0-9a-zA-Z%s/_-]+)).?)+$"
  253. re_key = r"(?:\[(-?\d+)\])|([0-9a-zA-Z%s/_-]+)"
  254. com_sep = set(['.', '#', '|', ':'])
  255. # pylint: disable=too-many-arguments
  256. def __init__(self,
  257. filename=None,
  258. content=None,
  259. content_type='yaml',
  260. separator='.',
  261. backup=False):
  262. self.content = content
  263. self._separator = separator
  264. self.filename = filename
  265. self.__yaml_dict = content
  266. self.content_type = content_type
  267. self.backup = backup
  268. self.load(content_type=self.content_type)
  269. if self.__yaml_dict is None:
  270. self.__yaml_dict = {}
  271. @property
  272. def separator(self):
  273. ''' getter method for yaml_dict '''
  274. return self._separator
  275. @separator.setter
  276. def separator(self):
  277. ''' getter method for yaml_dict '''
  278. return self._separator
  279. @property
  280. def yaml_dict(self):
  281. ''' getter method for yaml_dict '''
  282. return self.__yaml_dict
  283. @yaml_dict.setter
  284. def yaml_dict(self, value):
  285. ''' setter method for yaml_dict '''
  286. self.__yaml_dict = value
  287. @staticmethod
  288. def parse_key(key, sep='.'):
  289. '''parse the key allowing the appropriate separator'''
  290. common_separators = list(Yedit.com_sep - set([sep]))
  291. return re.findall(Yedit.re_key % ''.join(common_separators), key)
  292. @staticmethod
  293. def valid_key(key, sep='.'):
  294. '''validate the incoming key'''
  295. common_separators = list(Yedit.com_sep - set([sep]))
  296. if not re.match(Yedit.re_valid_key % ''.join(common_separators), key):
  297. return False
  298. return True
  299. @staticmethod
  300. def remove_entry(data, key, sep='.'):
  301. ''' remove data at location key '''
  302. if key == '' and isinstance(data, dict):
  303. data.clear()
  304. return True
  305. elif key == '' and isinstance(data, list):
  306. del data[:]
  307. return True
  308. if not (key and Yedit.valid_key(key, sep)) and \
  309. isinstance(data, (list, dict)):
  310. return None
  311. key_indexes = Yedit.parse_key(key, sep)
  312. for arr_ind, dict_key in key_indexes[:-1]:
  313. if dict_key and isinstance(data, dict):
  314. data = data.get(dict_key, None)
  315. elif (arr_ind and isinstance(data, list) and
  316. int(arr_ind) <= len(data) - 1):
  317. data = data[int(arr_ind)]
  318. else:
  319. return None
  320. # process last index for remove
  321. # expected list entry
  322. if key_indexes[-1][0]:
  323. if isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  324. del data[int(key_indexes[-1][0])]
  325. return True
  326. # expected dict entry
  327. elif key_indexes[-1][1]:
  328. if isinstance(data, dict):
  329. del data[key_indexes[-1][1]]
  330. return True
  331. @staticmethod
  332. def add_entry(data, key, item=None, sep='.'):
  333. ''' Get an item from a dictionary with key notation a.b.c
  334. d = {'a': {'b': 'c'}}}
  335. key = a#b
  336. return c
  337. '''
  338. if key == '':
  339. pass
  340. elif (not (key and Yedit.valid_key(key, sep)) and
  341. isinstance(data, (list, dict))):
  342. return None
  343. key_indexes = Yedit.parse_key(key, sep)
  344. for arr_ind, dict_key in key_indexes[:-1]:
  345. if dict_key:
  346. if isinstance(data, dict) and dict_key in data and data[dict_key]: # noqa: E501
  347. data = data[dict_key]
  348. continue
  349. elif data and not isinstance(data, dict):
  350. return None
  351. data[dict_key] = {}
  352. data = data[dict_key]
  353. elif (arr_ind and isinstance(data, list) and
  354. int(arr_ind) <= len(data) - 1):
  355. data = data[int(arr_ind)]
  356. else:
  357. return None
  358. if key == '':
  359. data = item
  360. # process last index for add
  361. # expected list entry
  362. elif key_indexes[-1][0] and isinstance(data, list) and int(key_indexes[-1][0]) <= len(data) - 1: # noqa: E501
  363. data[int(key_indexes[-1][0])] = item
  364. # expected dict entry
  365. elif key_indexes[-1][1] and isinstance(data, dict):
  366. data[key_indexes[-1][1]] = item
  367. return data
  368. @staticmethod
  369. def get_entry(data, key, sep='.'):
  370. ''' Get an item from a dictionary with key notation a.b.c
  371. d = {'a': {'b': 'c'}}}
  372. key = a.b
  373. return c
  374. '''
  375. if key == '':
  376. pass
  377. elif (not (key and Yedit.valid_key(key, sep)) and
  378. isinstance(data, (list, dict))):
  379. return None
  380. key_indexes = Yedit.parse_key(key, sep)
  381. for arr_ind, dict_key in key_indexes:
  382. if dict_key and isinstance(data, dict):
  383. data = data.get(dict_key, None)
  384. elif (arr_ind and isinstance(data, list) and
  385. int(arr_ind) <= len(data) - 1):
  386. data = data[int(arr_ind)]
  387. else:
  388. return None
  389. return data
  390. @staticmethod
  391. def _write(filename, contents):
  392. ''' Actually write the file contents to disk. This helps with mocking. '''
  393. tmp_filename = filename + '.yedit'
  394. with open(tmp_filename, 'w') as yfd:
  395. yfd.write(contents)
  396. os.rename(tmp_filename, filename)
  397. def write(self):
  398. ''' write to file '''
  399. if not self.filename:
  400. raise YeditException('Please specify a filename.')
  401. if self.backup and self.file_exists():
  402. shutil.copy(self.filename, self.filename + '.orig')
  403. if hasattr(yaml, 'RoundTripDumper'):
  404. # pylint: disable=no-member
  405. if hasattr(self.yaml_dict, 'fa'):
  406. self.yaml_dict.fa.set_block_style()
  407. # pylint: disable=no-member
  408. Yedit._write(self.filename, yaml.dump(self.yaml_dict, Dumper=yaml.RoundTripDumper))
  409. else:
  410. Yedit._write(self.filename, yaml.safe_dump(self.yaml_dict, default_flow_style=False))
  411. return (True, self.yaml_dict)
  412. def read(self):
  413. ''' read from file '''
  414. # check if it exists
  415. if self.filename is None or not self.file_exists():
  416. return None
  417. contents = None
  418. with open(self.filename) as yfd:
  419. contents = yfd.read()
  420. return contents
  421. def file_exists(self):
  422. ''' return whether file exists '''
  423. if os.path.exists(self.filename):
  424. return True
  425. return False
  426. def load(self, content_type='yaml'):
  427. ''' return yaml file '''
  428. contents = self.read()
  429. if not contents and not self.content:
  430. return None
  431. if self.content:
  432. if isinstance(self.content, dict):
  433. self.yaml_dict = self.content
  434. return self.yaml_dict
  435. elif isinstance(self.content, str):
  436. contents = self.content
  437. # check if it is yaml
  438. try:
  439. if content_type == 'yaml' and contents:
  440. # pylint: disable=no-member
  441. if hasattr(yaml, 'RoundTripLoader'):
  442. self.yaml_dict = yaml.load(contents, yaml.RoundTripLoader)
  443. else:
  444. self.yaml_dict = yaml.safe_load(contents)
  445. # pylint: disable=no-member
  446. if hasattr(self.yaml_dict, 'fa'):
  447. self.yaml_dict.fa.set_block_style()
  448. elif content_type == 'json' and contents:
  449. self.yaml_dict = json.loads(contents)
  450. except yaml.YAMLError as err:
  451. # Error loading yaml or json
  452. raise YeditException('Problem with loading yaml file. %s' % err)
  453. return self.yaml_dict
  454. def get(self, key):
  455. ''' get a specified key'''
  456. try:
  457. entry = Yedit.get_entry(self.yaml_dict, key, self.separator)
  458. except KeyError:
  459. entry = None
  460. return entry
  461. def pop(self, path, key_or_item):
  462. ''' remove a key, value pair from a dict or an item for a list'''
  463. try:
  464. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  465. except KeyError:
  466. entry = None
  467. if entry is None:
  468. return (False, self.yaml_dict)
  469. if isinstance(entry, dict):
  470. # pylint: disable=no-member,maybe-no-member
  471. if key_or_item in entry:
  472. entry.pop(key_or_item)
  473. return (True, self.yaml_dict)
  474. return (False, self.yaml_dict)
  475. elif isinstance(entry, list):
  476. # pylint: disable=no-member,maybe-no-member
  477. ind = None
  478. try:
  479. ind = entry.index(key_or_item)
  480. except ValueError:
  481. return (False, self.yaml_dict)
  482. entry.pop(ind)
  483. return (True, self.yaml_dict)
  484. return (False, self.yaml_dict)
  485. def delete(self, path):
  486. ''' remove path from a dict'''
  487. try:
  488. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  489. except KeyError:
  490. entry = None
  491. if entry is None:
  492. return (False, self.yaml_dict)
  493. result = Yedit.remove_entry(self.yaml_dict, path, self.separator)
  494. if not result:
  495. return (False, self.yaml_dict)
  496. return (True, self.yaml_dict)
  497. def exists(self, path, value):
  498. ''' check if value exists at path'''
  499. try:
  500. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  501. except KeyError:
  502. entry = None
  503. if isinstance(entry, list):
  504. if value in entry:
  505. return True
  506. return False
  507. elif isinstance(entry, dict):
  508. if isinstance(value, dict):
  509. rval = False
  510. for key, val in value.items():
  511. if entry[key] != val:
  512. rval = False
  513. break
  514. else:
  515. rval = True
  516. return rval
  517. return value in entry
  518. return entry == value
  519. def append(self, path, value):
  520. '''append value to a list'''
  521. try:
  522. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  523. except KeyError:
  524. entry = None
  525. if entry is None:
  526. self.put(path, [])
  527. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  528. if not isinstance(entry, list):
  529. return (False, self.yaml_dict)
  530. # pylint: disable=no-member,maybe-no-member
  531. entry.append(value)
  532. return (True, self.yaml_dict)
  533. # pylint: disable=too-many-arguments
  534. def update(self, path, value, index=None, curr_value=None):
  535. ''' put path, value into a dict '''
  536. try:
  537. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  538. except KeyError:
  539. entry = None
  540. if isinstance(entry, dict):
  541. # pylint: disable=no-member,maybe-no-member
  542. if not isinstance(value, dict):
  543. raise YeditException('Cannot replace key, value entry in ' +
  544. 'dict with non-dict type. value=[%s] [%s]' % (value, type(value))) # noqa: E501
  545. entry.update(value)
  546. return (True, self.yaml_dict)
  547. elif isinstance(entry, list):
  548. # pylint: disable=no-member,maybe-no-member
  549. ind = None
  550. if curr_value:
  551. try:
  552. ind = entry.index(curr_value)
  553. except ValueError:
  554. return (False, self.yaml_dict)
  555. elif index is not None:
  556. ind = index
  557. if ind is not None and entry[ind] != value:
  558. entry[ind] = value
  559. return (True, self.yaml_dict)
  560. # see if it exists in the list
  561. try:
  562. ind = entry.index(value)
  563. except ValueError:
  564. # doesn't exist, append it
  565. entry.append(value)
  566. return (True, self.yaml_dict)
  567. # already exists, return
  568. if ind is not None:
  569. return (False, self.yaml_dict)
  570. return (False, self.yaml_dict)
  571. def put(self, path, value):
  572. ''' put path, value into a dict '''
  573. try:
  574. entry = Yedit.get_entry(self.yaml_dict, path, self.separator)
  575. except KeyError:
  576. entry = None
  577. if entry == value:
  578. return (False, self.yaml_dict)
  579. # deepcopy didn't work
  580. if hasattr(yaml, 'round_trip_dump'):
  581. # pylint: disable=no-member
  582. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict,
  583. default_flow_style=False),
  584. yaml.RoundTripLoader)
  585. # pylint: disable=no-member
  586. if hasattr(self.yaml_dict, 'fa'):
  587. tmp_copy.fa.set_block_style()
  588. else:
  589. tmp_copy = copy.deepcopy(self.yaml_dict)
  590. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  591. if not result:
  592. return (False, self.yaml_dict)
  593. self.yaml_dict = tmp_copy
  594. return (True, self.yaml_dict)
  595. def create(self, path, value):
  596. ''' create a yaml file '''
  597. if not self.file_exists():
  598. # deepcopy didn't work
  599. if hasattr(yaml, 'round_trip_dump'):
  600. # pylint: disable=no-member
  601. tmp_copy = yaml.load(yaml.round_trip_dump(self.yaml_dict, default_flow_style=False), # noqa: E501
  602. yaml.RoundTripLoader)
  603. # pylint: disable=no-member
  604. if hasattr(self.yaml_dict, 'fa'):
  605. tmp_copy.fa.set_block_style()
  606. else:
  607. tmp_copy = copy.deepcopy(self.yaml_dict)
  608. result = Yedit.add_entry(tmp_copy, path, value, self.separator)
  609. if result:
  610. self.yaml_dict = tmp_copy
  611. return (True, self.yaml_dict)
  612. return (False, self.yaml_dict)
  613. @staticmethod
  614. def get_curr_value(invalue, val_type):
  615. '''return the current value'''
  616. if invalue is None:
  617. return None
  618. curr_value = invalue
  619. if val_type == 'yaml':
  620. curr_value = yaml.load(invalue)
  621. elif val_type == 'json':
  622. curr_value = json.loads(invalue)
  623. return curr_value
  624. @staticmethod
  625. def parse_value(inc_value, vtype=''):
  626. '''determine value type passed'''
  627. true_bools = ['y', 'Y', 'yes', 'Yes', 'YES', 'true', 'True', 'TRUE',
  628. 'on', 'On', 'ON', ]
  629. false_bools = ['n', 'N', 'no', 'No', 'NO', 'false', 'False', 'FALSE',
  630. 'off', 'Off', 'OFF']
  631. # It came in as a string but you didn't specify value_type as string
  632. # we will convert to bool if it matches any of the above cases
  633. if isinstance(inc_value, str) and 'bool' in vtype:
  634. if inc_value not in true_bools and inc_value not in false_bools:
  635. raise YeditException('Not a boolean type. str=[%s] vtype=[%s]'
  636. % (inc_value, vtype))
  637. elif isinstance(inc_value, bool) and 'str' in vtype:
  638. inc_value = str(inc_value)
  639. # If vtype is not str then go ahead and attempt to yaml load it.
  640. if isinstance(inc_value, str) and 'str' not in vtype:
  641. try:
  642. inc_value = yaml.load(inc_value)
  643. except Exception:
  644. raise YeditException('Could not determine type of incoming ' +
  645. 'value. value=[%s] vtype=[%s]'
  646. % (type(inc_value), vtype))
  647. return inc_value
  648. # pylint: disable=too-many-return-statements,too-many-branches
  649. @staticmethod
  650. def run_ansible(module):
  651. '''perform the idempotent crud operations'''
  652. yamlfile = Yedit(filename=module.params['src'],
  653. backup=module.params['backup'],
  654. separator=module.params['separator'])
  655. if module.params['src']:
  656. rval = yamlfile.load()
  657. if yamlfile.yaml_dict is None and \
  658. module.params['state'] != 'present':
  659. return {'failed': True,
  660. 'msg': 'Error opening file [%s]. Verify that the ' +
  661. 'file exists, that it is has correct' +
  662. ' permissions, and is valid yaml.'}
  663. if module.params['state'] == 'list':
  664. if module.params['content']:
  665. content = Yedit.parse_value(module.params['content'],
  666. module.params['content_type'])
  667. yamlfile.yaml_dict = content
  668. if module.params['key']:
  669. rval = yamlfile.get(module.params['key']) or {}
  670. return {'changed': False, 'result': rval, 'state': "list"}
  671. elif module.params['state'] == 'absent':
  672. if module.params['content']:
  673. content = Yedit.parse_value(module.params['content'],
  674. module.params['content_type'])
  675. yamlfile.yaml_dict = content
  676. if module.params['update']:
  677. rval = yamlfile.pop(module.params['key'],
  678. module.params['value'])
  679. else:
  680. rval = yamlfile.delete(module.params['key'])
  681. if rval[0] and module.params['src']:
  682. yamlfile.write()
  683. return {'changed': rval[0], 'result': rval[1], 'state': "absent"}
  684. elif module.params['state'] == 'present':
  685. # check if content is different than what is in the file
  686. if module.params['content']:
  687. content = Yedit.parse_value(module.params['content'],
  688. module.params['content_type'])
  689. # We had no edits to make and the contents are the same
  690. if yamlfile.yaml_dict == content and \
  691. module.params['value'] is None:
  692. return {'changed': False,
  693. 'result': yamlfile.yaml_dict,
  694. 'state': "present"}
  695. yamlfile.yaml_dict = content
  696. # we were passed a value; parse it
  697. if module.params['value']:
  698. value = Yedit.parse_value(module.params['value'],
  699. module.params['value_type'])
  700. key = module.params['key']
  701. if module.params['update']:
  702. # pylint: disable=line-too-long
  703. curr_value = Yedit.get_curr_value(Yedit.parse_value(module.params['curr_value']), # noqa: E501
  704. module.params['curr_value_format']) # noqa: E501
  705. rval = yamlfile.update(key, value, module.params['index'], curr_value) # noqa: E501
  706. elif module.params['append']:
  707. rval = yamlfile.append(key, value)
  708. else:
  709. rval = yamlfile.put(key, value)
  710. if rval[0] and module.params['src']:
  711. yamlfile.write()
  712. return {'changed': rval[0],
  713. 'result': rval[1], 'state': "present"}
  714. # no edits to make
  715. if module.params['src']:
  716. # pylint: disable=redefined-variable-type
  717. rval = yamlfile.write()
  718. return {'changed': rval[0],
  719. 'result': rval[1],
  720. 'state': "present"}
  721. return {'failed': True, 'msg': 'Unkown state passed'}
  722. # -*- -*- -*- End included fragment: ../../lib_utils/src/class/yedit.py -*- -*- -*-
  723. # -*- -*- -*- Begin included fragment: lib/base.py -*- -*- -*-
  724. # pylint: disable=too-many-lines
  725. # noqa: E301,E302,E303,T001
  726. class OpenShiftCLIError(Exception):
  727. '''Exception class for openshiftcli'''
  728. pass
  729. # pylint: disable=too-few-public-methods
  730. class OpenShiftCLI(object):
  731. ''' Class to wrap the command line tools '''
  732. def __init__(self,
  733. namespace,
  734. kubeconfig='/etc/origin/master/admin.kubeconfig',
  735. verbose=False,
  736. all_namespaces=False):
  737. ''' Constructor for OpenshiftCLI '''
  738. self.namespace = namespace
  739. self.verbose = verbose
  740. self.kubeconfig = Utils.create_tmpfile_copy(kubeconfig)
  741. self.all_namespaces = all_namespaces
  742. # Pylint allows only 5 arguments to be passed.
  743. # pylint: disable=too-many-arguments
  744. def _replace_content(self, resource, rname, content, force=False, sep='.'):
  745. ''' replace the current object with the content '''
  746. res = self._get(resource, rname)
  747. if not res['results']:
  748. return res
  749. fname = Utils.create_tmpfile(rname + '-')
  750. yed = Yedit(fname, res['results'][0], separator=sep)
  751. changes = []
  752. for key, value in content.items():
  753. changes.append(yed.put(key, value))
  754. if any([change[0] for change in changes]):
  755. yed.write()
  756. atexit.register(Utils.cleanup, [fname])
  757. return self._replace(fname, force)
  758. return {'returncode': 0, 'updated': False}
  759. def _replace(self, fname, force=False):
  760. '''replace the current object with oc replace'''
  761. cmd = ['replace', '-f', fname]
  762. if force:
  763. cmd.append('--force')
  764. return self.openshift_cmd(cmd)
  765. def _create_from_content(self, rname, content):
  766. '''create a temporary file and then call oc create on it'''
  767. fname = Utils.create_tmpfile(rname + '-')
  768. yed = Yedit(fname, content=content)
  769. yed.write()
  770. atexit.register(Utils.cleanup, [fname])
  771. return self._create(fname)
  772. def _create(self, fname):
  773. '''call oc create on a filename'''
  774. return self.openshift_cmd(['create', '-f', fname])
  775. def _delete(self, resource, rname, selector=None):
  776. '''call oc delete on a resource'''
  777. cmd = ['delete', resource, rname]
  778. if selector:
  779. cmd.append('--selector=%s' % selector)
  780. return self.openshift_cmd(cmd)
  781. def _process(self, template_name, create=False, params=None, template_data=None): # noqa: E501
  782. '''process a template
  783. template_name: the name of the template to process
  784. create: whether to send to oc create after processing
  785. params: the parameters for the template
  786. template_data: the incoming template's data; instead of a file
  787. '''
  788. cmd = ['process']
  789. if template_data:
  790. cmd.extend(['-f', '-'])
  791. else:
  792. cmd.append(template_name)
  793. if params:
  794. param_str = ["%s=%s" % (key, value) for key, value in params.items()]
  795. cmd.append('-v')
  796. cmd.extend(param_str)
  797. results = self.openshift_cmd(cmd, output=True, input_data=template_data)
  798. if results['returncode'] != 0 or not create:
  799. return results
  800. fname = Utils.create_tmpfile(template_name + '-')
  801. yed = Yedit(fname, results['results'])
  802. yed.write()
  803. atexit.register(Utils.cleanup, [fname])
  804. return self.openshift_cmd(['create', '-f', fname])
  805. def _get(self, resource, rname=None, selector=None):
  806. '''return a resource by name '''
  807. cmd = ['get', resource]
  808. if selector:
  809. cmd.append('--selector=%s' % selector)
  810. elif rname:
  811. cmd.append(rname)
  812. cmd.extend(['-o', 'json'])
  813. rval = self.openshift_cmd(cmd, output=True)
  814. # Ensure results are retuned in an array
  815. if 'items' in rval:
  816. rval['results'] = rval['items']
  817. elif not isinstance(rval['results'], list):
  818. rval['results'] = [rval['results']]
  819. return rval
  820. def _schedulable(self, node=None, selector=None, schedulable=True):
  821. ''' perform oadm manage-node scheduable '''
  822. cmd = ['manage-node']
  823. if node:
  824. cmd.extend(node)
  825. else:
  826. cmd.append('--selector=%s' % selector)
  827. cmd.append('--schedulable=%s' % schedulable)
  828. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw') # noqa: E501
  829. def _list_pods(self, node=None, selector=None, pod_selector=None):
  830. ''' perform oadm list pods
  831. node: the node in which to list pods
  832. selector: the label selector filter if provided
  833. pod_selector: the pod selector filter if provided
  834. '''
  835. cmd = ['manage-node']
  836. if node:
  837. cmd.extend(node)
  838. else:
  839. cmd.append('--selector=%s' % selector)
  840. if pod_selector:
  841. cmd.append('--pod-selector=%s' % pod_selector)
  842. cmd.extend(['--list-pods', '-o', 'json'])
  843. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  844. # pylint: disable=too-many-arguments
  845. def _evacuate(self, node=None, selector=None, pod_selector=None, dry_run=False, grace_period=None, force=False):
  846. ''' perform oadm manage-node evacuate '''
  847. cmd = ['manage-node']
  848. if node:
  849. cmd.extend(node)
  850. else:
  851. cmd.append('--selector=%s' % selector)
  852. if dry_run:
  853. cmd.append('--dry-run')
  854. if pod_selector:
  855. cmd.append('--pod-selector=%s' % pod_selector)
  856. if grace_period:
  857. cmd.append('--grace-period=%s' % int(grace_period))
  858. if force:
  859. cmd.append('--force')
  860. cmd.append('--evacuate')
  861. return self.openshift_cmd(cmd, oadm=True, output=True, output_type='raw')
  862. def _version(self):
  863. ''' return the openshift version'''
  864. return self.openshift_cmd(['version'], output=True, output_type='raw')
  865. def _import_image(self, url=None, name=None, tag=None):
  866. ''' perform image import '''
  867. cmd = ['import-image']
  868. image = '{0}'.format(name)
  869. if tag:
  870. image += ':{0}'.format(tag)
  871. cmd.append(image)
  872. if url:
  873. cmd.append('--from={0}/{1}'.format(url, image))
  874. cmd.append('-n{0}'.format(self.namespace))
  875. cmd.append('--confirm')
  876. return self.openshift_cmd(cmd)
  877. def _run(self, cmds, input_data):
  878. ''' Actually executes the command. This makes mocking easier. '''
  879. curr_env = os.environ.copy()
  880. curr_env.update({'KUBECONFIG': self.kubeconfig})
  881. proc = subprocess.Popen(cmds,
  882. stdin=subprocess.PIPE,
  883. stdout=subprocess.PIPE,
  884. stderr=subprocess.PIPE,
  885. env=curr_env)
  886. stdout, stderr = proc.communicate(input_data)
  887. return proc.returncode, stdout, stderr
  888. # pylint: disable=too-many-arguments,too-many-branches
  889. def openshift_cmd(self, cmd, oadm=False, output=False, output_type='json', input_data=None):
  890. '''Base command for oc '''
  891. cmds = []
  892. if oadm:
  893. cmds = ['oadm']
  894. else:
  895. cmds = ['oc']
  896. if self.all_namespaces:
  897. cmds.extend(['--all-namespaces'])
  898. elif self.namespace is not None and self.namespace.lower() not in ['none', 'emtpy']: # E501
  899. cmds.extend(['-n', self.namespace])
  900. cmds.extend(cmd)
  901. rval = {}
  902. results = ''
  903. err = None
  904. if self.verbose:
  905. print(' '.join(cmds))
  906. returncode, stdout, stderr = self._run(cmds, input_data)
  907. rval = {"returncode": returncode,
  908. "results": results,
  909. "cmd": ' '.join(cmds)}
  910. if returncode == 0:
  911. if output:
  912. if output_type == 'json':
  913. try:
  914. rval['results'] = json.loads(stdout)
  915. except ValueError as err:
  916. if "No JSON object could be decoded" in err.args:
  917. err = err.args
  918. elif output_type == 'raw':
  919. rval['results'] = stdout
  920. if self.verbose:
  921. print("STDOUT: {0}".format(stdout))
  922. print("STDERR: {0}".format(stderr))
  923. if err:
  924. rval.update({"err": err,
  925. "stderr": stderr,
  926. "stdout": stdout,
  927. "cmd": cmds})
  928. else:
  929. rval.update({"stderr": stderr,
  930. "stdout": stdout,
  931. "results": {}})
  932. return rval
  933. class Utils(object):
  934. ''' utilities for openshiftcli modules '''
  935. @staticmethod
  936. def _write(filename, contents):
  937. ''' Actually write the file contents to disk. This helps with mocking. '''
  938. with open(filename, 'w') as sfd:
  939. sfd.write(contents)
  940. @staticmethod
  941. def create_tmp_file_from_contents(rname, data, ftype='yaml'):
  942. ''' create a file in tmp with name and contents'''
  943. tmp = Utils.create_tmpfile(prefix=rname)
  944. if ftype == 'yaml':
  945. # pylint: disable=no-member
  946. if hasattr(yaml, 'RoundTripDumper'):
  947. Utils._write(tmp, yaml.dump(data, Dumper=yaml.RoundTripDumper))
  948. else:
  949. Utils._write(tmp, yaml.safe_dump(data, default_flow_style=False))
  950. elif ftype == 'json':
  951. Utils._write(tmp, json.dumps(data))
  952. else:
  953. Utils._write(tmp, data)
  954. # Register cleanup when module is done
  955. atexit.register(Utils.cleanup, [tmp])
  956. return tmp
  957. @staticmethod
  958. def create_tmpfile_copy(inc_file):
  959. '''create a temporary copy of a file'''
  960. tmpfile = Utils.create_tmpfile('lib_openshift-')
  961. Utils._write(tmpfile, open(inc_file).read())
  962. # Cleanup the tmpfile
  963. atexit.register(Utils.cleanup, [tmpfile])
  964. return tmpfile
  965. @staticmethod
  966. def create_tmpfile(prefix='tmp'):
  967. ''' Generates and returns a temporary file name '''
  968. with tempfile.NamedTemporaryFile(prefix=prefix, delete=False) as tmp:
  969. return tmp.name
  970. @staticmethod
  971. def create_tmp_files_from_contents(content, content_type=None):
  972. '''Turn an array of dict: filename, content into a files array'''
  973. if not isinstance(content, list):
  974. content = [content]
  975. files = []
  976. for item in content:
  977. path = Utils.create_tmp_file_from_contents(item['path'] + '-',
  978. item['data'],
  979. ftype=content_type)
  980. files.append({'name': os.path.basename(item['path']),
  981. 'path': path})
  982. return files
  983. @staticmethod
  984. def cleanup(files):
  985. '''Clean up on exit '''
  986. for sfile in files:
  987. if os.path.exists(sfile):
  988. if os.path.isdir(sfile):
  989. shutil.rmtree(sfile)
  990. elif os.path.isfile(sfile):
  991. os.remove(sfile)
  992. @staticmethod
  993. def exists(results, _name):
  994. ''' Check to see if the results include the name '''
  995. if not results:
  996. return False
  997. if Utils.find_result(results, _name):
  998. return True
  999. return False
  1000. @staticmethod
  1001. def find_result(results, _name):
  1002. ''' Find the specified result by name'''
  1003. rval = None
  1004. for result in results:
  1005. if 'metadata' in result and result['metadata']['name'] == _name:
  1006. rval = result
  1007. break
  1008. return rval
  1009. @staticmethod
  1010. def get_resource_file(sfile, sfile_type='yaml'):
  1011. ''' return the service file '''
  1012. contents = None
  1013. with open(sfile) as sfd:
  1014. contents = sfd.read()
  1015. if sfile_type == 'yaml':
  1016. # pylint: disable=no-member
  1017. if hasattr(yaml, 'RoundTripLoader'):
  1018. contents = yaml.load(contents, yaml.RoundTripLoader)
  1019. else:
  1020. contents = yaml.safe_load(contents)
  1021. elif sfile_type == 'json':
  1022. contents = json.loads(contents)
  1023. return contents
  1024. @staticmethod
  1025. def filter_versions(stdout):
  1026. ''' filter the oc version output '''
  1027. version_dict = {}
  1028. version_search = ['oc', 'openshift', 'kubernetes']
  1029. for line in stdout.strip().split('\n'):
  1030. for term in version_search:
  1031. if not line:
  1032. continue
  1033. if line.startswith(term):
  1034. version_dict[term] = line.split()[-1]
  1035. # horrible hack to get openshift version in Openshift 3.2
  1036. # By default "oc version in 3.2 does not return an "openshift" version
  1037. if "openshift" not in version_dict:
  1038. version_dict["openshift"] = version_dict["oc"]
  1039. return version_dict
  1040. @staticmethod
  1041. def add_custom_versions(versions):
  1042. ''' create custom versions strings '''
  1043. versions_dict = {}
  1044. for tech, version in versions.items():
  1045. # clean up "-" from version
  1046. if "-" in version:
  1047. version = version.split("-")[0]
  1048. if version.startswith('v'):
  1049. versions_dict[tech + '_numeric'] = version[1:].split('+')[0]
  1050. # "v3.3.0.33" is what we have, we want "3.3"
  1051. versions_dict[tech + '_short'] = version[1:4]
  1052. return versions_dict
  1053. @staticmethod
  1054. def openshift_installed():
  1055. ''' check if openshift is installed '''
  1056. import yum
  1057. yum_base = yum.YumBase()
  1058. if yum_base.rpmdb.searchNevra(name='atomic-openshift'):
  1059. return True
  1060. return False
  1061. # Disabling too-many-branches. This is a yaml dictionary comparison function
  1062. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  1063. @staticmethod
  1064. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  1065. ''' Given a user defined definition, compare it with the results given back by our query. '''
  1066. # Currently these values are autogenerated and we do not need to check them
  1067. skip = ['metadata', 'status']
  1068. if skip_keys:
  1069. skip.extend(skip_keys)
  1070. for key, value in result_def.items():
  1071. if key in skip:
  1072. continue
  1073. # Both are lists
  1074. if isinstance(value, list):
  1075. if key not in user_def:
  1076. if debug:
  1077. print('User data does not have key [%s]' % key)
  1078. print('User data: %s' % user_def)
  1079. return False
  1080. if not isinstance(user_def[key], list):
  1081. if debug:
  1082. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  1083. return False
  1084. if len(user_def[key]) != len(value):
  1085. if debug:
  1086. print("List lengths are not equal.")
  1087. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  1088. print("user_def: %s" % user_def[key])
  1089. print("value: %s" % value)
  1090. return False
  1091. for values in zip(user_def[key], value):
  1092. if isinstance(values[0], dict) and isinstance(values[1], dict):
  1093. if debug:
  1094. print('sending list - list')
  1095. print(type(values[0]))
  1096. print(type(values[1]))
  1097. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  1098. if not result:
  1099. print('list compare returned false')
  1100. return False
  1101. elif value != user_def[key]:
  1102. if debug:
  1103. print('value should be identical')
  1104. print(value)
  1105. print(user_def[key])
  1106. return False
  1107. # recurse on a dictionary
  1108. elif isinstance(value, dict):
  1109. if key not in user_def:
  1110. if debug:
  1111. print("user_def does not have key [%s]" % key)
  1112. return False
  1113. if not isinstance(user_def[key], dict):
  1114. if debug:
  1115. print("dict returned false: not instance of dict")
  1116. return False
  1117. # before passing ensure keys match
  1118. api_values = set(value.keys()) - set(skip)
  1119. user_values = set(user_def[key].keys()) - set(skip)
  1120. if api_values != user_values:
  1121. if debug:
  1122. print("keys are not equal in dict")
  1123. print(api_values)
  1124. print(user_values)
  1125. return False
  1126. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  1127. if not result:
  1128. if debug:
  1129. print("dict returned false")
  1130. print(result)
  1131. return False
  1132. # Verify each key, value pair is the same
  1133. else:
  1134. if key not in user_def or value != user_def[key]:
  1135. if debug:
  1136. print("value not equal; user_def does not have key")
  1137. print(key)
  1138. print(value)
  1139. if key in user_def:
  1140. print(user_def[key])
  1141. return False
  1142. if debug:
  1143. print('returning true')
  1144. return True
  1145. class OpenShiftCLIConfig(object):
  1146. '''Generic Config'''
  1147. def __init__(self, rname, namespace, kubeconfig, options):
  1148. self.kubeconfig = kubeconfig
  1149. self.name = rname
  1150. self.namespace = namespace
  1151. self._options = options
  1152. @property
  1153. def config_options(self):
  1154. ''' return config options '''
  1155. return self._options
  1156. def to_option_list(self):
  1157. '''return all options as a string'''
  1158. return self.stringify()
  1159. def stringify(self):
  1160. ''' return the options hash as cli params in a string '''
  1161. rval = []
  1162. for key, data in self.config_options.items():
  1163. if data['include'] \
  1164. and (data['value'] or isinstance(data['value'], int)):
  1165. rval.append('--%s=%s' % (key.replace('_', '-'), data['value']))
  1166. return rval
  1167. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1168. # -*- -*- -*- Begin included fragment: lib/service.py -*- -*- -*-
  1169. # pylint: disable=too-many-instance-attributes
  1170. class ServiceConfig(object):
  1171. ''' Handle service options '''
  1172. # pylint: disable=too-many-arguments
  1173. def __init__(self,
  1174. sname,
  1175. namespace,
  1176. ports,
  1177. selector=None,
  1178. labels=None,
  1179. cluster_ip=None,
  1180. portal_ip=None,
  1181. session_affinity=None,
  1182. service_type=None):
  1183. ''' constructor for handling service options '''
  1184. self.name = sname
  1185. self.namespace = namespace
  1186. self.ports = ports
  1187. self.selector = selector
  1188. self.labels = labels
  1189. self.cluster_ip = cluster_ip
  1190. self.portal_ip = portal_ip
  1191. self.session_affinity = session_affinity
  1192. self.service_type = service_type
  1193. self.data = {}
  1194. self.create_dict()
  1195. def create_dict(self):
  1196. ''' instantiates a service dict '''
  1197. self.data['apiVersion'] = 'v1'
  1198. self.data['kind'] = 'Service'
  1199. self.data['metadata'] = {}
  1200. self.data['metadata']['name'] = self.name
  1201. self.data['metadata']['namespace'] = self.namespace
  1202. if self.labels:
  1203. for lab, lab_value in self.labels.items():
  1204. self.data['metadata'][lab] = lab_value
  1205. self.data['spec'] = {}
  1206. if self.ports:
  1207. self.data['spec']['ports'] = self.ports
  1208. else:
  1209. self.data['spec']['ports'] = []
  1210. if self.selector:
  1211. self.data['spec']['selector'] = self.selector
  1212. self.data['spec']['sessionAffinity'] = self.session_affinity or 'None'
  1213. if self.cluster_ip:
  1214. self.data['spec']['clusterIP'] = self.cluster_ip
  1215. if self.portal_ip:
  1216. self.data['spec']['portalIP'] = self.portal_ip
  1217. if self.service_type:
  1218. self.data['spec']['type'] = self.service_type
  1219. # pylint: disable=too-many-instance-attributes,too-many-public-methods
  1220. class Service(Yedit):
  1221. ''' Class to model the oc service object '''
  1222. port_path = "spec.ports"
  1223. portal_ip = "spec.portalIP"
  1224. cluster_ip = "spec.clusterIP"
  1225. kind = 'Service'
  1226. def __init__(self, content):
  1227. '''Service constructor'''
  1228. super(Service, self).__init__(content=content)
  1229. def get_ports(self):
  1230. ''' get a list of ports '''
  1231. return self.get(Service.port_path) or []
  1232. def add_ports(self, inc_ports):
  1233. ''' add a port object to the ports list '''
  1234. if not isinstance(inc_ports, list):
  1235. inc_ports = [inc_ports]
  1236. ports = self.get_ports()
  1237. if not ports:
  1238. self.put(Service.port_path, inc_ports)
  1239. else:
  1240. ports.extend(inc_ports)
  1241. return True
  1242. def find_ports(self, inc_port):
  1243. ''' find a specific port '''
  1244. for port in self.get_ports():
  1245. if port['port'] == inc_port['port']:
  1246. return port
  1247. return None
  1248. def delete_ports(self, inc_ports):
  1249. ''' remove a port from a service '''
  1250. if not isinstance(inc_ports, list):
  1251. inc_ports = [inc_ports]
  1252. ports = self.get(Service.port_path) or []
  1253. if not ports:
  1254. return True
  1255. removed = False
  1256. for inc_port in inc_ports:
  1257. port = self.find_ports(inc_port)
  1258. if port:
  1259. ports.remove(port)
  1260. removed = True
  1261. return removed
  1262. def add_cluster_ip(self, sip):
  1263. '''add cluster ip'''
  1264. self.put(Service.cluster_ip, sip)
  1265. def add_portal_ip(self, pip):
  1266. '''add cluster ip'''
  1267. self.put(Service.portal_ip, pip)
  1268. # -*- -*- -*- End included fragment: lib/service.py -*- -*- -*-
  1269. # -*- -*- -*- Begin included fragment: lib/deploymentconfig.py -*- -*- -*-
  1270. # pylint: disable=too-many-public-methods
  1271. class DeploymentConfig(Yedit):
  1272. ''' Class to wrap the oc command line tools '''
  1273. default_deployment_config = '''
  1274. apiVersion: v1
  1275. kind: DeploymentConfig
  1276. metadata:
  1277. name: default_dc
  1278. namespace: default
  1279. spec:
  1280. replicas: 0
  1281. selector:
  1282. default_dc: default_dc
  1283. strategy:
  1284. resources: {}
  1285. rollingParams:
  1286. intervalSeconds: 1
  1287. maxSurge: 0
  1288. maxUnavailable: 25%
  1289. timeoutSeconds: 600
  1290. updatePercent: -25
  1291. updatePeriodSeconds: 1
  1292. type: Rolling
  1293. template:
  1294. metadata:
  1295. spec:
  1296. containers:
  1297. - env:
  1298. - name: default
  1299. value: default
  1300. image: default
  1301. imagePullPolicy: IfNotPresent
  1302. name: default_dc
  1303. ports:
  1304. - containerPort: 8000
  1305. hostPort: 8000
  1306. protocol: TCP
  1307. name: default_port
  1308. resources: {}
  1309. terminationMessagePath: /dev/termination-log
  1310. dnsPolicy: ClusterFirst
  1311. hostNetwork: true
  1312. nodeSelector:
  1313. type: compute
  1314. restartPolicy: Always
  1315. securityContext: {}
  1316. serviceAccount: default
  1317. serviceAccountName: default
  1318. terminationGracePeriodSeconds: 30
  1319. triggers:
  1320. - type: ConfigChange
  1321. '''
  1322. replicas_path = "spec.replicas"
  1323. env_path = "spec.template.spec.containers[0].env"
  1324. volumes_path = "spec.template.spec.volumes"
  1325. container_path = "spec.template.spec.containers"
  1326. volume_mounts_path = "spec.template.spec.containers[0].volumeMounts"
  1327. def __init__(self, content=None):
  1328. ''' Constructor for deploymentconfig '''
  1329. if not content:
  1330. content = DeploymentConfig.default_deployment_config
  1331. super(DeploymentConfig, self).__init__(content=content)
  1332. # pylint: disable=no-member
  1333. def add_env_value(self, key, value):
  1334. ''' add key, value pair to env array '''
  1335. rval = False
  1336. env = self.get_env_vars()
  1337. if env:
  1338. env.append({'name': key, 'value': value})
  1339. rval = True
  1340. else:
  1341. result = self.put(DeploymentConfig.env_path, {'name': key, 'value': value})
  1342. rval = result[0]
  1343. return rval
  1344. def exists_env_value(self, key, value):
  1345. ''' return whether a key, value pair exists '''
  1346. results = self.get_env_vars()
  1347. if not results:
  1348. return False
  1349. for result in results:
  1350. if result['name'] == key and result['value'] == value:
  1351. return True
  1352. return False
  1353. def exists_env_key(self, key):
  1354. ''' return whether a key, value pair exists '''
  1355. results = self.get_env_vars()
  1356. if not results:
  1357. return False
  1358. for result in results:
  1359. if result['name'] == key:
  1360. return True
  1361. return False
  1362. def get_env_vars(self):
  1363. '''return a environment variables '''
  1364. return self.get(DeploymentConfig.env_path) or []
  1365. def delete_env_var(self, keys):
  1366. '''delete a list of keys '''
  1367. if not isinstance(keys, list):
  1368. keys = [keys]
  1369. env_vars_array = self.get_env_vars()
  1370. modified = False
  1371. idx = None
  1372. for key in keys:
  1373. for env_idx, env_var in enumerate(env_vars_array):
  1374. if env_var['name'] == key:
  1375. idx = env_idx
  1376. break
  1377. if idx:
  1378. modified = True
  1379. del env_vars_array[idx]
  1380. if modified:
  1381. return True
  1382. return False
  1383. def update_env_var(self, key, value):
  1384. '''place an env in the env var list'''
  1385. env_vars_array = self.get_env_vars()
  1386. idx = None
  1387. for env_idx, env_var in enumerate(env_vars_array):
  1388. if env_var['name'] == key:
  1389. idx = env_idx
  1390. break
  1391. if idx:
  1392. env_vars_array[idx]['value'] = value
  1393. else:
  1394. self.add_env_value(key, value)
  1395. return True
  1396. def exists_volume_mount(self, volume_mount):
  1397. ''' return whether a volume mount exists '''
  1398. exist_volume_mounts = self.get_volume_mounts()
  1399. if not exist_volume_mounts:
  1400. return False
  1401. volume_mount_found = False
  1402. for exist_volume_mount in exist_volume_mounts:
  1403. if exist_volume_mount['name'] == volume_mount['name']:
  1404. volume_mount_found = True
  1405. break
  1406. return volume_mount_found
  1407. def exists_volume(self, volume):
  1408. ''' return whether a volume exists '''
  1409. exist_volumes = self.get_volumes()
  1410. volume_found = False
  1411. for exist_volume in exist_volumes:
  1412. if exist_volume['name'] == volume['name']:
  1413. volume_found = True
  1414. break
  1415. return volume_found
  1416. def find_volume_by_name(self, volume, mounts=False):
  1417. ''' return the index of a volume '''
  1418. volumes = []
  1419. if mounts:
  1420. volumes = self.get_volume_mounts()
  1421. else:
  1422. volumes = self.get_volumes()
  1423. for exist_volume in volumes:
  1424. if exist_volume['name'] == volume['name']:
  1425. return exist_volume
  1426. return None
  1427. def get_replicas(self):
  1428. ''' return replicas setting '''
  1429. return self.get(DeploymentConfig.replicas_path)
  1430. def get_volume_mounts(self):
  1431. '''return volume mount information '''
  1432. return self.get_volumes(mounts=True)
  1433. def get_volumes(self, mounts=False):
  1434. '''return volume mount information '''
  1435. if mounts:
  1436. return self.get(DeploymentConfig.volume_mounts_path) or []
  1437. return self.get(DeploymentConfig.volumes_path) or []
  1438. def delete_volume_by_name(self, volume):
  1439. '''delete a volume '''
  1440. modified = False
  1441. exist_volume_mounts = self.get_volume_mounts()
  1442. exist_volumes = self.get_volumes()
  1443. del_idx = None
  1444. for idx, exist_volume in enumerate(exist_volumes):
  1445. if 'name' in exist_volume and exist_volume['name'] == volume['name']:
  1446. del_idx = idx
  1447. break
  1448. if del_idx != None:
  1449. del exist_volumes[del_idx]
  1450. modified = True
  1451. del_idx = None
  1452. for idx, exist_volume_mount in enumerate(exist_volume_mounts):
  1453. if 'name' in exist_volume_mount and exist_volume_mount['name'] == volume['name']:
  1454. del_idx = idx
  1455. break
  1456. if del_idx != None:
  1457. del exist_volume_mounts[idx]
  1458. modified = True
  1459. return modified
  1460. def add_volume_mount(self, volume_mount):
  1461. ''' add a volume or volume mount to the proper location '''
  1462. exist_volume_mounts = self.get_volume_mounts()
  1463. if not exist_volume_mounts and volume_mount:
  1464. self.put(DeploymentConfig.volume_mounts_path, [volume_mount])
  1465. else:
  1466. exist_volume_mounts.append(volume_mount)
  1467. def add_volume(self, volume):
  1468. ''' add a volume or volume mount to the proper location '''
  1469. exist_volumes = self.get_volumes()
  1470. if not volume:
  1471. return
  1472. if not exist_volumes:
  1473. self.put(DeploymentConfig.volumes_path, [volume])
  1474. else:
  1475. exist_volumes.append(volume)
  1476. def update_replicas(self, replicas):
  1477. ''' update replicas value '''
  1478. self.put(DeploymentConfig.replicas_path, replicas)
  1479. def update_volume(self, volume):
  1480. '''place an env in the env var list'''
  1481. exist_volumes = self.get_volumes()
  1482. if not volume:
  1483. return False
  1484. # update the volume
  1485. update_idx = None
  1486. for idx, exist_vol in enumerate(exist_volumes):
  1487. if exist_vol['name'] == volume['name']:
  1488. update_idx = idx
  1489. break
  1490. if update_idx != None:
  1491. exist_volumes[update_idx] = volume
  1492. else:
  1493. self.add_volume(volume)
  1494. return True
  1495. def update_volume_mount(self, volume_mount):
  1496. '''place an env in the env var list'''
  1497. modified = False
  1498. exist_volume_mounts = self.get_volume_mounts()
  1499. if not volume_mount:
  1500. return False
  1501. # update the volume mount
  1502. for exist_vol_mount in exist_volume_mounts:
  1503. if exist_vol_mount['name'] == volume_mount['name']:
  1504. if 'mountPath' in exist_vol_mount and \
  1505. str(exist_vol_mount['mountPath']) != str(volume_mount['mountPath']):
  1506. exist_vol_mount['mountPath'] = volume_mount['mountPath']
  1507. modified = True
  1508. break
  1509. if not modified:
  1510. self.add_volume_mount(volume_mount)
  1511. modified = True
  1512. return modified
  1513. def needs_update_volume(self, volume, volume_mount):
  1514. ''' verify a volume update is needed '''
  1515. exist_volume = self.find_volume_by_name(volume)
  1516. exist_volume_mount = self.find_volume_by_name(volume, mounts=True)
  1517. results = []
  1518. results.append(exist_volume['name'] == volume['name'])
  1519. if 'secret' in volume:
  1520. results.append('secret' in exist_volume)
  1521. results.append(exist_volume['secret']['secretName'] == volume['secret']['secretName'])
  1522. results.append(exist_volume_mount['name'] == volume_mount['name'])
  1523. results.append(exist_volume_mount['mountPath'] == volume_mount['mountPath'])
  1524. elif 'emptyDir' in volume:
  1525. results.append(exist_volume_mount['name'] == volume['name'])
  1526. results.append(exist_volume_mount['mountPath'] == volume_mount['mountPath'])
  1527. elif 'persistentVolumeClaim' in volume:
  1528. pvc = 'persistentVolumeClaim'
  1529. results.append(pvc in exist_volume)
  1530. if results[-1]:
  1531. results.append(exist_volume[pvc]['claimName'] == volume[pvc]['claimName'])
  1532. if 'claimSize' in volume[pvc]:
  1533. results.append(exist_volume[pvc]['claimSize'] == volume[pvc]['claimSize'])
  1534. elif 'hostpath' in volume:
  1535. results.append('hostPath' in exist_volume)
  1536. results.append(exist_volume['hostPath']['path'] == volume_mount['mountPath'])
  1537. return not all(results)
  1538. def needs_update_replicas(self, replicas):
  1539. ''' verify whether a replica update is needed '''
  1540. current_reps = self.get(DeploymentConfig.replicas_path)
  1541. return not current_reps == replicas
  1542. # -*- -*- -*- End included fragment: lib/deploymentconfig.py -*- -*- -*-
  1543. # -*- -*- -*- Begin included fragment: lib/serviceaccount.py -*- -*- -*-
  1544. class ServiceAccountConfig(object):
  1545. '''Service account config class
  1546. This class stores the options and returns a default service account
  1547. '''
  1548. # pylint: disable=too-many-arguments
  1549. def __init__(self, sname, namespace, kubeconfig, secrets=None, image_pull_secrets=None):
  1550. self.name = sname
  1551. self.kubeconfig = kubeconfig
  1552. self.namespace = namespace
  1553. self.secrets = secrets or []
  1554. self.image_pull_secrets = image_pull_secrets or []
  1555. self.data = {}
  1556. self.create_dict()
  1557. def create_dict(self):
  1558. ''' return a properly structured volume '''
  1559. self.data['apiVersion'] = 'v1'
  1560. self.data['kind'] = 'ServiceAccount'
  1561. self.data['metadata'] = {}
  1562. self.data['metadata']['name'] = self.name
  1563. self.data['metadata']['namespace'] = self.namespace
  1564. self.data['secrets'] = []
  1565. if self.secrets:
  1566. for sec in self.secrets:
  1567. self.data['secrets'].append({"name": sec})
  1568. self.data['imagePullSecrets'] = []
  1569. if self.image_pull_secrets:
  1570. for sec in self.image_pull_secrets:
  1571. self.data['imagePullSecrets'].append({"name": sec})
  1572. class ServiceAccount(Yedit):
  1573. ''' Class to wrap the oc command line tools '''
  1574. image_pull_secrets_path = "imagePullSecrets"
  1575. secrets_path = "secrets"
  1576. def __init__(self, content):
  1577. '''ServiceAccount constructor'''
  1578. super(ServiceAccount, self).__init__(content=content)
  1579. self._secrets = None
  1580. self._image_pull_secrets = None
  1581. @property
  1582. def image_pull_secrets(self):
  1583. ''' property for image_pull_secrets '''
  1584. if self._image_pull_secrets is None:
  1585. self._image_pull_secrets = self.get(ServiceAccount.image_pull_secrets_path) or []
  1586. return self._image_pull_secrets
  1587. @image_pull_secrets.setter
  1588. def image_pull_secrets(self, secrets):
  1589. ''' property for secrets '''
  1590. self._image_pull_secrets = secrets
  1591. @property
  1592. def secrets(self):
  1593. ''' property for secrets '''
  1594. if not self._secrets:
  1595. self._secrets = self.get(ServiceAccount.secrets_path) or []
  1596. return self._secrets
  1597. @secrets.setter
  1598. def secrets(self, secrets):
  1599. ''' property for secrets '''
  1600. self._secrets = secrets
  1601. def delete_secret(self, inc_secret):
  1602. ''' remove a secret '''
  1603. remove_idx = None
  1604. for idx, sec in enumerate(self.secrets):
  1605. if sec['name'] == inc_secret:
  1606. remove_idx = idx
  1607. break
  1608. if remove_idx:
  1609. del self.secrets[remove_idx]
  1610. return True
  1611. return False
  1612. def delete_image_pull_secret(self, inc_secret):
  1613. ''' remove a image_pull_secret '''
  1614. remove_idx = None
  1615. for idx, sec in enumerate(self.image_pull_secrets):
  1616. if sec['name'] == inc_secret:
  1617. remove_idx = idx
  1618. break
  1619. if remove_idx:
  1620. del self.image_pull_secrets[remove_idx]
  1621. return True
  1622. return False
  1623. def find_secret(self, inc_secret):
  1624. '''find secret'''
  1625. for secret in self.secrets:
  1626. if secret['name'] == inc_secret:
  1627. return secret
  1628. return None
  1629. def find_image_pull_secret(self, inc_secret):
  1630. '''find secret'''
  1631. for secret in self.image_pull_secrets:
  1632. if secret['name'] == inc_secret:
  1633. return secret
  1634. return None
  1635. def add_secret(self, inc_secret):
  1636. '''add secret'''
  1637. if self.secrets:
  1638. self.secrets.append({"name": inc_secret}) # pylint: disable=no-member
  1639. else:
  1640. self.put(ServiceAccount.secrets_path, [{"name": inc_secret}])
  1641. def add_image_pull_secret(self, inc_secret):
  1642. '''add image_pull_secret'''
  1643. if self.image_pull_secrets:
  1644. self.image_pull_secrets.append({"name": inc_secret}) # pylint: disable=no-member
  1645. else:
  1646. self.put(ServiceAccount.image_pull_secrets_path, [{"name": inc_secret}])
  1647. # -*- -*- -*- End included fragment: lib/serviceaccount.py -*- -*- -*-
  1648. # -*- -*- -*- Begin included fragment: lib/secret.py -*- -*- -*-
  1649. # pylint: disable=too-many-instance-attributes
  1650. class SecretConfig(object):
  1651. ''' Handle secret options '''
  1652. # pylint: disable=too-many-arguments
  1653. def __init__(self,
  1654. sname,
  1655. namespace,
  1656. kubeconfig,
  1657. secrets=None):
  1658. ''' constructor for handling secret options '''
  1659. self.kubeconfig = kubeconfig
  1660. self.name = sname
  1661. self.namespace = namespace
  1662. self.secrets = secrets
  1663. self.data = {}
  1664. self.create_dict()
  1665. def create_dict(self):
  1666. ''' return a secret as a dict '''
  1667. self.data['apiVersion'] = 'v1'
  1668. self.data['kind'] = 'Secret'
  1669. self.data['metadata'] = {}
  1670. self.data['metadata']['name'] = self.name
  1671. self.data['metadata']['namespace'] = self.namespace
  1672. self.data['data'] = {}
  1673. if self.secrets:
  1674. for key, value in self.secrets.items():
  1675. self.data['data'][key] = value
  1676. # pylint: disable=too-many-instance-attributes
  1677. class Secret(Yedit):
  1678. ''' Class to wrap the oc command line tools '''
  1679. secret_path = "data"
  1680. kind = 'secret'
  1681. def __init__(self, content):
  1682. '''secret constructor'''
  1683. super(Secret, self).__init__(content=content)
  1684. self._secrets = None
  1685. @property
  1686. def secrets(self):
  1687. '''secret property getter'''
  1688. if self._secrets is None:
  1689. self._secrets = self.get_secrets()
  1690. return self._secrets
  1691. @secrets.setter
  1692. def secrets(self):
  1693. '''secret property setter'''
  1694. if self._secrets is None:
  1695. self._secrets = self.get_secrets()
  1696. return self._secrets
  1697. def get_secrets(self):
  1698. ''' returns all of the defined secrets '''
  1699. return self.get(Secret.secret_path) or {}
  1700. def add_secret(self, key, value):
  1701. ''' add a secret '''
  1702. if self.secrets:
  1703. self.secrets[key] = value
  1704. else:
  1705. self.put(Secret.secret_path, {key: value})
  1706. return True
  1707. def delete_secret(self, key):
  1708. ''' delete secret'''
  1709. try:
  1710. del self.secrets[key]
  1711. except KeyError as _:
  1712. return False
  1713. return True
  1714. def find_secret(self, key):
  1715. ''' find secret'''
  1716. rval = None
  1717. try:
  1718. rval = self.secrets[key]
  1719. except KeyError as _:
  1720. return None
  1721. return {'key': key, 'value': rval}
  1722. def update_secret(self, key, value):
  1723. ''' update a secret'''
  1724. # pylint: disable=no-member
  1725. if self.secrets.has_key(key):
  1726. self.secrets[key] = value
  1727. else:
  1728. self.add_secret(key, value)
  1729. return True
  1730. # -*- -*- -*- End included fragment: lib/secret.py -*- -*- -*-
  1731. # -*- -*- -*- Begin included fragment: lib/rolebinding.py -*- -*- -*-
  1732. # pylint: disable=too-many-instance-attributes
  1733. class RoleBindingConfig(object):
  1734. ''' Handle route options '''
  1735. # pylint: disable=too-many-arguments
  1736. def __init__(self,
  1737. sname,
  1738. namespace,
  1739. kubeconfig,
  1740. group_names=None,
  1741. role_ref=None,
  1742. subjects=None,
  1743. usernames=None):
  1744. ''' constructor for handling route options '''
  1745. self.kubeconfig = kubeconfig
  1746. self.name = sname
  1747. self.namespace = namespace
  1748. self.group_names = group_names
  1749. self.role_ref = role_ref
  1750. self.subjects = subjects
  1751. self.usernames = usernames
  1752. self.data = {}
  1753. self.create_dict()
  1754. def create_dict(self):
  1755. ''' return a service as a dict '''
  1756. self.data['apiVersion'] = 'v1'
  1757. self.data['kind'] = 'RoleBinding'
  1758. self.data['groupNames'] = self.group_names
  1759. self.data['metadata']['name'] = self.name
  1760. self.data['metadata']['namespace'] = self.namespace
  1761. self.data['roleRef'] = self.role_ref
  1762. self.data['subjects'] = self.subjects
  1763. self.data['userNames'] = self.usernames
  1764. # pylint: disable=too-many-instance-attributes,too-many-public-methods
  1765. class RoleBinding(Yedit):
  1766. ''' Class to wrap the oc command line tools '''
  1767. group_names_path = "groupNames"
  1768. role_ref_path = "roleRef"
  1769. subjects_path = "subjects"
  1770. user_names_path = "userNames"
  1771. kind = 'RoleBinding'
  1772. def __init__(self, content):
  1773. '''RoleBinding constructor'''
  1774. super(RoleBinding, self).__init__(content=content)
  1775. self._subjects = None
  1776. self._role_ref = None
  1777. self._group_names = None
  1778. self._user_names = None
  1779. @property
  1780. def subjects(self):
  1781. ''' subjects property '''
  1782. if self._subjects == None:
  1783. self._subjects = self.get_subjects()
  1784. return self._subjects
  1785. @subjects.setter
  1786. def subjects(self, data):
  1787. ''' subjects property setter'''
  1788. self._subjects = data
  1789. @property
  1790. def role_ref(self):
  1791. ''' role_ref property '''
  1792. if self._role_ref == None:
  1793. self._role_ref = self.get_role_ref()
  1794. return self._role_ref
  1795. @role_ref.setter
  1796. def role_ref(self, data):
  1797. ''' role_ref property setter'''
  1798. self._role_ref = data
  1799. @property
  1800. def group_names(self):
  1801. ''' group_names property '''
  1802. if self._group_names == None:
  1803. self._group_names = self.get_group_names()
  1804. return self._group_names
  1805. @group_names.setter
  1806. def group_names(self, data):
  1807. ''' group_names property setter'''
  1808. self._group_names = data
  1809. @property
  1810. def user_names(self):
  1811. ''' user_names property '''
  1812. if self._user_names == None:
  1813. self._user_names = self.get_user_names()
  1814. return self._user_names
  1815. @user_names.setter
  1816. def user_names(self, data):
  1817. ''' user_names property setter'''
  1818. self._user_names = data
  1819. def get_group_names(self):
  1820. ''' return groupNames '''
  1821. return self.get(RoleBinding.group_names_path) or []
  1822. def get_user_names(self):
  1823. ''' return usernames '''
  1824. return self.get(RoleBinding.user_names_path) or []
  1825. def get_role_ref(self):
  1826. ''' return role_ref '''
  1827. return self.get(RoleBinding.role_ref_path) or {}
  1828. def get_subjects(self):
  1829. ''' return subjects '''
  1830. return self.get(RoleBinding.subjects_path) or []
  1831. #### ADD #####
  1832. def add_subject(self, inc_subject):
  1833. ''' add a subject '''
  1834. if self.subjects:
  1835. self.subjects.append(inc_subject)
  1836. else:
  1837. self.put(RoleBinding.subjects_path, [inc_subject])
  1838. return True
  1839. def add_role_ref(self, inc_role_ref):
  1840. ''' add a role_ref '''
  1841. if not self.role_ref:
  1842. self.put(RoleBinding.role_ref_path, {"name": inc_role_ref})
  1843. return True
  1844. return False
  1845. def add_group_names(self, inc_group_names):
  1846. ''' add a group_names '''
  1847. if self.group_names:
  1848. self.group_names.append(inc_group_names)
  1849. else:
  1850. self.put(RoleBinding.group_names_path, [inc_group_names])
  1851. return True
  1852. def add_user_name(self, inc_user_name):
  1853. ''' add a username '''
  1854. if self.user_names:
  1855. self.user_names.append(inc_user_name)
  1856. else:
  1857. self.put(RoleBinding.user_names_path, [inc_user_name])
  1858. return True
  1859. #### /ADD #####
  1860. #### Remove #####
  1861. def remove_subject(self, inc_subject):
  1862. ''' remove a subject '''
  1863. try:
  1864. self.subjects.remove(inc_subject)
  1865. except ValueError as _:
  1866. return False
  1867. return True
  1868. def remove_role_ref(self, inc_role_ref):
  1869. ''' remove a role_ref '''
  1870. if self.role_ref and self.role_ref['name'] == inc_role_ref:
  1871. del self.role_ref['name']
  1872. return True
  1873. return False
  1874. def remove_group_name(self, inc_group_name):
  1875. ''' remove a groupname '''
  1876. try:
  1877. self.group_names.remove(inc_group_name)
  1878. except ValueError as _:
  1879. return False
  1880. return True
  1881. def remove_user_name(self, inc_user_name):
  1882. ''' remove a username '''
  1883. try:
  1884. self.user_names.remove(inc_user_name)
  1885. except ValueError as _:
  1886. return False
  1887. return True
  1888. #### /REMOVE #####
  1889. #### UPDATE #####
  1890. def update_subject(self, inc_subject):
  1891. ''' update a subject '''
  1892. try:
  1893. index = self.subjects.index(inc_subject)
  1894. except ValueError as _:
  1895. return self.add_subject(inc_subject)
  1896. self.subjects[index] = inc_subject
  1897. return True
  1898. def update_group_name(self, inc_group_name):
  1899. ''' update a groupname '''
  1900. try:
  1901. index = self.group_names.index(inc_group_name)
  1902. except ValueError as _:
  1903. return self.add_group_names(inc_group_name)
  1904. self.group_names[index] = inc_group_name
  1905. return True
  1906. def update_user_name(self, inc_user_name):
  1907. ''' update a username '''
  1908. try:
  1909. index = self.user_names.index(inc_user_name)
  1910. except ValueError as _:
  1911. return self.add_user_name(inc_user_name)
  1912. self.user_names[index] = inc_user_name
  1913. return True
  1914. def update_role_ref(self, inc_role_ref):
  1915. ''' update a role_ref '''
  1916. self.role_ref['name'] = inc_role_ref
  1917. return True
  1918. #### /UPDATE #####
  1919. #### FIND ####
  1920. def find_subject(self, inc_subject):
  1921. ''' find a subject '''
  1922. index = None
  1923. try:
  1924. index = self.subjects.index(inc_subject)
  1925. except ValueError as _:
  1926. return index
  1927. return index
  1928. def find_group_name(self, inc_group_name):
  1929. ''' find a group_name '''
  1930. index = None
  1931. try:
  1932. index = self.group_names.index(inc_group_name)
  1933. except ValueError as _:
  1934. return index
  1935. return index
  1936. def find_user_name(self, inc_user_name):
  1937. ''' find a user_name '''
  1938. index = None
  1939. try:
  1940. index = self.user_names.index(inc_user_name)
  1941. except ValueError as _:
  1942. return index
  1943. return index
  1944. def find_role_ref(self, inc_role_ref):
  1945. ''' find a user_name '''
  1946. if self.role_ref and self.role_ref['name'] == inc_role_ref['name']:
  1947. return self.role_ref
  1948. return None
  1949. # -*- -*- -*- End included fragment: lib/rolebinding.py -*- -*- -*-
  1950. # -*- -*- -*- Begin included fragment: class/oadm_router.py -*- -*- -*-
  1951. import time
  1952. class RouterException(Exception):
  1953. ''' Router exception'''
  1954. pass
  1955. class RouterConfig(OpenShiftCLIConfig):
  1956. ''' RouterConfig is a DTO for the router. '''
  1957. def __init__(self, rname, namespace, kubeconfig, router_options):
  1958. super(RouterConfig, self).__init__(rname, namespace, kubeconfig, router_options)
  1959. class Router(OpenShiftCLI):
  1960. ''' Class to wrap the oc command line tools '''
  1961. def __init__(self,
  1962. router_config,
  1963. verbose=False):
  1964. ''' Constructor for OpenshiftOC
  1965. a router consists of 3 or more parts
  1966. - dc/router
  1967. - svc/router
  1968. - endpoint/router
  1969. '''
  1970. super(Router, self).__init__('default', router_config.kubeconfig, verbose)
  1971. self.config = router_config
  1972. self.verbose = verbose
  1973. self.router_parts = [{'kind': 'dc', 'name': self.config.name},
  1974. {'kind': 'svc', 'name': self.config.name},
  1975. {'kind': 'sa', 'name': self.config.config_options['service_account']['value']},
  1976. {'kind': 'secret', 'name': self.config.name + '-certs'},
  1977. {'kind': 'clusterrolebinding', 'name': 'router-' + self.config.name + '-role'},
  1978. #{'kind': 'endpoints', 'name': self.config.name},
  1979. ]
  1980. self.__router_prep = None
  1981. self.dconfig = None
  1982. self.svc = None
  1983. self._secret = None
  1984. self._serviceaccount = None
  1985. self._rolebinding = None
  1986. self.get()
  1987. @property
  1988. def router_prep(self):
  1989. ''' property deploymentconfig'''
  1990. if self.__router_prep == None:
  1991. results = self.prepare_router()
  1992. if not results:
  1993. raise RouterException('Could not perform router preparation')
  1994. self.__router_prep = results
  1995. return self.__router_prep
  1996. @router_prep.setter
  1997. def router_prep(self, obj):
  1998. '''set the router prep property'''
  1999. self.__router_prep = obj
  2000. @property
  2001. def deploymentconfig(self):
  2002. ''' property deploymentconfig'''
  2003. return self.dconfig
  2004. @deploymentconfig.setter
  2005. def deploymentconfig(self, config):
  2006. ''' setter for property deploymentconfig '''
  2007. self.dconfig = config
  2008. @property
  2009. def service(self):
  2010. ''' property service '''
  2011. return self.svc
  2012. @service.setter
  2013. def service(self, config):
  2014. ''' setter for property service '''
  2015. self.svc = config
  2016. @property
  2017. def secret(self):
  2018. ''' property secret '''
  2019. return self._secret
  2020. @secret.setter
  2021. def secret(self, config):
  2022. ''' setter for property secret '''
  2023. self._secret = config
  2024. @property
  2025. def serviceaccount(self):
  2026. ''' property secret '''
  2027. return self._serviceaccount
  2028. @serviceaccount.setter
  2029. def serviceaccount(self, config):
  2030. ''' setter for property secret '''
  2031. self._serviceaccount = config
  2032. @property
  2033. def rolebinding(self):
  2034. ''' property rolebinding '''
  2035. return self._rolebinding
  2036. @rolebinding.setter
  2037. def rolebinding(self, config):
  2038. ''' setter for property rolebinding '''
  2039. self._rolebinding = config
  2040. def get(self):
  2041. ''' return the self.router_parts '''
  2042. self.service = None
  2043. self.deploymentconfig = None
  2044. self.serviceaccount = None
  2045. self.secret = None
  2046. self.rolebinding = None
  2047. for part in self.router_parts:
  2048. result = self._get(part['kind'], rname=part['name'])
  2049. if result['returncode'] == 0 and part['kind'] == 'dc':
  2050. self.deploymentconfig = DeploymentConfig(result['results'][0])
  2051. elif result['returncode'] == 0 and part['kind'] == 'svc':
  2052. self.service = Service(content=result['results'][0])
  2053. elif result['returncode'] == 0 and part['kind'] == 'sa':
  2054. self.serviceaccount = ServiceAccount(content=result['results'][0])
  2055. elif result['returncode'] == 0 and part['kind'] == 'secret':
  2056. self.secret = Secret(content=result['results'][0])
  2057. elif result['returncode'] == 0 and part['kind'] == 'clusterrolebinding':
  2058. self.rolebinding = RoleBinding(content=result['results'][0])
  2059. return {'deploymentconfig': self.deploymentconfig,
  2060. 'service': self.service,
  2061. 'serviceaccount': self.serviceaccount,
  2062. 'secret': self.secret,
  2063. 'clusterrolebinding': self.rolebinding,
  2064. }
  2065. def exists(self):
  2066. '''return a whether svc or dc exists '''
  2067. if self.deploymentconfig and self.service and self.secret and self.serviceaccount:
  2068. return True
  2069. return False
  2070. def delete(self):
  2071. '''return all pods '''
  2072. parts = []
  2073. for part in self.router_parts:
  2074. parts.append(self._delete(part['kind'], part['name']))
  2075. return parts
  2076. def add_modifications(self, deploymentconfig):
  2077. '''modify the deployment config'''
  2078. # We want modifications in the form of edits coming in from the module.
  2079. # Let's apply these here
  2080. edit_results = []
  2081. for edit in self.config.config_options['edits'].get('value', []):
  2082. if edit['action'] == 'put':
  2083. edit_results.append(deploymentconfig.put(edit['key'],
  2084. edit['value']))
  2085. if edit['action'] == 'update':
  2086. edit_results.append(deploymentconfig.update(edit['key'],
  2087. edit['value'],
  2088. edit.get('index', None),
  2089. edit.get('curr_value', None)))
  2090. if edit['action'] == 'append':
  2091. edit_results.append(deploymentconfig.append(edit['key'],
  2092. edit['value']))
  2093. if edit_results and not any([res[0] for res in edit_results]):
  2094. return None
  2095. return deploymentconfig
  2096. def prepare_router(self):
  2097. '''prepare router for instantiation'''
  2098. # We need to create the pem file
  2099. router_pem = '/tmp/router.pem'
  2100. with open(router_pem, 'w') as rfd:
  2101. rfd.write(open(self.config.config_options['cert_file']['value']).read())
  2102. rfd.write(open(self.config.config_options['key_file']['value']).read())
  2103. if self.config.config_options['cacert_file']['value'] and \
  2104. os.path.exists(self.config.config_options['cacert_file']['value']):
  2105. rfd.write(open(self.config.config_options['cacert_file']['value']).read())
  2106. atexit.register(Utils.cleanup, [router_pem])
  2107. self.config.config_options['default_cert']['value'] = router_pem
  2108. options = self.config.to_option_list()
  2109. cmd = ['router', self.config.name, '-n', self.config.namespace]
  2110. cmd.extend(options)
  2111. cmd.extend(['--dry-run=True', '-o', 'json'])
  2112. results = self.openshift_cmd(cmd, oadm=True, output=True, output_type='json')
  2113. # pylint: disable=no-member
  2114. if results['returncode'] != 0 and results['results'].has_key('items'):
  2115. return results
  2116. oc_objects = {'DeploymentConfig': {'obj': None, 'path': None},
  2117. 'Secret': {'obj': None, 'path': None},
  2118. 'ServiceAccount': {'obj': None, 'path': None},
  2119. 'ClusterRoleBinding': {'obj': None, 'path': None},
  2120. 'Service': {'obj': None, 'path': None},
  2121. }
  2122. # pylint: disable=invalid-sequence-index
  2123. for res in results['results']['items']:
  2124. if res['kind'] == 'DeploymentConfig':
  2125. oc_objects['DeploymentConfig']['obj'] = DeploymentConfig(res)
  2126. elif res['kind'] == 'Service':
  2127. oc_objects['Service']['obj'] = Service(res)
  2128. elif res['kind'] == 'ServiceAccount':
  2129. oc_objects['ServiceAccount']['obj'] = ServiceAccount(res)
  2130. elif res['kind'] == 'Secret':
  2131. oc_objects['Secret']['obj'] = Secret(res)
  2132. elif res['kind'] == 'ClusterRoleBinding':
  2133. oc_objects['ClusterRoleBinding']['obj'] = RoleBinding(res)
  2134. # Currently only deploymentconfig needs updating
  2135. # Verify we got a deploymentconfig
  2136. if not oc_objects['DeploymentConfig']['obj']:
  2137. return results
  2138. # results will need to get parsed here and modifications added
  2139. oc_objects['DeploymentConfig']['obj'] = self.add_modifications(oc_objects['DeploymentConfig']['obj'])
  2140. for oc_type in oc_objects.keys():
  2141. oc_objects[oc_type]['path'] = Utils.create_tmp_file_from_contents(oc_type, oc_objects[oc_type]['obj'].yaml_dict)
  2142. return oc_objects
  2143. def create(self):
  2144. '''Create a deploymentconfig '''
  2145. # generate the objects and prepare for instantiation
  2146. self.prepare_router()
  2147. results = []
  2148. for _, oc_data in self.router_prep.items():
  2149. results.append(self._create(oc_data['path']))
  2150. rval = 0
  2151. for result in results:
  2152. if result['returncode'] != 0 and not 'already exist' in result['stderr']:
  2153. rval = result['returncode']
  2154. return {'returncode': rval, 'results': results}
  2155. def update(self):
  2156. '''run update for the router. This performs a delete and then create '''
  2157. parts = self.delete()
  2158. for part in parts:
  2159. if part['returncode'] != 0:
  2160. if part.has_key('stderr') and 'not found' in part['stderr']:
  2161. # the object is not there, continue
  2162. continue
  2163. # something went wrong
  2164. return parts
  2165. # Ugly built in sleep here.
  2166. time.sleep(15)
  2167. return self.create()
  2168. # pylint: disable=too-many-return-statements,too-many-branches
  2169. def needs_update(self):
  2170. ''' check to see if we need to update '''
  2171. if not self.deploymentconfig or not self.service or not self.serviceaccount or not self.secret:
  2172. return True
  2173. oc_objects_prep = self.prepare_router()
  2174. # Since the output from oadm_router is returned as raw
  2175. # we need to parse it. The first line is the stats_password in 3.1
  2176. # Inside of 3.2, it is just json
  2177. # ServiceAccount:
  2178. # Need to determine the pregenerated ones from the original
  2179. # Since these are auto generated, we can skip
  2180. skip = ['secrets', 'imagePullSecrets']
  2181. if not Utils.check_def_equal(oc_objects_prep['ServiceAccount']['obj'].yaml_dict,
  2182. self.serviceaccount.yaml_dict,
  2183. skip_keys=skip,
  2184. debug=self.verbose):
  2185. return True
  2186. # Secret:
  2187. # In 3.2 oadm router generates a secret volume for certificates
  2188. # See if one was generated from our dry-run and verify it if needed
  2189. if oc_objects_prep['Secret']['obj']:
  2190. if not self.secret:
  2191. return True
  2192. if not Utils.check_def_equal(oc_objects_prep['Secret']['obj'].yaml_dict,
  2193. self.secret.yaml_dict,
  2194. skip_keys=skip,
  2195. debug=self.verbose):
  2196. return True
  2197. # Service:
  2198. # Fix the ports to have protocol=TCP
  2199. for port in oc_objects_prep['Service']['obj'].get('spec.ports'):
  2200. port['protocol'] = 'TCP'
  2201. skip = ['portalIP', 'clusterIP', 'sessionAffinity', 'type']
  2202. if not Utils.check_def_equal(oc_objects_prep['Service']['obj'].yaml_dict,
  2203. self.service.yaml_dict,
  2204. skip_keys=skip,
  2205. debug=self.verbose):
  2206. return True
  2207. # DeploymentConfig:
  2208. # Router needs some exceptions.
  2209. # We do not want to check the autogenerated password for stats admin
  2210. if not self.config.config_options['stats_password']['value']:
  2211. for idx, env_var in enumerate(oc_objects_prep['DeploymentConfig']['obj'].get(\
  2212. 'spec.template.spec.containers[0].env') or []):
  2213. if env_var['name'] == 'STATS_PASSWORD':
  2214. env_var['value'] = \
  2215. self.deploymentconfig.get('spec.template.spec.containers[0].env[%s].value' % idx)
  2216. break
  2217. # dry-run doesn't add the protocol to the ports section. We will manually do that.
  2218. for idx, port in enumerate(oc_objects_prep['DeploymentConfig']['obj'].get(\
  2219. 'spec.template.spec.containers[0].ports') or []):
  2220. if not port.has_key('protocol'):
  2221. port['protocol'] = 'TCP'
  2222. # These are different when generating
  2223. skip = ['dnsPolicy',
  2224. 'terminationGracePeriodSeconds',
  2225. 'restartPolicy', 'timeoutSeconds',
  2226. 'livenessProbe', 'readinessProbe',
  2227. 'terminationMessagePath', 'hostPort',
  2228. 'defaultMode',
  2229. ]
  2230. return not Utils.check_def_equal(oc_objects_prep['DeploymentConfig']['obj'].yaml_dict,
  2231. self.deploymentconfig.yaml_dict,
  2232. skip_keys=skip,
  2233. debug=self.verbose)
  2234. @staticmethod
  2235. def run_ansible(params, check_mode):
  2236. '''run ansible idempotent code'''
  2237. rconfig = RouterConfig(params['name'],
  2238. params['namespace'],
  2239. params['kubeconfig'],
  2240. {'default_cert': {'value': None, 'include': True},
  2241. 'cert_file': {'value': params['cert_file'], 'include': False},
  2242. 'key_file': {'value': params['key_file'], 'include': False},
  2243. 'images': {'value': params['images'], 'include': True},
  2244. 'latest_images': {'value': params['latest_images'], 'include': True},
  2245. 'labels': {'value': params['labels'], 'include': True},
  2246. 'ports': {'value': ','.join(params['ports']), 'include': True},
  2247. 'replicas': {'value': params['replicas'], 'include': True},
  2248. 'selector': {'value': params['selector'], 'include': True},
  2249. 'service_account': {'value': params['service_account'], 'include': True},
  2250. 'router_type': {'value': params['router_type'], 'include': False},
  2251. 'host_network': {'value': params['host_network'], 'include': True},
  2252. 'external_host': {'value': params['external_host'], 'include': True},
  2253. 'external_host_vserver': {'value': params['external_host_vserver'],
  2254. 'include': True},
  2255. 'external_host_insecure': {'value': params['external_host_insecure'],
  2256. 'include': True},
  2257. 'external_host_partition_path': {'value': params['external_host_partition_path'],
  2258. 'include': True},
  2259. 'external_host_username': {'value': params['external_host_username'],
  2260. 'include': True},
  2261. 'external_host_password': {'value': params['external_host_password'],
  2262. 'include': True},
  2263. 'external_host_private_key': {'value': params['external_host_private_key'],
  2264. 'include': True},
  2265. 'expose_metrics': {'value': params['expose_metrics'], 'include': True},
  2266. 'metrics_image': {'value': params['metrics_image'], 'include': True},
  2267. 'stats_user': {'value': params['stats_user'], 'include': True},
  2268. 'stats_password': {'value': params['stats_password'], 'include': True},
  2269. 'stats_port': {'value': params['stats_port'], 'include': True},
  2270. # extra
  2271. 'cacert_file': {'value': params['cacert_file'], 'include': False},
  2272. # edits
  2273. 'edits': {'value': params['edits'], 'include': False},
  2274. })
  2275. ocrouter = Router(rconfig)
  2276. state = params['state']
  2277. ########
  2278. # Delete
  2279. ########
  2280. if state == 'absent':
  2281. if not ocrouter.exists():
  2282. return {'changed': False, 'state': state}
  2283. if check_mode:
  2284. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'}
  2285. api_rval = ocrouter.delete()
  2286. return {'changed': True, 'results': api_rval, 'state': state}
  2287. if state == 'present':
  2288. ########
  2289. # Create
  2290. ########
  2291. if not ocrouter.exists():
  2292. if check_mode:
  2293. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
  2294. api_rval = ocrouter.create()
  2295. if api_rval['returncode'] != 0:
  2296. return {'failed': True, 'msg': api_rval}
  2297. return {'changed': True, 'results': api_rval, 'state': state}
  2298. ########
  2299. # Update
  2300. ########
  2301. if not ocrouter.needs_update():
  2302. return {'changed': False, 'state': state}
  2303. if check_mode:
  2304. return {'changed': False, 'msg': 'CHECK_MODE: Would have performed an update.'}
  2305. api_rval = ocrouter.update()
  2306. if api_rval['returncode'] != 0:
  2307. return {'failed': True, 'msg': api_rval}
  2308. return {'changed': True, 'results': api_rval, 'state': state}
  2309. # -*- -*- -*- End included fragment: class/oadm_router.py -*- -*- -*-
  2310. # -*- -*- -*- Begin included fragment: ansible/oadm_router.py -*- -*- -*-
  2311. def main():
  2312. '''
  2313. ansible oc module for router
  2314. '''
  2315. module = AnsibleModule(
  2316. argument_spec=dict(
  2317. state=dict(default='present', type='str',
  2318. choices=['present', 'absent']),
  2319. debug=dict(default=False, type='bool'),
  2320. namespace=dict(default='default', type='str'),
  2321. name=dict(default='router', type='str'),
  2322. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  2323. cert_file=dict(default=None, type='str'),
  2324. key_file=dict(default=None, type='str'),
  2325. images=dict(default=None, type='str'), #'openshift3/ose-${component}:${version}'
  2326. latest_images=dict(default=False, type='bool'),
  2327. labels=dict(default=None, type='list'),
  2328. ports=dict(default=['80:80', '443:443'], type='list'),
  2329. replicas=dict(default=1, type='int'),
  2330. selector=dict(default=None, type='str'),
  2331. service_account=dict(default='router', type='str'),
  2332. router_type=dict(default='haproxy-router', type='str'),
  2333. host_network=dict(default=True, type='bool'),
  2334. # external host options
  2335. external_host=dict(default=None, type='str'),
  2336. external_host_vserver=dict(default=None, type='str'),
  2337. external_host_insecure=dict(default=False, type='bool'),
  2338. external_host_partition_path=dict(default=None, type='str'),
  2339. external_host_username=dict(default=None, type='str'),
  2340. external_host_password=dict(default=None, type='str'),
  2341. external_host_private_key=dict(default=None, type='str'),
  2342. # Metrics
  2343. expose_metrics=dict(default=False, type='bool'),
  2344. metrics_image=dict(default=None, type='str'),
  2345. # Stats
  2346. stats_user=dict(default=None, type='str'),
  2347. stats_password=dict(default=None, type='str'),
  2348. stats_port=dict(default=1936, type='int'),
  2349. # extra
  2350. cacert_file=dict(default=None, type='str'),
  2351. # edits
  2352. edits=dict(default=[], type='list'),
  2353. ),
  2354. mutually_exclusive=[["router_type", "images"]],
  2355. supports_check_mode=True,
  2356. )
  2357. results = Router.run_ansible(module.params, module.check_mode)
  2358. if 'failed' in results:
  2359. module.fail_json(**results)
  2360. module.exit_json(**results)
  2361. if __name__ == '__main__':
  2362. main()
  2363. # -*- -*- -*- End included fragment: ansible/oadm_router.py -*- -*- -*-