oc_adm_registry.py 94 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807
  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: '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.ops.openshift.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. versions_dict[tech + '_numeric'] = version[1:].split('+')[0]
  1168. # "v3.3.0.33" is what we have, we want "3.3"
  1169. versions_dict[tech + '_short'] = version[1:4]
  1170. return versions_dict
  1171. @staticmethod
  1172. def openshift_installed():
  1173. ''' check if openshift is installed '''
  1174. import rpm
  1175. transaction_set = rpm.TransactionSet()
  1176. rpmquery = transaction_set.dbMatch("name", "atomic-openshift")
  1177. return rpmquery.count() > 0
  1178. # Disabling too-many-branches. This is a yaml dictionary comparison function
  1179. # pylint: disable=too-many-branches,too-many-return-statements,too-many-statements
  1180. @staticmethod
  1181. def check_def_equal(user_def, result_def, skip_keys=None, debug=False):
  1182. ''' Given a user defined definition, compare it with the results given back by our query. '''
  1183. # Currently these values are autogenerated and we do not need to check them
  1184. skip = ['metadata', 'status']
  1185. if skip_keys:
  1186. skip.extend(skip_keys)
  1187. for key, value in result_def.items():
  1188. if key in skip:
  1189. continue
  1190. # Both are lists
  1191. if isinstance(value, list):
  1192. if key not in user_def:
  1193. if debug:
  1194. print('User data does not have key [%s]' % key)
  1195. print('User data: %s' % user_def)
  1196. return False
  1197. if not isinstance(user_def[key], list):
  1198. if debug:
  1199. print('user_def[key] is not a list key=[%s] user_def[key]=%s' % (key, user_def[key]))
  1200. return False
  1201. if len(user_def[key]) != len(value):
  1202. if debug:
  1203. print("List lengths are not equal.")
  1204. print("key=[%s]: user_def[%s] != value[%s]" % (key, len(user_def[key]), len(value)))
  1205. print("user_def: %s" % user_def[key])
  1206. print("value: %s" % value)
  1207. return False
  1208. for values in zip(user_def[key], value):
  1209. if isinstance(values[0], dict) and isinstance(values[1], dict):
  1210. if debug:
  1211. print('sending list - list')
  1212. print(type(values[0]))
  1213. print(type(values[1]))
  1214. result = Utils.check_def_equal(values[0], values[1], skip_keys=skip_keys, debug=debug)
  1215. if not result:
  1216. print('list compare returned false')
  1217. return False
  1218. elif value != user_def[key]:
  1219. if debug:
  1220. print('value should be identical')
  1221. print(user_def[key])
  1222. print(value)
  1223. return False
  1224. # recurse on a dictionary
  1225. elif isinstance(value, dict):
  1226. if key not in user_def:
  1227. if debug:
  1228. print("user_def does not have key [%s]" % key)
  1229. return False
  1230. if not isinstance(user_def[key], dict):
  1231. if debug:
  1232. print("dict returned false: not instance of dict")
  1233. return False
  1234. # before passing ensure keys match
  1235. api_values = set(value.keys()) - set(skip)
  1236. user_values = set(user_def[key].keys()) - set(skip)
  1237. if api_values != user_values:
  1238. if debug:
  1239. print("keys are not equal in dict")
  1240. print(user_values)
  1241. print(api_values)
  1242. return False
  1243. result = Utils.check_def_equal(user_def[key], value, skip_keys=skip_keys, debug=debug)
  1244. if not result:
  1245. if debug:
  1246. print("dict returned false")
  1247. print(result)
  1248. return False
  1249. # Verify each key, value pair is the same
  1250. else:
  1251. if key not in user_def or value != user_def[key]:
  1252. if debug:
  1253. print("value not equal; user_def does not have key")
  1254. print(key)
  1255. print(value)
  1256. if key in user_def:
  1257. print(user_def[key])
  1258. return False
  1259. if debug:
  1260. print('returning true')
  1261. return True
  1262. class OpenShiftCLIConfig(object):
  1263. '''Generic Config'''
  1264. def __init__(self, rname, namespace, kubeconfig, options):
  1265. self.kubeconfig = kubeconfig
  1266. self.name = rname
  1267. self.namespace = namespace
  1268. self._options = options
  1269. @property
  1270. def config_options(self):
  1271. ''' return config options '''
  1272. return self._options
  1273. def to_option_list(self, ascommalist=''):
  1274. '''return all options as a string
  1275. if ascommalist is set to the name of a key, and
  1276. the value of that key is a dict, format the dict
  1277. as a list of comma delimited key=value pairs'''
  1278. return self.stringify(ascommalist)
  1279. def stringify(self, ascommalist=''):
  1280. ''' return the options hash as cli params in a string
  1281. if ascommalist is set to the name of a key, and
  1282. the value of that key is a dict, format the dict
  1283. as a list of comma delimited key=value pairs '''
  1284. rval = []
  1285. for key in sorted(self.config_options.keys()):
  1286. data = self.config_options[key]
  1287. if data['include'] \
  1288. and (data['value'] is not None or isinstance(data['value'], int)):
  1289. if key == ascommalist:
  1290. val = ','.join(['{}={}'.format(kk, vv) for kk, vv in sorted(data['value'].items())])
  1291. else:
  1292. val = data['value']
  1293. rval.append('--{}={}'.format(key.replace('_', '-'), val))
  1294. return rval
  1295. # -*- -*- -*- End included fragment: lib/base.py -*- -*- -*-
  1296. # -*- -*- -*- Begin included fragment: lib/deploymentconfig.py -*- -*- -*-
  1297. # pylint: disable=too-many-public-methods
  1298. class DeploymentConfig(Yedit):
  1299. ''' Class to model an openshift DeploymentConfig'''
  1300. default_deployment_config = '''
  1301. apiVersion: v1
  1302. kind: DeploymentConfig
  1303. metadata:
  1304. name: default_dc
  1305. namespace: default
  1306. spec:
  1307. replicas: 0
  1308. selector:
  1309. default_dc: default_dc
  1310. strategy:
  1311. resources: {}
  1312. rollingParams:
  1313. intervalSeconds: 1
  1314. maxSurge: 0
  1315. maxUnavailable: 25%
  1316. timeoutSeconds: 600
  1317. updatePercent: -25
  1318. updatePeriodSeconds: 1
  1319. type: Rolling
  1320. template:
  1321. metadata:
  1322. spec:
  1323. containers:
  1324. - env:
  1325. - name: default
  1326. value: default
  1327. image: default
  1328. imagePullPolicy: IfNotPresent
  1329. name: default_dc
  1330. ports:
  1331. - containerPort: 8000
  1332. hostPort: 8000
  1333. protocol: TCP
  1334. name: default_port
  1335. resources: {}
  1336. terminationMessagePath: /dev/termination-log
  1337. dnsPolicy: ClusterFirst
  1338. hostNetwork: true
  1339. nodeSelector:
  1340. type: compute
  1341. restartPolicy: Always
  1342. securityContext: {}
  1343. serviceAccount: default
  1344. serviceAccountName: default
  1345. terminationGracePeriodSeconds: 30
  1346. triggers:
  1347. - type: ConfigChange
  1348. '''
  1349. replicas_path = "spec.replicas"
  1350. env_path = "spec.template.spec.containers[0].env"
  1351. volumes_path = "spec.template.spec.volumes"
  1352. container_path = "spec.template.spec.containers"
  1353. volume_mounts_path = "spec.template.spec.containers[0].volumeMounts"
  1354. def __init__(self, content=None):
  1355. ''' Constructor for deploymentconfig '''
  1356. if not content:
  1357. content = DeploymentConfig.default_deployment_config
  1358. super(DeploymentConfig, self).__init__(content=content)
  1359. def add_env_value(self, key, value):
  1360. ''' add key, value pair to env array '''
  1361. rval = False
  1362. env = self.get_env_vars()
  1363. if env:
  1364. env.append({'name': key, 'value': value})
  1365. rval = True
  1366. else:
  1367. result = self.put(DeploymentConfig.env_path, {'name': key, 'value': value})
  1368. rval = result[0]
  1369. return rval
  1370. def exists_env_value(self, key, value):
  1371. ''' return whether a key, value pair exists '''
  1372. results = self.get_env_vars()
  1373. if not results:
  1374. return False
  1375. for result in results:
  1376. if result['name'] == key and result['value'] == value:
  1377. return True
  1378. return False
  1379. def exists_env_key(self, key):
  1380. ''' return whether a key, value pair exists '''
  1381. results = self.get_env_vars()
  1382. if not results:
  1383. return False
  1384. for result in results:
  1385. if result['name'] == key:
  1386. return True
  1387. return False
  1388. def get_env_var(self, key):
  1389. '''return a environment variables '''
  1390. results = self.get(DeploymentConfig.env_path) or []
  1391. if not results:
  1392. return None
  1393. for env_var in results:
  1394. if env_var['name'] == key:
  1395. return env_var
  1396. return None
  1397. def get_env_vars(self):
  1398. '''return a environment variables '''
  1399. return self.get(DeploymentConfig.env_path) or []
  1400. def delete_env_var(self, keys):
  1401. '''delete a list of keys '''
  1402. if not isinstance(keys, list):
  1403. keys = [keys]
  1404. env_vars_array = self.get_env_vars()
  1405. modified = False
  1406. idx = None
  1407. for key in keys:
  1408. for env_idx, env_var in enumerate(env_vars_array):
  1409. if env_var['name'] == key:
  1410. idx = env_idx
  1411. break
  1412. if idx:
  1413. modified = True
  1414. del env_vars_array[idx]
  1415. if modified:
  1416. return True
  1417. return False
  1418. def update_env_var(self, key, value):
  1419. '''place an env in the env var list'''
  1420. env_vars_array = self.get_env_vars()
  1421. idx = None
  1422. for env_idx, env_var in enumerate(env_vars_array):
  1423. if env_var['name'] == key:
  1424. idx = env_idx
  1425. break
  1426. if idx:
  1427. env_vars_array[idx]['value'] = value
  1428. else:
  1429. self.add_env_value(key, value)
  1430. return True
  1431. def exists_volume_mount(self, volume_mount):
  1432. ''' return whether a volume mount exists '''
  1433. exist_volume_mounts = self.get_volume_mounts()
  1434. if not exist_volume_mounts:
  1435. return False
  1436. volume_mount_found = False
  1437. for exist_volume_mount in exist_volume_mounts:
  1438. if exist_volume_mount['name'] == volume_mount['name']:
  1439. volume_mount_found = True
  1440. break
  1441. return volume_mount_found
  1442. def exists_volume(self, volume):
  1443. ''' return whether a volume exists '''
  1444. exist_volumes = self.get_volumes()
  1445. volume_found = False
  1446. for exist_volume in exist_volumes:
  1447. if exist_volume['name'] == volume['name']:
  1448. volume_found = True
  1449. break
  1450. return volume_found
  1451. def find_volume_by_name(self, volume, mounts=False):
  1452. ''' return the index of a volume '''
  1453. volumes = []
  1454. if mounts:
  1455. volumes = self.get_volume_mounts()
  1456. else:
  1457. volumes = self.get_volumes()
  1458. for exist_volume in volumes:
  1459. if exist_volume['name'] == volume['name']:
  1460. return exist_volume
  1461. return None
  1462. def get_replicas(self):
  1463. ''' return replicas setting '''
  1464. return self.get(DeploymentConfig.replicas_path)
  1465. def get_volume_mounts(self):
  1466. '''return volume mount information '''
  1467. return self.get_volumes(mounts=True)
  1468. def get_volumes(self, mounts=False):
  1469. '''return volume mount information '''
  1470. if mounts:
  1471. return self.get(DeploymentConfig.volume_mounts_path) or []
  1472. return self.get(DeploymentConfig.volumes_path) or []
  1473. def delete_volume_by_name(self, volume):
  1474. '''delete a volume '''
  1475. modified = False
  1476. exist_volume_mounts = self.get_volume_mounts()
  1477. exist_volumes = self.get_volumes()
  1478. del_idx = None
  1479. for idx, exist_volume in enumerate(exist_volumes):
  1480. if 'name' in exist_volume and exist_volume['name'] == volume['name']:
  1481. del_idx = idx
  1482. break
  1483. if del_idx != None:
  1484. del exist_volumes[del_idx]
  1485. modified = True
  1486. del_idx = None
  1487. for idx, exist_volume_mount in enumerate(exist_volume_mounts):
  1488. if 'name' in exist_volume_mount and exist_volume_mount['name'] == volume['name']:
  1489. del_idx = idx
  1490. break
  1491. if del_idx != None:
  1492. del exist_volume_mounts[idx]
  1493. modified = True
  1494. return modified
  1495. def add_volume_mount(self, volume_mount):
  1496. ''' add a volume or volume mount to the proper location '''
  1497. exist_volume_mounts = self.get_volume_mounts()
  1498. if not exist_volume_mounts and volume_mount:
  1499. self.put(DeploymentConfig.volume_mounts_path, [volume_mount])
  1500. else:
  1501. exist_volume_mounts.append(volume_mount)
  1502. def add_volume(self, volume):
  1503. ''' add a volume or volume mount to the proper location '''
  1504. exist_volumes = self.get_volumes()
  1505. if not volume:
  1506. return
  1507. if not exist_volumes:
  1508. self.put(DeploymentConfig.volumes_path, [volume])
  1509. else:
  1510. exist_volumes.append(volume)
  1511. def update_replicas(self, replicas):
  1512. ''' update replicas value '''
  1513. self.put(DeploymentConfig.replicas_path, replicas)
  1514. def update_volume(self, volume):
  1515. '''place an env in the env var list'''
  1516. exist_volumes = self.get_volumes()
  1517. if not volume:
  1518. return False
  1519. # update the volume
  1520. update_idx = None
  1521. for idx, exist_vol in enumerate(exist_volumes):
  1522. if exist_vol['name'] == volume['name']:
  1523. update_idx = idx
  1524. break
  1525. if update_idx != None:
  1526. exist_volumes[update_idx] = volume
  1527. else:
  1528. self.add_volume(volume)
  1529. return True
  1530. def update_volume_mount(self, volume_mount):
  1531. '''place an env in the env var list'''
  1532. modified = False
  1533. exist_volume_mounts = self.get_volume_mounts()
  1534. if not volume_mount:
  1535. return False
  1536. # update the volume mount
  1537. for exist_vol_mount in exist_volume_mounts:
  1538. if exist_vol_mount['name'] == volume_mount['name']:
  1539. if 'mountPath' in exist_vol_mount and \
  1540. str(exist_vol_mount['mountPath']) != str(volume_mount['mountPath']):
  1541. exist_vol_mount['mountPath'] = volume_mount['mountPath']
  1542. modified = True
  1543. break
  1544. if not modified:
  1545. self.add_volume_mount(volume_mount)
  1546. modified = True
  1547. return modified
  1548. def needs_update_volume(self, volume, volume_mount):
  1549. ''' verify a volume update is needed '''
  1550. exist_volume = self.find_volume_by_name(volume)
  1551. exist_volume_mount = self.find_volume_by_name(volume, mounts=True)
  1552. results = []
  1553. results.append(exist_volume['name'] == volume['name'])
  1554. if 'secret' in volume:
  1555. results.append('secret' in exist_volume)
  1556. results.append(exist_volume['secret']['secretName'] == volume['secret']['secretName'])
  1557. results.append(exist_volume_mount['name'] == volume_mount['name'])
  1558. results.append(exist_volume_mount['mountPath'] == volume_mount['mountPath'])
  1559. elif 'emptyDir' in volume:
  1560. results.append(exist_volume_mount['name'] == volume['name'])
  1561. results.append(exist_volume_mount['mountPath'] == volume_mount['mountPath'])
  1562. elif 'persistentVolumeClaim' in volume:
  1563. pvc = 'persistentVolumeClaim'
  1564. results.append(pvc in exist_volume)
  1565. if results[-1]:
  1566. results.append(exist_volume[pvc]['claimName'] == volume[pvc]['claimName'])
  1567. if 'claimSize' in volume[pvc]:
  1568. results.append(exist_volume[pvc]['claimSize'] == volume[pvc]['claimSize'])
  1569. elif 'hostpath' in volume:
  1570. results.append('hostPath' in exist_volume)
  1571. results.append(exist_volume['hostPath']['path'] == volume_mount['mountPath'])
  1572. return not all(results)
  1573. def needs_update_replicas(self, replicas):
  1574. ''' verify whether a replica update is needed '''
  1575. current_reps = self.get(DeploymentConfig.replicas_path)
  1576. return not current_reps == replicas
  1577. # -*- -*- -*- End included fragment: lib/deploymentconfig.py -*- -*- -*-
  1578. # -*- -*- -*- Begin included fragment: lib/secret.py -*- -*- -*-
  1579. # pylint: disable=too-many-instance-attributes
  1580. class SecretConfig(object):
  1581. ''' Handle secret options '''
  1582. # pylint: disable=too-many-arguments
  1583. def __init__(self,
  1584. sname,
  1585. namespace,
  1586. kubeconfig,
  1587. secrets=None,
  1588. stype=None,
  1589. annotations=None):
  1590. ''' constructor for handling secret options '''
  1591. self.kubeconfig = kubeconfig
  1592. self.name = sname
  1593. self.type = stype
  1594. self.namespace = namespace
  1595. self.secrets = secrets
  1596. self.annotations = annotations
  1597. self.data = {}
  1598. self.create_dict()
  1599. def create_dict(self):
  1600. ''' assign the correct properties for a secret dict '''
  1601. self.data['apiVersion'] = 'v1'
  1602. self.data['kind'] = 'Secret'
  1603. self.data['type'] = self.type
  1604. self.data['metadata'] = {}
  1605. self.data['metadata']['name'] = self.name
  1606. self.data['metadata']['namespace'] = self.namespace
  1607. self.data['data'] = {}
  1608. if self.secrets:
  1609. for key, value in self.secrets.items():
  1610. self.data['data'][key] = value
  1611. if self.annotations:
  1612. self.data['metadata']['annotations'] = self.annotations
  1613. # pylint: disable=too-many-instance-attributes
  1614. class Secret(Yedit):
  1615. ''' Class to wrap the oc command line tools '''
  1616. secret_path = "data"
  1617. kind = 'secret'
  1618. def __init__(self, content):
  1619. '''secret constructor'''
  1620. super(Secret, self).__init__(content=content)
  1621. self._secrets = None
  1622. @property
  1623. def secrets(self):
  1624. '''secret property getter'''
  1625. if self._secrets is None:
  1626. self._secrets = self.get_secrets()
  1627. return self._secrets
  1628. @secrets.setter
  1629. def secrets(self):
  1630. '''secret property setter'''
  1631. if self._secrets is None:
  1632. self._secrets = self.get_secrets()
  1633. return self._secrets
  1634. def get_secrets(self):
  1635. ''' returns all of the defined secrets '''
  1636. return self.get(Secret.secret_path) or {}
  1637. def add_secret(self, key, value):
  1638. ''' add a secret '''
  1639. if self.secrets:
  1640. self.secrets[key] = value
  1641. else:
  1642. self.put(Secret.secret_path, {key: value})
  1643. return True
  1644. def delete_secret(self, key):
  1645. ''' delete secret'''
  1646. try:
  1647. del self.secrets[key]
  1648. except KeyError as _:
  1649. return False
  1650. return True
  1651. def find_secret(self, key):
  1652. ''' find secret'''
  1653. rval = None
  1654. try:
  1655. rval = self.secrets[key]
  1656. except KeyError as _:
  1657. return None
  1658. return {'key': key, 'value': rval}
  1659. def update_secret(self, key, value):
  1660. ''' update a secret'''
  1661. if key in self.secrets:
  1662. self.secrets[key] = value
  1663. else:
  1664. self.add_secret(key, value)
  1665. return True
  1666. # -*- -*- -*- End included fragment: lib/secret.py -*- -*- -*-
  1667. # -*- -*- -*- Begin included fragment: lib/service.py -*- -*- -*-
  1668. # pylint: disable=too-many-instance-attributes
  1669. class ServiceConfig(object):
  1670. ''' Handle service options '''
  1671. # pylint: disable=too-many-arguments
  1672. def __init__(self,
  1673. sname,
  1674. namespace,
  1675. ports,
  1676. annotations=None,
  1677. selector=None,
  1678. labels=None,
  1679. cluster_ip=None,
  1680. portal_ip=None,
  1681. session_affinity=None,
  1682. service_type=None,
  1683. external_ips=None):
  1684. ''' constructor for handling service options '''
  1685. self.name = sname
  1686. self.namespace = namespace
  1687. self.ports = ports
  1688. self.annotations = annotations
  1689. self.selector = selector
  1690. self.labels = labels
  1691. self.cluster_ip = cluster_ip
  1692. self.portal_ip = portal_ip
  1693. self.session_affinity = session_affinity
  1694. self.service_type = service_type
  1695. self.external_ips = external_ips
  1696. self.data = {}
  1697. self.create_dict()
  1698. def create_dict(self):
  1699. ''' instantiates a service dict '''
  1700. self.data['apiVersion'] = 'v1'
  1701. self.data['kind'] = 'Service'
  1702. self.data['metadata'] = {}
  1703. self.data['metadata']['name'] = self.name
  1704. self.data['metadata']['namespace'] = self.namespace
  1705. if self.labels:
  1706. self.data['metadata']['labels'] = {}
  1707. for lab, lab_value in self.labels.items():
  1708. self.data['metadata']['labels'][lab] = lab_value
  1709. if self.annotations:
  1710. self.data['metadata']['annotations'] = self.annotations
  1711. self.data['spec'] = {}
  1712. if self.ports:
  1713. self.data['spec']['ports'] = self.ports
  1714. else:
  1715. self.data['spec']['ports'] = []
  1716. if self.selector:
  1717. self.data['spec']['selector'] = self.selector
  1718. self.data['spec']['sessionAffinity'] = self.session_affinity or 'None'
  1719. if self.cluster_ip:
  1720. self.data['spec']['clusterIP'] = self.cluster_ip
  1721. if self.portal_ip:
  1722. self.data['spec']['portalIP'] = self.portal_ip
  1723. if self.service_type:
  1724. self.data['spec']['type'] = self.service_type
  1725. if self.external_ips:
  1726. self.data['spec']['externalIPs'] = self.external_ips
  1727. # pylint: disable=too-many-instance-attributes,too-many-public-methods
  1728. class Service(Yedit):
  1729. ''' Class to model the oc service object '''
  1730. port_path = "spec.ports"
  1731. portal_ip = "spec.portalIP"
  1732. cluster_ip = "spec.clusterIP"
  1733. selector_path = 'spec.selector'
  1734. kind = 'Service'
  1735. external_ips = "spec.externalIPs"
  1736. def __init__(self, content):
  1737. '''Service constructor'''
  1738. super(Service, self).__init__(content=content)
  1739. def get_ports(self):
  1740. ''' get a list of ports '''
  1741. return self.get(Service.port_path) or []
  1742. def get_selector(self):
  1743. ''' get the service selector'''
  1744. return self.get(Service.selector_path) or {}
  1745. def add_ports(self, inc_ports):
  1746. ''' add a port object to the ports list '''
  1747. if not isinstance(inc_ports, list):
  1748. inc_ports = [inc_ports]
  1749. ports = self.get_ports()
  1750. if not ports:
  1751. self.put(Service.port_path, inc_ports)
  1752. else:
  1753. ports.extend(inc_ports)
  1754. return True
  1755. def find_ports(self, inc_port):
  1756. ''' find a specific port '''
  1757. for port in self.get_ports():
  1758. if port['port'] == inc_port['port']:
  1759. return port
  1760. return None
  1761. def delete_ports(self, inc_ports):
  1762. ''' remove a port from a service '''
  1763. if not isinstance(inc_ports, list):
  1764. inc_ports = [inc_ports]
  1765. ports = self.get(Service.port_path) or []
  1766. if not ports:
  1767. return True
  1768. removed = False
  1769. for inc_port in inc_ports:
  1770. port = self.find_ports(inc_port)
  1771. if port:
  1772. ports.remove(port)
  1773. removed = True
  1774. return removed
  1775. def add_cluster_ip(self, sip):
  1776. '''add cluster ip'''
  1777. self.put(Service.cluster_ip, sip)
  1778. def add_portal_ip(self, pip):
  1779. '''add cluster ip'''
  1780. self.put(Service.portal_ip, pip)
  1781. def get_external_ips(self):
  1782. ''' get a list of external_ips '''
  1783. return self.get(Service.external_ips) or []
  1784. def add_external_ips(self, inc_external_ips):
  1785. ''' add an external_ip to the external_ips list '''
  1786. if not isinstance(inc_external_ips, list):
  1787. inc_external_ips = [inc_external_ips]
  1788. external_ips = self.get_external_ips()
  1789. if not external_ips:
  1790. self.put(Service.external_ips, inc_external_ips)
  1791. else:
  1792. external_ips.extend(inc_external_ips)
  1793. return True
  1794. def find_external_ips(self, inc_external_ip):
  1795. ''' find a specific external IP '''
  1796. val = None
  1797. try:
  1798. idx = self.get_external_ips().index(inc_external_ip)
  1799. val = self.get_external_ips()[idx]
  1800. except ValueError:
  1801. pass
  1802. return val
  1803. def delete_external_ips(self, inc_external_ips):
  1804. ''' remove an external IP from a service '''
  1805. if not isinstance(inc_external_ips, list):
  1806. inc_external_ips = [inc_external_ips]
  1807. external_ips = self.get(Service.external_ips) or []
  1808. if not external_ips:
  1809. return True
  1810. removed = False
  1811. for inc_external_ip in inc_external_ips:
  1812. external_ip = self.find_external_ips(inc_external_ip)
  1813. if external_ip:
  1814. external_ips.remove(external_ip)
  1815. removed = True
  1816. return removed
  1817. # -*- -*- -*- End included fragment: lib/service.py -*- -*- -*-
  1818. # -*- -*- -*- Begin included fragment: lib/volume.py -*- -*- -*-
  1819. class Volume(object):
  1820. ''' Class to represent an openshift volume object'''
  1821. volume_mounts_path = {"pod": "spec.containers[0].volumeMounts",
  1822. "dc": "spec.template.spec.containers[0].volumeMounts",
  1823. "rc": "spec.template.spec.containers[0].volumeMounts",
  1824. }
  1825. volumes_path = {"pod": "spec.volumes",
  1826. "dc": "spec.template.spec.volumes",
  1827. "rc": "spec.template.spec.volumes",
  1828. }
  1829. @staticmethod
  1830. def create_volume_structure(volume_info):
  1831. ''' return a properly structured volume '''
  1832. volume_mount = None
  1833. volume = {'name': volume_info['name']}
  1834. volume_type = volume_info['type'].lower()
  1835. if volume_type == 'secret':
  1836. volume['secret'] = {}
  1837. volume[volume_info['type']] = {'secretName': volume_info['secret_name']}
  1838. volume_mount = {'mountPath': volume_info['path'],
  1839. 'name': volume_info['name']}
  1840. elif volume_type == 'emptydir':
  1841. volume['emptyDir'] = {}
  1842. volume_mount = {'mountPath': volume_info['path'],
  1843. 'name': volume_info['name']}
  1844. elif volume_type == 'pvc' or volume_type == 'persistentvolumeclaim':
  1845. volume['persistentVolumeClaim'] = {}
  1846. volume['persistentVolumeClaim']['claimName'] = volume_info['claimName']
  1847. volume['persistentVolumeClaim']['claimSize'] = volume_info['claimSize']
  1848. elif volume_type == 'hostpath':
  1849. volume['hostPath'] = {}
  1850. volume['hostPath']['path'] = volume_info['path']
  1851. elif volume_type == 'configmap':
  1852. volume['configMap'] = {}
  1853. volume['configMap']['name'] = volume_info['configmap_name']
  1854. volume_mount = {'mountPath': volume_info['path'],
  1855. 'name': volume_info['name']}
  1856. return (volume, volume_mount)
  1857. # -*- -*- -*- End included fragment: lib/volume.py -*- -*- -*-
  1858. # -*- -*- -*- Begin included fragment: class/oc_version.py -*- -*- -*-
  1859. # pylint: disable=too-many-instance-attributes
  1860. class OCVersion(OpenShiftCLI):
  1861. ''' Class to wrap the oc command line tools '''
  1862. # pylint allows 5
  1863. # pylint: disable=too-many-arguments
  1864. def __init__(self,
  1865. config,
  1866. debug):
  1867. ''' Constructor for OCVersion '''
  1868. super(OCVersion, self).__init__(None, config)
  1869. self.debug = debug
  1870. def get(self):
  1871. '''get and return version information '''
  1872. results = {}
  1873. version_results = self._version()
  1874. if version_results['returncode'] == 0:
  1875. filtered_vers = Utils.filter_versions(version_results['results'])
  1876. custom_vers = Utils.add_custom_versions(filtered_vers)
  1877. results['returncode'] = version_results['returncode']
  1878. results.update(filtered_vers)
  1879. results.update(custom_vers)
  1880. return results
  1881. raise OpenShiftCLIError('Problem detecting openshift version.')
  1882. @staticmethod
  1883. def run_ansible(params):
  1884. '''run the idempotent ansible code'''
  1885. oc_version = OCVersion(params['kubeconfig'], params['debug'])
  1886. if params['state'] == 'list':
  1887. #pylint: disable=protected-access
  1888. result = oc_version.get()
  1889. return {'state': params['state'],
  1890. 'results': result,
  1891. 'changed': False}
  1892. # -*- -*- -*- End included fragment: class/oc_version.py -*- -*- -*-
  1893. # -*- -*- -*- Begin included fragment: class/oc_adm_registry.py -*- -*- -*-
  1894. class RegistryException(Exception):
  1895. ''' Registry Exception Class '''
  1896. pass
  1897. class RegistryConfig(OpenShiftCLIConfig):
  1898. ''' RegistryConfig is a DTO for the registry. '''
  1899. def __init__(self, rname, namespace, kubeconfig, registry_options):
  1900. super(RegistryConfig, self).__init__(rname, namespace, kubeconfig, registry_options)
  1901. class Registry(OpenShiftCLI):
  1902. ''' Class to wrap the oc command line tools '''
  1903. volume_mount_path = 'spec.template.spec.containers[0].volumeMounts'
  1904. volume_path = 'spec.template.spec.volumes'
  1905. env_path = 'spec.template.spec.containers[0].env'
  1906. def __init__(self,
  1907. registry_config,
  1908. verbose=False):
  1909. ''' Constructor for Registry
  1910. a registry consists of 3 or more parts
  1911. - dc/docker-registry
  1912. - svc/docker-registry
  1913. Parameters:
  1914. :registry_config:
  1915. :verbose:
  1916. '''
  1917. super(Registry, self).__init__(registry_config.namespace, registry_config.kubeconfig, verbose)
  1918. self.version = OCVersion(registry_config.kubeconfig, verbose)
  1919. self.svc_ip = None
  1920. self.portal_ip = None
  1921. self.config = registry_config
  1922. self.verbose = verbose
  1923. self.registry_parts = [{'kind': 'dc', 'name': self.config.name},
  1924. {'kind': 'svc', 'name': self.config.name},
  1925. ]
  1926. self.__prepared_registry = None
  1927. self.volume_mounts = []
  1928. self.volumes = []
  1929. if self.config.config_options['volume_mounts']['value']:
  1930. for volume in self.config.config_options['volume_mounts']['value']:
  1931. volume_info = {'secret_name': volume.get('secret_name', None),
  1932. 'name': volume.get('name', None),
  1933. 'type': volume.get('type', None),
  1934. 'path': volume.get('path', None),
  1935. 'claimName': volume.get('claim_name', None),
  1936. 'claimSize': volume.get('claim_size', None),
  1937. }
  1938. vol, vol_mount = Volume.create_volume_structure(volume_info)
  1939. self.volumes.append(vol)
  1940. self.volume_mounts.append(vol_mount)
  1941. self.dconfig = None
  1942. self.svc = None
  1943. @property
  1944. def deploymentconfig(self):
  1945. ''' deploymentconfig property '''
  1946. return self.dconfig
  1947. @deploymentconfig.setter
  1948. def deploymentconfig(self, config):
  1949. ''' setter for deploymentconfig property '''
  1950. self.dconfig = config
  1951. @property
  1952. def service(self):
  1953. ''' service property '''
  1954. return self.svc
  1955. @service.setter
  1956. def service(self, config):
  1957. ''' setter for service property '''
  1958. self.svc = config
  1959. @property
  1960. def prepared_registry(self):
  1961. ''' prepared_registry property '''
  1962. if not self.__prepared_registry:
  1963. results = self.prepare_registry()
  1964. if not results or ('returncode' in results and results['returncode'] != 0):
  1965. raise RegistryException('Could not perform registry preparation. {}'.format(results))
  1966. self.__prepared_registry = results
  1967. return self.__prepared_registry
  1968. @prepared_registry.setter
  1969. def prepared_registry(self, data):
  1970. ''' setter method for prepared_registry attribute '''
  1971. self.__prepared_registry = data
  1972. def get(self):
  1973. ''' return the self.registry_parts '''
  1974. self.deploymentconfig = None
  1975. self.service = None
  1976. rval = 0
  1977. for part in self.registry_parts:
  1978. result = self._get(part['kind'], name=part['name'])
  1979. if result['returncode'] == 0 and part['kind'] == 'dc':
  1980. self.deploymentconfig = DeploymentConfig(result['results'][0])
  1981. elif result['returncode'] == 0 and part['kind'] == 'svc':
  1982. self.service = Service(result['results'][0])
  1983. if result['returncode'] != 0:
  1984. rval = result['returncode']
  1985. return {'returncode': rval, 'deploymentconfig': self.deploymentconfig, 'service': self.service}
  1986. def exists(self):
  1987. '''does the object exist?'''
  1988. if self.deploymentconfig and self.service:
  1989. return True
  1990. return False
  1991. def delete(self, complete=True):
  1992. '''return all pods '''
  1993. parts = []
  1994. for part in self.registry_parts:
  1995. if not complete and part['kind'] == 'svc':
  1996. continue
  1997. parts.append(self._delete(part['kind'], part['name']))
  1998. # Clean up returned results
  1999. rval = 0
  2000. for part in parts:
  2001. # pylint: disable=invalid-sequence-index
  2002. if 'returncode' in part and part['returncode'] != 0:
  2003. rval = part['returncode']
  2004. return {'returncode': rval, 'results': parts}
  2005. def prepare_registry(self):
  2006. ''' prepare a registry for instantiation '''
  2007. options = self.config.to_option_list(ascommalist='labels')
  2008. cmd = ['registry']
  2009. cmd.extend(options)
  2010. cmd.extend(['--dry-run=True', '-o', 'json'])
  2011. results = self.openshift_cmd(cmd, oadm=True, output=True, output_type='json')
  2012. # probably need to parse this
  2013. # pylint thinks results is a string
  2014. # pylint: disable=no-member
  2015. if results['returncode'] != 0 and 'items' not in results['results']:
  2016. raise RegistryException('Could not perform registry preparation. {}'.format(results))
  2017. service = None
  2018. deploymentconfig = None
  2019. # pylint: disable=invalid-sequence-index
  2020. for res in results['results']['items']:
  2021. if res['kind'] == 'DeploymentConfig':
  2022. deploymentconfig = DeploymentConfig(res)
  2023. elif res['kind'] == 'Service':
  2024. service = Service(res)
  2025. # Verify we got a service and a deploymentconfig
  2026. if not service or not deploymentconfig:
  2027. return results
  2028. # results will need to get parsed here and modifications added
  2029. deploymentconfig = DeploymentConfig(self.add_modifications(deploymentconfig))
  2030. # modify service ip
  2031. if self.svc_ip:
  2032. service.put('spec.clusterIP', self.svc_ip)
  2033. if self.portal_ip:
  2034. service.put('spec.portalIP', self.portal_ip)
  2035. # the dry-run doesn't apply the selector correctly
  2036. if self.service:
  2037. service.put('spec.selector', self.service.get_selector())
  2038. # need to create the service and the deploymentconfig
  2039. service_file = Utils.create_tmp_file_from_contents('service', service.yaml_dict)
  2040. deployment_file = Utils.create_tmp_file_from_contents('deploymentconfig', deploymentconfig.yaml_dict)
  2041. return {"service": service,
  2042. "service_file": service_file,
  2043. "service_update": False,
  2044. "deployment": deploymentconfig,
  2045. "deployment_file": deployment_file,
  2046. "deployment_update": False}
  2047. def create(self):
  2048. '''Create a registry'''
  2049. results = []
  2050. self.needs_update()
  2051. # if the object is none, then we need to create it
  2052. # if the object needs an update, then we should call replace
  2053. # Handle the deploymentconfig
  2054. if self.deploymentconfig is None:
  2055. results.append(self._create(self.prepared_registry['deployment_file']))
  2056. elif self.prepared_registry['deployment_update']:
  2057. results.append(self._replace(self.prepared_registry['deployment_file']))
  2058. # Handle the service
  2059. if self.service is None:
  2060. results.append(self._create(self.prepared_registry['service_file']))
  2061. elif self.prepared_registry['service_update']:
  2062. results.append(self._replace(self.prepared_registry['service_file']))
  2063. # Clean up returned results
  2064. rval = 0
  2065. for result in results:
  2066. # pylint: disable=invalid-sequence-index
  2067. if 'returncode' in result and result['returncode'] != 0:
  2068. rval = result['returncode']
  2069. return {'returncode': rval, 'results': results}
  2070. def update(self):
  2071. '''run update for the registry. This performs a replace if required'''
  2072. # Store the current service IP
  2073. if self.service:
  2074. svcip = self.service.get('spec.clusterIP')
  2075. if svcip:
  2076. self.svc_ip = svcip
  2077. portip = self.service.get('spec.portalIP')
  2078. if portip:
  2079. self.portal_ip = portip
  2080. results = []
  2081. if self.prepared_registry['deployment_update']:
  2082. results.append(self._replace(self.prepared_registry['deployment_file']))
  2083. if self.prepared_registry['service_update']:
  2084. results.append(self._replace(self.prepared_registry['service_file']))
  2085. # Clean up returned results
  2086. rval = 0
  2087. for result in results:
  2088. if result['returncode'] != 0:
  2089. rval = result['returncode']
  2090. return {'returncode': rval, 'results': results}
  2091. def add_modifications(self, deploymentconfig):
  2092. ''' update a deployment config with changes '''
  2093. # The environment variable for REGISTRY_HTTP_SECRET is autogenerated
  2094. # We should set the generated deploymentconfig to the in memory version
  2095. # the following modifications will overwrite if needed
  2096. if self.deploymentconfig:
  2097. result = self.deploymentconfig.get_env_var('REGISTRY_HTTP_SECRET')
  2098. if result:
  2099. deploymentconfig.update_env_var('REGISTRY_HTTP_SECRET', result['value'])
  2100. # Currently we know that our deployment of a registry requires a few extra modifications
  2101. # Modification 1
  2102. # we need specific environment variables to be set
  2103. for key, value in self.config.config_options['env_vars'].get('value', {}).items():
  2104. if not deploymentconfig.exists_env_key(key):
  2105. deploymentconfig.add_env_value(key, value)
  2106. else:
  2107. deploymentconfig.update_env_var(key, value)
  2108. # Modification 2
  2109. # we need specific volume variables to be set
  2110. for volume in self.volumes:
  2111. deploymentconfig.update_volume(volume)
  2112. for vol_mount in self.volume_mounts:
  2113. deploymentconfig.update_volume_mount(vol_mount)
  2114. # Modification 3
  2115. # Edits
  2116. edit_results = []
  2117. for edit in self.config.config_options['edits'].get('value', []):
  2118. if edit['action'] == 'put':
  2119. edit_results.append(deploymentconfig.put(edit['key'],
  2120. edit['value']))
  2121. if edit['action'] == 'update':
  2122. edit_results.append(deploymentconfig.update(edit['key'],
  2123. edit['value'],
  2124. edit.get('index', None),
  2125. edit.get('curr_value', None)))
  2126. if edit['action'] == 'append':
  2127. edit_results.append(deploymentconfig.append(edit['key'],
  2128. edit['value']))
  2129. if edit_results and not any([res[0] for res in edit_results]):
  2130. return None
  2131. return deploymentconfig.yaml_dict
  2132. def needs_update(self):
  2133. ''' check to see if we need to update '''
  2134. exclude_list = ['clusterIP', 'portalIP', 'type', 'protocol']
  2135. if self.service is None or \
  2136. not Utils.check_def_equal(self.prepared_registry['service'].yaml_dict,
  2137. self.service.yaml_dict,
  2138. exclude_list,
  2139. debug=self.verbose):
  2140. self.prepared_registry['service_update'] = True
  2141. exclude_list = ['dnsPolicy',
  2142. 'terminationGracePeriodSeconds',
  2143. 'restartPolicy', 'timeoutSeconds',
  2144. 'livenessProbe', 'readinessProbe',
  2145. 'terminationMessagePath',
  2146. 'securityContext',
  2147. 'imagePullPolicy',
  2148. 'protocol', # ports.portocol: TCP
  2149. 'type', # strategy: {'type': 'rolling'}
  2150. 'defaultMode', # added on secrets
  2151. 'activeDeadlineSeconds', # added in 1.5 for timeouts
  2152. ]
  2153. if self.deploymentconfig is None or \
  2154. not Utils.check_def_equal(self.prepared_registry['deployment'].yaml_dict,
  2155. self.deploymentconfig.yaml_dict,
  2156. exclude_list,
  2157. debug=self.verbose):
  2158. self.prepared_registry['deployment_update'] = True
  2159. return self.prepared_registry['deployment_update'] or self.prepared_registry['service_update'] or False
  2160. # In the future, we would like to break out each ansible state into a function.
  2161. # pylint: disable=too-many-branches,too-many-return-statements
  2162. @staticmethod
  2163. def run_ansible(params, check_mode):
  2164. '''run idempotent ansible code'''
  2165. registry_options = {'images': {'value': params['images'], 'include': True},
  2166. 'latest_images': {'value': params['latest_images'], 'include': True},
  2167. 'labels': {'value': params['labels'], 'include': True},
  2168. 'ports': {'value': ','.join(params['ports']), 'include': True},
  2169. 'replicas': {'value': params['replicas'], 'include': True},
  2170. 'selector': {'value': params['selector'], 'include': True},
  2171. 'service_account': {'value': params['service_account'], 'include': True},
  2172. 'mount_host': {'value': params['mount_host'], 'include': True},
  2173. 'env_vars': {'value': params['env_vars'], 'include': False},
  2174. 'volume_mounts': {'value': params['volume_mounts'], 'include': False},
  2175. 'edits': {'value': params['edits'], 'include': False},
  2176. 'tls_key': {'value': params['tls_key'], 'include': True},
  2177. 'tls_certificate': {'value': params['tls_certificate'], 'include': True},
  2178. }
  2179. # Do not always pass the daemonset and enforce-quota parameters because they are not understood
  2180. # by old versions of oc.
  2181. # Default value is false. So, it's safe to not pass an explicit false value to oc versions which
  2182. # understand these parameters.
  2183. if params['daemonset']:
  2184. registry_options['daemonset'] = {'value': params['daemonset'], 'include': True}
  2185. if params['enforce_quota']:
  2186. registry_options['enforce_quota'] = {'value': params['enforce_quota'], 'include': True}
  2187. rconfig = RegistryConfig(params['name'],
  2188. params['namespace'],
  2189. params['kubeconfig'],
  2190. registry_options)
  2191. ocregistry = Registry(rconfig, params['debug'])
  2192. api_rval = ocregistry.get()
  2193. state = params['state']
  2194. ########
  2195. # get
  2196. ########
  2197. if state == 'list':
  2198. if api_rval['returncode'] != 0:
  2199. return {'failed': True, 'msg': api_rval}
  2200. return {'changed': False, 'results': api_rval, 'state': state}
  2201. ########
  2202. # Delete
  2203. ########
  2204. if state == 'absent':
  2205. if not ocregistry.exists():
  2206. return {'changed': False, 'state': state}
  2207. if check_mode:
  2208. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'}
  2209. # Unsure as to why this is angry with the return type.
  2210. # pylint: disable=redefined-variable-type
  2211. api_rval = ocregistry.delete()
  2212. if api_rval['returncode'] != 0:
  2213. return {'failed': True, 'msg': api_rval}
  2214. return {'changed': True, 'results': api_rval, 'state': state}
  2215. if state == 'present':
  2216. ########
  2217. # Create
  2218. ########
  2219. if not ocregistry.exists():
  2220. if check_mode:
  2221. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
  2222. api_rval = ocregistry.create()
  2223. if api_rval['returncode'] != 0:
  2224. return {'failed': True, 'msg': api_rval}
  2225. return {'changed': True, 'results': api_rval, 'state': state}
  2226. ########
  2227. # Update
  2228. ########
  2229. if not params['force'] and not ocregistry.needs_update():
  2230. return {'changed': False, 'state': state}
  2231. if check_mode:
  2232. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed an update.'}
  2233. api_rval = ocregistry.update()
  2234. if api_rval['returncode'] != 0:
  2235. return {'failed': True, 'msg': api_rval}
  2236. return {'changed': True, 'results': api_rval, 'state': state}
  2237. return {'failed': True, 'msg': 'Unknown state passed. %s' % state}
  2238. # -*- -*- -*- End included fragment: class/oc_adm_registry.py -*- -*- -*-
  2239. # -*- -*- -*- Begin included fragment: ansible/oc_adm_registry.py -*- -*- -*-
  2240. def main():
  2241. '''
  2242. ansible oc module for registry
  2243. '''
  2244. module = AnsibleModule(
  2245. argument_spec=dict(
  2246. state=dict(default='present', type='str',
  2247. choices=['present', 'absent']),
  2248. debug=dict(default=False, type='bool'),
  2249. namespace=dict(default='default', type='str'),
  2250. name=dict(default=None, required=True, type='str'),
  2251. kubeconfig=dict(default='/etc/origin/master/admin.kubeconfig', type='str'),
  2252. images=dict(default=None, type='str'),
  2253. latest_images=dict(default=False, type='bool'),
  2254. labels=dict(default=None, type='dict'),
  2255. ports=dict(default=['5000'], type='list'),
  2256. replicas=dict(default=1, type='int'),
  2257. selector=dict(default=None, type='str'),
  2258. service_account=dict(default='registry', type='str'),
  2259. mount_host=dict(default=None, type='str'),
  2260. volume_mounts=dict(default=None, type='list'),
  2261. env_vars=dict(default={}, type='dict'),
  2262. edits=dict(default=[], type='list'),
  2263. enforce_quota=dict(default=False, type='bool'),
  2264. force=dict(default=False, type='bool'),
  2265. daemonset=dict(default=False, type='bool'),
  2266. tls_key=dict(default=None, type='str'),
  2267. tls_certificate=dict(default=None, type='str'),
  2268. ),
  2269. supports_check_mode=True,
  2270. )
  2271. results = Registry.run_ansible(module.params, module.check_mode)
  2272. if 'failed' in results:
  2273. module.fail_json(**results)
  2274. module.exit_json(**results)
  2275. if __name__ == '__main__':
  2276. main()
  2277. # -*- -*- -*- End included fragment: ansible/oc_adm_registry.py -*- -*- -*-