oc_adm_registry.py 94 KB

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