123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- # pylint: skip-file
- # flake8: noqa
- class RouterException(Exception):
- ''' Router exception'''
- pass
- class RouterConfig(OpenShiftCLIConfig):
- ''' RouterConfig is a DTO for the router. '''
- def __init__(self, rname, namespace, kubeconfig, router_options):
- super(RouterConfig, self).__init__(rname, namespace, kubeconfig, router_options)
- class Router(OpenShiftCLI):
- ''' Class to wrap the oc command line tools '''
- def __init__(self,
- router_config,
- verbose=False):
- ''' Constructor for OpenshiftOC
- a router consists of 3 or more parts
- - dc/router
- - svc/router
- - sa/router
- - secret/router-certs
- - clusterrolebinding/router-router-role
- '''
- super(Router, self).__init__(router_config.namespace, router_config.kubeconfig, verbose)
- self.config = router_config
- self.verbose = verbose
- self.router_parts = [{'kind': 'dc', 'name': self.config.name},
- {'kind': 'svc', 'name': self.config.name},
- {'kind': 'sa', 'name': self.config.config_options['service_account']['value']},
- {'kind': 'secret', 'name': self.config.name + '-certs'},
- {'kind': 'clusterrolebinding', 'name': 'router-' + self.config.name + '-role'},
- ]
- self.__prepared_router = None
- self.dconfig = None
- self.svc = None
- self._secret = None
- self._serviceaccount = None
- self._rolebinding = None
- @property
- def prepared_router(self):
- ''' property for the prepared router'''
- if self.__prepared_router is None:
- results = self._prepare_router()
- if not results or 'returncode' in results and results['returncode'] != 0:
- if 'stderr' in results:
- raise RouterException('Could not perform router preparation: %s' % results['stderr'])
- raise RouterException('Could not perform router preparation.')
- self.__prepared_router = results
- return self.__prepared_router
- @prepared_router.setter
- def prepared_router(self, obj):
- '''setter for the prepared_router'''
- self.__prepared_router = obj
- @property
- def deploymentconfig(self):
- ''' property deploymentconfig'''
- return self.dconfig
- @deploymentconfig.setter
- def deploymentconfig(self, config):
- ''' setter for property deploymentconfig '''
- self.dconfig = config
- @property
- def service(self):
- ''' property for service '''
- return self.svc
- @service.setter
- def service(self, config):
- ''' setter for property service '''
- self.svc = config
- @property
- def secret(self):
- ''' property secret '''
- return self._secret
- @secret.setter
- def secret(self, config):
- ''' setter for property secret '''
- self._secret = config
- @property
- def serviceaccount(self):
- ''' property for serviceaccount '''
- return self._serviceaccount
- @serviceaccount.setter
- def serviceaccount(self, config):
- ''' setter for property serviceaccount '''
- self._serviceaccount = config
- @property
- def rolebinding(self):
- ''' property rolebinding '''
- return self._rolebinding
- @rolebinding.setter
- def rolebinding(self, config):
- ''' setter for property rolebinding '''
- self._rolebinding = config
- def get_object_by_kind(self, kind):
- '''return the current object kind by name'''
- if re.match("^(dc|deploymentconfig)$", kind, flags=re.IGNORECASE):
- return self.deploymentconfig
- elif re.match("^(svc|service)$", kind, flags=re.IGNORECASE):
- return self.service
- elif re.match("^(sa|serviceaccount)$", kind, flags=re.IGNORECASE):
- return self.serviceaccount
- elif re.match("secret", kind, flags=re.IGNORECASE):
- return self.secret
- elif re.match("clusterrolebinding", kind, flags=re.IGNORECASE):
- return self.rolebinding
- return None
- def get(self):
- ''' return the self.router_parts '''
- self.service = None
- self.deploymentconfig = None
- self.serviceaccount = None
- self.secret = None
- self.rolebinding = None
- for part in self.router_parts:
- result = self._get(part['kind'], name=part['name'])
- if result['returncode'] == 0 and part['kind'] == 'dc':
- self.deploymentconfig = DeploymentConfig(result['results'][0])
- elif result['returncode'] == 0 and part['kind'] == 'svc':
- self.service = Service(content=result['results'][0])
- elif result['returncode'] == 0 and part['kind'] == 'sa':
- self.serviceaccount = ServiceAccount(content=result['results'][0])
- elif result['returncode'] == 0 and part['kind'] == 'secret':
- self.secret = Secret(content=result['results'][0])
- elif result['returncode'] == 0 and part['kind'] == 'clusterrolebinding':
- self.rolebinding = RoleBinding(content=result['results'][0])
- return {'deploymentconfig': self.deploymentconfig,
- 'service': self.service,
- 'serviceaccount': self.serviceaccount,
- 'secret': self.secret,
- 'clusterrolebinding': self.rolebinding,
- }
- def exists(self):
- '''return a whether svc or dc exists '''
- if self.deploymentconfig and self.service and self.secret and self.serviceaccount:
- return True
- return False
- def delete(self):
- '''return all pods '''
- parts = []
- for part in self.router_parts:
- parts.append(self._delete(part['kind'], part['name']))
- rval = 0
- for part in parts:
- if part['returncode'] != 0 and not 'already exist' in part['stderr']:
- rval = part['returncode']
- return {'returncode': rval, 'results': parts}
- def add_modifications(self, deploymentconfig):
- '''modify the deployment config'''
- # We want modifications in the form of edits coming in from the module.
- # Let's apply these here
- # If extended validation is enabled, set the corresponding environment
- # variable.
- if self.config.config_options['extended_validation']['value']:
- if not deploymentconfig.exists_env_key('EXTENDED_VALIDATION'):
- deploymentconfig.add_env_value('EXTENDED_VALIDATION', "true")
- else:
- deploymentconfig.update_env_var('EXTENDED_VALIDATION', "true")
- # Apply any edits.
- edit_results = []
- for edit in self.config.config_options['edits'].get('value', []):
- if edit['action'] == 'put':
- edit_results.append(deploymentconfig.put(edit['key'],
- edit['value']))
- if edit['action'] == 'update':
- edit_results.append(deploymentconfig.update(edit['key'],
- edit['value'],
- edit.get('index', None),
- edit.get('curr_value', None)))
- if edit['action'] == 'append':
- edit_results.append(deploymentconfig.append(edit['key'],
- edit['value']))
- if edit_results and not any([res[0] for res in edit_results]):
- return None
- return deploymentconfig
- # pylint: disable=too-many-branches
- def _prepare_router(self):
- '''prepare router for instantiation'''
- # if cacert, key, and cert were passed, combine them into a pem file
- if (self.config.config_options['cacert_file']['value'] and
- self.config.config_options['cert_file']['value'] and
- self.config.config_options['key_file']['value']):
- router_pem = '/tmp/router.pem'
- with open(router_pem, 'w') as rfd:
- rfd.write(open(self.config.config_options['cert_file']['value']).read())
- rfd.write(open(self.config.config_options['key_file']['value']).read())
- if self.config.config_options['cacert_file']['value'] and \
- os.path.exists(self.config.config_options['cacert_file']['value']):
- rfd.write(open(self.config.config_options['cacert_file']['value']).read())
- atexit.register(Utils.cleanup, [router_pem])
- self.config.config_options['default_cert']['value'] = router_pem
- elif self.config.config_options['default_cert']['value'] is None:
- # No certificate was passed to us. do not pass one to oc adm router
- self.config.config_options['default_cert']['include'] = False
- options = self.config.to_option_list(ascommalist='labels')
- cmd = ['router', self.config.name]
- cmd.extend(options)
- cmd.extend(['--dry-run=True', '-o', 'json'])
- results = self.openshift_cmd(cmd, oadm=True, output=True, output_type='json')
- # pylint: disable=maybe-no-member
- if results['returncode'] != 0 or 'items' not in results['results']:
- return results
- oc_objects = {'DeploymentConfig': {'obj': None, 'path': None, 'update': False},
- 'Secret': {'obj': None, 'path': None, 'update': False},
- 'ServiceAccount': {'obj': None, 'path': None, 'update': False},
- 'ClusterRoleBinding': {'obj': None, 'path': None, 'update': False},
- 'Service': {'obj': None, 'path': None, 'update': False},
- }
- # pylint: disable=invalid-sequence-index
- for res in results['results']['items']:
- if res['kind'] == 'DeploymentConfig':
- oc_objects['DeploymentConfig']['obj'] = DeploymentConfig(res)
- elif res['kind'] == 'Service':
- oc_objects['Service']['obj'] = Service(res)
- elif res['kind'] == 'ServiceAccount':
- oc_objects['ServiceAccount']['obj'] = ServiceAccount(res)
- elif res['kind'] == 'Secret':
- oc_objects['Secret']['obj'] = Secret(res)
- elif res['kind'] == 'ClusterRoleBinding':
- oc_objects['ClusterRoleBinding']['obj'] = RoleBinding(res)
- # Currently only deploymentconfig needs updating
- # Verify we got a deploymentconfig
- if not oc_objects['DeploymentConfig']['obj']:
- return results
- # add modifications added
- oc_objects['DeploymentConfig']['obj'] = self.add_modifications(oc_objects['DeploymentConfig']['obj'])
- for oc_type, oc_data in oc_objects.items():
- if oc_data['obj'] is not None:
- oc_data['path'] = Utils.create_tmp_file_from_contents(oc_type, oc_data['obj'].yaml_dict)
- return oc_objects
- def create(self):
- '''Create a router
- This includes the different parts:
- - deploymentconfig
- - service
- - serviceaccount
- - secrets
- - clusterrolebinding
- '''
- results = []
- self.needs_update()
- # pylint: disable=maybe-no-member
- for kind, oc_data in self.prepared_router.items():
- if oc_data['obj'] is not None:
- time.sleep(1)
- if self.get_object_by_kind(kind) is None:
- results.append(self._create(oc_data['path']))
- elif oc_data['update']:
- results.append(self._replace(oc_data['path']))
- rval = 0
- for result in results:
- if result['returncode'] != 0 and not 'already exist' in result['stderr']:
- rval = result['returncode']
- return {'returncode': rval, 'results': results}
- def update(self):
- '''run update for the router. This performs a replace'''
- results = []
- # pylint: disable=maybe-no-member
- for _, oc_data in self.prepared_router.items():
- if oc_data['update']:
- results.append(self._replace(oc_data['path']))
- rval = 0
- for result in results:
- if result['returncode'] != 0:
- rval = result['returncode']
- return {'returncode': rval, 'results': results}
- # pylint: disable=too-many-return-statements,too-many-branches
- def needs_update(self):
- ''' check to see if we need to update '''
- # ServiceAccount:
- # Need to determine changes from the pregenerated ones from the original
- # Since these are auto generated, we can skip
- skip = ['secrets', 'imagePullSecrets']
- if self.serviceaccount is None or \
- not Utils.check_def_equal(self.prepared_router['ServiceAccount']['obj'].yaml_dict,
- self.serviceaccount.yaml_dict,
- skip_keys=skip,
- debug=self.verbose):
- self.prepared_router['ServiceAccount']['update'] = True
- # Secret:
- # See if one was generated from our dry-run and verify it if needed
- if self.prepared_router['Secret']['obj']:
- if not self.secret:
- self.prepared_router['Secret']['update'] = True
- if self.secret is None or \
- not Utils.check_def_equal(self.prepared_router['Secret']['obj'].yaml_dict,
- self.secret.yaml_dict,
- skip_keys=skip,
- debug=self.verbose):
- self.prepared_router['Secret']['update'] = True
- # Service:
- # Fix the ports to have protocol=TCP
- for port in self.prepared_router['Service']['obj'].get('spec.ports'):
- port['protocol'] = 'TCP'
- skip = ['portalIP', 'clusterIP', 'sessionAffinity', 'type']
- if self.service is None or \
- not Utils.check_def_equal(self.prepared_router['Service']['obj'].yaml_dict,
- self.service.yaml_dict,
- skip_keys=skip,
- debug=self.verbose):
- self.prepared_router['Service']['update'] = True
- # DeploymentConfig:
- # Router needs some exceptions.
- # We do not want to check the autogenerated password for stats admin
- if self.deploymentconfig is not None:
- if not self.config.config_options['stats_password']['value']:
- for idx, env_var in enumerate(self.prepared_router['DeploymentConfig']['obj'].get(\
- 'spec.template.spec.containers[0].env') or []):
- if env_var['name'] == 'STATS_PASSWORD':
- env_var['value'] = \
- self.deploymentconfig.get('spec.template.spec.containers[0].env[%s].value' % idx)
- break
- # dry-run doesn't add the protocol to the ports section. We will manually do that.
- for idx, port in enumerate(self.prepared_router['DeploymentConfig']['obj'].get(\
- 'spec.template.spec.containers[0].ports') or []):
- if not 'protocol' in port:
- port['protocol'] = 'TCP'
- # These are different when generating
- skip = ['dnsPolicy',
- 'terminationGracePeriodSeconds',
- 'restartPolicy', 'timeoutSeconds',
- 'livenessProbe', 'readinessProbe',
- 'terminationMessagePath', 'hostPort',
- 'defaultMode',
- ]
- if self.deploymentconfig is None or \
- not Utils.check_def_equal(self.prepared_router['DeploymentConfig']['obj'].yaml_dict,
- self.deploymentconfig.yaml_dict,
- skip_keys=skip,
- debug=self.verbose):
- self.prepared_router['DeploymentConfig']['update'] = True
- # Check if any of the parts need updating, if so, return True
- # else, no need to update
- # pylint: disable=no-member
- return any([self.prepared_router[oc_type]['update'] for oc_type in self.prepared_router.keys()])
- @staticmethod
- def run_ansible(params, check_mode):
- '''run the oc_adm_router module'''
- rconfig = RouterConfig(params['name'],
- params['namespace'],
- params['kubeconfig'],
- {'default_cert': {'value': params['default_cert'], 'include': True},
- 'cert_file': {'value': params['cert_file'], 'include': False},
- 'key_file': {'value': params['key_file'], 'include': False},
- 'images': {'value': params['images'], 'include': True},
- 'latest_images': {'value': params['latest_images'], 'include': True},
- 'labels': {'value': params['labels'], 'include': True},
- 'ports': {'value': ','.join(params['ports']), 'include': True},
- 'replicas': {'value': params['replicas'], 'include': True},
- 'selector': {'value': params['selector'], 'include': True},
- 'service_account': {'value': params['service_account'], 'include': True},
- 'router_type': {'value': params['router_type'], 'include': False},
- 'host_network': {'value': params['host_network'], 'include': True},
- 'extended_validation': {'value': params['extended_validation'], 'include': False},
- 'external_host': {'value': params['external_host'], 'include': True},
- 'external_host_vserver': {'value': params['external_host_vserver'],
- 'include': True},
- 'external_host_insecure': {'value': params['external_host_insecure'],
- 'include': True},
- 'external_host_partition_path': {'value': params['external_host_partition_path'],
- 'include': True},
- 'external_host_username': {'value': params['external_host_username'],
- 'include': True},
- 'external_host_password': {'value': params['external_host_password'],
- 'include': True},
- 'external_host_private_key': {'value': params['external_host_private_key'],
- 'include': True},
- 'stats_user': {'value': params['stats_user'], 'include': True},
- 'stats_password': {'value': params['stats_password'], 'include': True},
- 'stats_port': {'value': params['stats_port'], 'include': True},
- # extra
- 'cacert_file': {'value': params['cacert_file'], 'include': False},
- # edits
- 'edits': {'value': params['edits'], 'include': False},
- })
- state = params['state']
- ocrouter = Router(rconfig, verbose=params['debug'])
- api_rval = ocrouter.get()
- ########
- # get
- ########
- if state == 'list':
- return {'changed': False, 'results': api_rval, 'state': state}
- ########
- # Delete
- ########
- if state == 'absent':
- if not ocrouter.exists():
- return {'changed': False, 'state': state}
- if check_mode:
- return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'}
- # In case of delete we return a list of each object
- # that represents a router and its result in a list
- # pylint: disable=redefined-variable-type
- api_rval = ocrouter.delete()
- return {'changed': True, 'results': api_rval, 'state': state}
- if state == 'present':
- ########
- # Create
- ########
- if not ocrouter.exists():
- if check_mode:
- return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'}
- api_rval = ocrouter.create()
- if api_rval['returncode'] != 0:
- return {'failed': True, 'msg': api_rval}
- return {'changed': True, 'results': api_rval, 'state': state}
- ########
- # Update
- ########
- if not ocrouter.needs_update():
- return {'changed': False, 'state': state}
- if check_mode:
- return {'changed': False, 'msg': 'CHECK_MODE: Would have performed an update.'}
- api_rval = ocrouter.update()
- if api_rval['returncode'] != 0:
- return {'failed': True, 'msg': api_rval}
- return {'changed': True, 'results': api_rval, 'state': state}
|