oc_adm_registry.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. # pylint: skip-file
  2. # flake8: noqa
  3. class RegistryException(Exception):
  4. ''' Registry Exception Class '''
  5. pass
  6. class RegistryConfig(OpenShiftCLIConfig):
  7. ''' RegistryConfig is a DTO for the registry. '''
  8. def __init__(self, rname, namespace, kubeconfig, registry_options):
  9. super(RegistryConfig, self).__init__(rname, namespace, kubeconfig, registry_options)
  10. class Registry(OpenShiftCLI):
  11. ''' Class to wrap the oc command line tools '''
  12. volume_mount_path = 'spec.template.spec.containers[0].volumeMounts'
  13. volume_path = 'spec.template.spec.volumes'
  14. env_path = 'spec.template.spec.containers[0].env'
  15. def __init__(self,
  16. registry_config,
  17. verbose=False):
  18. ''' Constructor for Registry
  19. a registry consists of 3 or more parts
  20. - dc/docker-registry
  21. - svc/docker-registry
  22. Parameters:
  23. :registry_config:
  24. :verbose:
  25. '''
  26. super(Registry, self).__init__(registry_config.namespace, registry_config.kubeconfig, verbose)
  27. self.version = OCVersion(registry_config.kubeconfig, verbose)
  28. self.svc_ip = None
  29. self.portal_ip = None
  30. self.config = registry_config
  31. self.verbose = verbose
  32. self.registry_parts = [{'kind': 'dc', 'name': self.config.name},
  33. {'kind': 'svc', 'name': self.config.name},
  34. ]
  35. self.__prepared_registry = None
  36. self.volume_mounts = []
  37. self.volumes = []
  38. if self.config.config_options['volume_mounts']['value']:
  39. for volume in self.config.config_options['volume_mounts']['value']:
  40. volume_info = {'secret_name': volume.get('secret_name', None),
  41. 'name': volume.get('name', None),
  42. 'type': volume.get('type', None),
  43. 'path': volume.get('path', None),
  44. 'claimName': volume.get('claim_name', None),
  45. 'claimSize': volume.get('claim_size', None),
  46. }
  47. vol, vol_mount = Volume.create_volume_structure(volume_info)
  48. self.volumes.append(vol)
  49. self.volume_mounts.append(vol_mount)
  50. self.dconfig = None
  51. self.svc = None
  52. @property
  53. def deploymentconfig(self):
  54. ''' deploymentconfig property '''
  55. return self.dconfig
  56. @deploymentconfig.setter
  57. def deploymentconfig(self, config):
  58. ''' setter for deploymentconfig property '''
  59. self.dconfig = config
  60. @property
  61. def service(self):
  62. ''' service property '''
  63. return self.svc
  64. @service.setter
  65. def service(self, config):
  66. ''' setter for service property '''
  67. self.svc = config
  68. @property
  69. def prepared_registry(self):
  70. ''' prepared_registry property '''
  71. if not self.__prepared_registry:
  72. results = self.prepare_registry()
  73. if not results:
  74. raise RegistryException('Could not perform registry preparation.')
  75. self.__prepared_registry = results
  76. return self.__prepared_registry
  77. @prepared_registry.setter
  78. def prepared_registry(self, data):
  79. ''' setter method for prepared_registry attribute '''
  80. self.__prepared_registry = data
  81. def get(self):
  82. ''' return the self.registry_parts '''
  83. self.deploymentconfig = None
  84. self.service = None
  85. rval = 0
  86. for part in self.registry_parts:
  87. result = self._get(part['kind'], rname=part['name'])
  88. if result['returncode'] == 0 and part['kind'] == 'dc':
  89. self.deploymentconfig = DeploymentConfig(result['results'][0])
  90. elif result['returncode'] == 0 and part['kind'] == 'svc':
  91. self.service = Yedit(content=result['results'][0])
  92. if result['returncode'] != 0:
  93. rval = result['returncode']
  94. return {'returncode': rval, 'deploymentconfig': self.deploymentconfig, 'service': self.service}
  95. def exists(self):
  96. '''does the object exist?'''
  97. self.get()
  98. if self.deploymentconfig or self.service:
  99. return True
  100. return False
  101. def delete(self, complete=True):
  102. '''return all pods '''
  103. parts = []
  104. for part in self.registry_parts:
  105. if not complete and part['kind'] == 'svc':
  106. continue
  107. parts.append(self._delete(part['kind'], part['name']))
  108. # Clean up returned results
  109. rval = 0
  110. for part in parts:
  111. # pylint: disable=invalid-sequence-index
  112. if 'returncode' in part and part['returncode'] != 0:
  113. rval = part['returncode']
  114. return {'returncode': rval, 'results': parts}
  115. def prepare_registry(self):
  116. ''' prepare a registry for instantiation '''
  117. options = self.config.to_option_list()
  118. cmd = ['registry', '-n', self.config.namespace]
  119. cmd.extend(options)
  120. cmd.extend(['--dry-run=True', '-o', 'json'])
  121. results = self.openshift_cmd(cmd, oadm=True, output=True, output_type='json')
  122. # probably need to parse this
  123. # pylint thinks results is a string
  124. # pylint: disable=no-member
  125. if results['returncode'] != 0 and results['results'].has_key('items'):
  126. return results
  127. service = None
  128. deploymentconfig = None
  129. # pylint: disable=invalid-sequence-index
  130. for res in results['results']['items']:
  131. if res['kind'] == 'DeploymentConfig':
  132. deploymentconfig = DeploymentConfig(res)
  133. elif res['kind'] == 'Service':
  134. service = Service(res)
  135. # Verify we got a service and a deploymentconfig
  136. if not service or not deploymentconfig:
  137. return results
  138. # results will need to get parsed here and modifications added
  139. deploymentconfig = DeploymentConfig(self.add_modifications(deploymentconfig))
  140. # modify service ip
  141. if self.svc_ip:
  142. service.put('spec.clusterIP', self.svc_ip)
  143. if self.portal_ip:
  144. service.put('spec.portalIP', self.portal_ip)
  145. # need to create the service and the deploymentconfig
  146. service_file = Utils.create_tmp_file_from_contents('service', service.yaml_dict)
  147. deployment_file = Utils.create_tmp_file_from_contents('deploymentconfig', deploymentconfig.yaml_dict)
  148. return {"service": service,
  149. "service_file": service_file,
  150. "service_update": False,
  151. "deployment": deploymentconfig,
  152. "deployment_file": deployment_file,
  153. "deployment_update": False}
  154. def create(self):
  155. '''Create a registry'''
  156. results = []
  157. for config_file in ['deployment_file', 'service_file']:
  158. results.append(self._create(self.prepared_registry[config_file]))
  159. # Clean up returned results
  160. rval = 0
  161. for result in results:
  162. # pylint: disable=invalid-sequence-index
  163. if 'returncode' in result and result['returncode'] != 0:
  164. rval = result['returncode']
  165. return {'returncode': rval, 'results': results}
  166. def update(self):
  167. '''run update for the registry. This performs a delete and then create '''
  168. # Store the current service IP
  169. if self.service:
  170. svcip = self.service.get('spec.clusterIP')
  171. if svcip:
  172. self.svc_ip = svcip
  173. portip = self.service.get('spec.portalIP')
  174. if portip:
  175. self.portal_ip = portip
  176. results = []
  177. if self.prepared_registry['deployment_update']:
  178. results.append(self._replace(self.prepared_registry['deployment_file']))
  179. if self.prepared_registry['service_update']:
  180. results.append(self._replace(self.prepared_registry['service_file']))
  181. # Clean up returned results
  182. rval = 0
  183. for result in results:
  184. if result['returncode'] != 0:
  185. rval = result['returncode']
  186. return {'returncode': rval, 'results': results}
  187. def add_modifications(self, deploymentconfig):
  188. ''' update a deployment config with changes '''
  189. # Currently we know that our deployment of a registry requires a few extra modifications
  190. # Modification 1
  191. # we need specific environment variables to be set
  192. for key, value in self.config.config_options['env_vars']['value'].items():
  193. if not deploymentconfig.exists_env_key(key):
  194. deploymentconfig.add_env_value(key, value)
  195. else:
  196. deploymentconfig.update_env_var(key, value)
  197. # Modification 2
  198. # we need specific volume variables to be set
  199. for volume in self.volumes:
  200. deploymentconfig.update_volume(volume)
  201. for vol_mount in self.volume_mounts:
  202. deploymentconfig.update_volume_mount(vol_mount)
  203. # Modification 3
  204. # Edits
  205. edit_results = []
  206. for edit in self.config.config_options['edits'].get('value', []):
  207. if edit['action'] == 'put':
  208. edit_results.append(deploymentconfig.put(edit['key'],
  209. edit['value']))
  210. if edit['action'] == 'update':
  211. edit_results.append(deploymentconfig.update(edit['key'],
  212. edit['value'],
  213. edit.get('index', None),
  214. edit.get('curr_value', None)))
  215. if edit['action'] == 'append':
  216. edit_results.append(deploymentconfig.append(edit['key'],
  217. edit['value']))
  218. if edit_results and not any([res[0] for res in edit_results]):
  219. return None
  220. return deploymentconfig.yaml_dict
  221. def needs_update(self):
  222. ''' check to see if we need to update '''
  223. if not self.service or not self.deploymentconfig:
  224. return True
  225. exclude_list = ['clusterIP', 'portalIP', 'type', 'protocol']
  226. if not Utils.check_def_equal(self.prepared_registry['service'].yaml_dict,
  227. self.service.yaml_dict,
  228. exclude_list,
  229. debug=self.verbose):
  230. self.prepared_registry['service_update'] = True
  231. exclude_list = ['dnsPolicy',
  232. 'terminationGracePeriodSeconds',
  233. 'restartPolicy', 'timeoutSeconds',
  234. 'livenessProbe', 'readinessProbe',
  235. 'terminationMessagePath',
  236. 'securityContext',
  237. 'imagePullPolicy',
  238. 'protocol', # ports.portocol: TCP
  239. 'type', # strategy: {'type': 'rolling'}
  240. 'defaultMode', # added on secrets
  241. 'activeDeadlineSeconds', # added in 1.5 for timeouts
  242. ]
  243. if not Utils.check_def_equal(self.prepared_registry['deployment'].yaml_dict,
  244. self.deploymentconfig.yaml_dict,
  245. exclude_list,
  246. debug=self.verbose):
  247. self.prepared_registry['deployment_update'] = True
  248. return self.prepared_registry['deployment_update'] or self.prepared_registry['service_update'] or False
  249. # In the future, we would like to break out each ansible state into a function.
  250. # pylint: disable=too-many-branches,too-many-return-statements
  251. @staticmethod
  252. def run_ansible(params, check_mode):
  253. '''run idempotent ansible code'''
  254. rconfig = RegistryConfig(params['name'],
  255. params['namespace'],
  256. params['kubeconfig'],
  257. {'images': {'value': params['images'], 'include': True},
  258. 'latest_images': {'value': params['latest_images'], 'include': True},
  259. 'labels': {'value': params['labels'], 'include': True},
  260. 'ports': {'value': ','.join(params['ports']), 'include': True},
  261. 'replicas': {'value': params['replicas'], 'include': True},
  262. 'selector': {'value': params['selector'], 'include': True},
  263. 'service_account': {'value': params['service_account'], 'include': True},
  264. 'mount_host': {'value': params['mount_host'], 'include': True},
  265. 'env_vars': {'value': params['env_vars'], 'include': False},
  266. 'volume_mounts': {'value': params['volume_mounts'], 'include': False},
  267. 'edits': {'value': params['edits'], 'include': False},
  268. 'enforce_quota': {'value': params['enforce_quota'], 'include': True},
  269. 'daemonset': {'value': params['daemonset'], 'include': True},
  270. 'tls_key': {'value': params['tls_key'], 'include': True},
  271. 'tls_certificate': {'value': params['tls_certificate'], 'include': True},
  272. })
  273. ocregistry = Registry(rconfig, params['debug'])
  274. api_rval = ocregistry.get()
  275. state = params['state']
  276. ########
  277. # get
  278. ########
  279. if state == 'list':
  280. if api_rval['returncode'] != 0:
  281. return {'failed': True, 'msg': api_rval}
  282. return {'changed': False, 'results': api_rval, 'state': state}
  283. ########
  284. # Delete
  285. ########
  286. if state == 'absent':
  287. if not ocregistry.exists():
  288. return {'changed': False, 'state': state}
  289. if check_mode:
  290. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'}
  291. # Unsure as to why this is angry with the return type.
  292. # pylint: disable=redefined-variable-type
  293. api_rval = ocregistry.delete()
  294. if api_rval['returncode'] != 0:
  295. return {'failed': True, 'msg': api_rval}
  296. return {'changed': True, 'results': api_rval, 'state': state}
  297. if state == 'present':
  298. ########
  299. # Create
  300. ########
  301. if not ocregistry.exists():
  302. if check_mode:
  303. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
  304. api_rval = ocregistry.create()
  305. if api_rval['returncode'] != 0:
  306. return {'failed': True, 'msg': api_rval}
  307. return {'changed': True, 'results': api_rval, 'state': state}
  308. ########
  309. # Update
  310. ########
  311. if not params['force'] and not ocregistry.needs_update():
  312. return {'changed': False, 'state': state}
  313. if check_mode:
  314. return {'changed': True, 'msg': 'CHECK_MODE: Would have performed an update.'}
  315. api_rval = ocregistry.update()
  316. if api_rval['returncode'] != 0:
  317. return {'failed': True, 'msg': api_rval}
  318. return {'changed': True, 'results': api_rval, 'state': state}
  319. return {'failed': True, 'msg': 'Unknown state passed. %s' % state}