oc_adm_router.py 108 KB

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