oc_adm_registry.py 94 KB

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