diff options
Diffstat (limited to 'roles/lib_openshift/src/class')
-rw-r--r-- | roles/lib_openshift/src/class/oc_adm_ca_server_cert.py | 135 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_adm_registry.py | 421 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_adm_router.py | 500 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_obj.py | 1 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_route.py | 8 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_sdnvalidator.py | 58 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_secret.py | 2 | ||||
-rw-r--r-- | roles/lib_openshift/src/class/oc_service.py | 6 |
8 files changed, 1124 insertions, 7 deletions
diff --git a/roles/lib_openshift/src/class/oc_adm_ca_server_cert.py b/roles/lib_openshift/src/class/oc_adm_ca_server_cert.py new file mode 100644 index 000000000..6ed1f2f35 --- /dev/null +++ b/roles/lib_openshift/src/class/oc_adm_ca_server_cert.py @@ -0,0 +1,135 @@ +# pylint: skip-file +# flake8: noqa + +class CAServerCertConfig(OpenShiftCLIConfig): + ''' CAServerCertConfig is a DTO for the oc adm ca command ''' + def __init__(self, kubeconfig, verbose, ca_options): + super(CAServerCertConfig, self).__init__('ca', None, kubeconfig, ca_options) + self.kubeconfig = kubeconfig + self.verbose = verbose + self._ca = ca_options + + +class CAServerCert(OpenShiftCLI): + ''' Class to wrap the oc adm ca create-server-cert command line''' + def __init__(self, + config, + verbose=False): + ''' Constructor for oadm ca ''' + super(CAServerCert, self).__init__(None, config.kubeconfig, verbose) + self.config = config + self.verbose = verbose + + def get(self): + '''get the current cert file + + If a file exists by the same name in the specified location then the cert exists + ''' + cert = self.config.config_options['cert']['value'] + if cert and os.path.exists(cert): + return open(cert).read() + + return None + + def create(self): + '''run openshift oc adm ca create-server-cert cmd''' + + # Added this here as a safegaurd for stomping on the + # cert and key files if they exist + if self.config.config_options['backup']['value']: + import time + ext = time.strftime("%Y-%m-%d@%H:%M:%S", time.localtime(time.time())) + date_str = "%s_" + "%s" % ext + + if os.path.exists(self.config.config_options['key']['value']): + shutil.copy(self.config.config_options['key']['value'], + date_str % self.config.config_options['key']['value']) + if os.path.exists(self.config.config_options['cert']['value']): + shutil.copy(self.config.config_options['cert']['value'], + date_str % self.config.config_options['cert']['value']) + + options = self.config.to_option_list() + + cmd = ['ca', 'create-server-cert'] + cmd.extend(options) + + return self.openshift_cmd(cmd, oadm=True) + + def exists(self): + ''' check whether the certificate exists and has the clusterIP ''' + + cert_path = self.config.config_options['cert']['value'] + if not os.path.exists(cert_path): + return False + + # Would prefer pyopenssl but is not installed. + # When we verify it is, switch this code + # Here is the code to get the subject and the SAN + # openssl x509 -text -noout -certopt \ + # no_header,no_version,no_serial,no_signame,no_validity,no_issuer,no_pubkey,no_sigdump,no_aux \ + # -in /etc/origin/master/registry.crt + # Instead of this solution we will use a regex. + cert_names = [] + hostnames = self.config.config_options['hostnames']['value'].split(',') + proc = subprocess.Popen(['openssl', 'x509', '-noout', '-text', '-in', cert_path], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + x509output, _ = proc.communicate() + if proc.returncode == 0: + regex = re.compile(r"^\s*X509v3 Subject Alternative Name:\s*?\n\s*(.*)\s*\n", re.MULTILINE) + match = regex.search(x509output) # E501 + for entry in re.split(r", *", match.group(1)): + if entry.startswith('DNS') or entry.startswith('IP Address'): + cert_names.append(entry.split(':')[1]) + # now that we have cert names let's compare + cert_set = set(cert_names) + hname_set = set(hostnames) + if cert_set.issubset(hname_set) and hname_set.issubset(cert_set): + return True + + return False + + @staticmethod + def run_ansible(params, check_mode): + '''run the idempotent ansible code''' + + config = CAServerCertConfig(params['kubeconfig'], + params['debug'], + {'cert': {'value': params['cert'], 'include': True}, + 'hostnames': {'value': ','.join(params['hostnames']), 'include': True}, + 'overwrite': {'value': True, 'include': True}, + 'key': {'value': params['key'], 'include': True}, + 'signer_cert': {'value': params['signer_cert'], 'include': True}, + 'signer_key': {'value': params['signer_key'], 'include': True}, + 'signer_serial': {'value': params['signer_serial'], 'include': True}, + 'backup': {'value': params['backup'], 'include': False}, + }) + + server_cert = CAServerCert(config) + + state = params['state'] + + if state == 'present': + ######## + # Create + ######## + if not server_cert.exists() or params['force']: + + if check_mode: + return {'changed': True, + 'msg': "CHECK_MODE: Would have created the certificate.", + 'state': state} + + api_rval = server_cert.create() + + return {'changed': True, 'results': api_rval, 'state': state} + + ######## + # Exists + ######## + api_rval = server_cert.get() + return {'changed': False, 'results': api_rval, 'state': state} + + return {'failed': True, + 'msg': 'Unknown state passed. %s' % state} + diff --git a/roles/lib_openshift/src/class/oc_adm_registry.py b/roles/lib_openshift/src/class/oc_adm_registry.py new file mode 100644 index 000000000..37904c43f --- /dev/null +++ b/roles/lib_openshift/src/class/oc_adm_registry.py @@ -0,0 +1,421 @@ +# pylint: skip-file +# flake8: noqa + +class RegistryException(Exception): + ''' Registry Exception Class ''' + pass + + +class RegistryConfig(OpenShiftCLIConfig): + ''' RegistryConfig is a DTO for the registry. ''' + def __init__(self, rname, namespace, kubeconfig, registry_options): + super(RegistryConfig, self).__init__(rname, namespace, kubeconfig, registry_options) + + +class Registry(OpenShiftCLI): + ''' Class to wrap the oc command line tools ''' + + volume_mount_path = 'spec.template.spec.containers[0].volumeMounts' + volume_path = 'spec.template.spec.volumes' + env_path = 'spec.template.spec.containers[0].env' + + def __init__(self, + registry_config, + verbose=False): + ''' Constructor for Registry + + a registry consists of 3 or more parts + - dc/docker-registry + - svc/docker-registry + + Parameters: + :registry_config: + :verbose: + ''' + super(Registry, self).__init__(registry_config.namespace, registry_config.kubeconfig, verbose) + self.version = OCVersion(registry_config.kubeconfig, verbose) + self.svc_ip = None + self.portal_ip = None + self.config = registry_config + self.verbose = verbose + self.registry_parts = [{'kind': 'dc', 'name': self.config.name}, + {'kind': 'svc', 'name': self.config.name}, + ] + + self.__prepared_registry = None + self.volume_mounts = [] + self.volumes = [] + if self.config.config_options['volume_mounts']['value']: + for volume in self.config.config_options['volume_mounts']['value']: + volume_info = {'secret_name': volume.get('secret_name', None), + 'name': volume.get('name', None), + 'type': volume.get('type', None), + 'path': volume.get('path', None), + 'claimName': volume.get('claim_name', None), + 'claimSize': volume.get('claim_size', None), + } + + vol, vol_mount = Volume.create_volume_structure(volume_info) + self.volumes.append(vol) + self.volume_mounts.append(vol_mount) + + self.dconfig = None + self.svc = None + + @property + def deploymentconfig(self): + ''' deploymentconfig property ''' + return self.dconfig + + @deploymentconfig.setter + def deploymentconfig(self, config): + ''' setter for deploymentconfig property ''' + self.dconfig = config + + @property + def service(self): + ''' service property ''' + return self.svc + + @service.setter + def service(self, config): + ''' setter for service property ''' + self.svc = config + + @property + def prepared_registry(self): + ''' prepared_registry property ''' + if not self.__prepared_registry: + results = self.prepare_registry() + if not results: + raise RegistryException('Could not perform registry preparation.') + self.__prepared_registry = results + + return self.__prepared_registry + + @prepared_registry.setter + def prepared_registry(self, data): + ''' setter method for prepared_registry attribute ''' + self.__prepared_registry = data + + def get(self): + ''' return the self.registry_parts ''' + self.deploymentconfig = None + self.service = None + + rval = 0 + for part in self.registry_parts: + result = self._get(part['kind'], rname=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(result['results'][0]) + + if result['returncode'] != 0: + rval = result['returncode'] + + + return {'returncode': rval, 'deploymentconfig': self.deploymentconfig, 'service': self.service} + + def exists(self): + '''does the object exist?''' + self.get() + if self.deploymentconfig and self.service: + return True + + return False + + def delete(self, complete=True): + '''return all pods ''' + parts = [] + for part in self.registry_parts: + if not complete and part['kind'] == 'svc': + continue + parts.append(self._delete(part['kind'], part['name'])) + + # Clean up returned results + rval = 0 + for part in parts: + # pylint: disable=invalid-sequence-index + if 'returncode' in part and part['returncode'] != 0: + rval = part['returncode'] + + return {'returncode': rval, 'results': parts} + + def prepare_registry(self): + ''' prepare a registry for instantiation ''' + options = self.config.to_option_list() + + cmd = ['registry', '-n', self.config.namespace] + cmd.extend(options) + cmd.extend(['--dry-run=True', '-o', 'json']) + + results = self.openshift_cmd(cmd, oadm=True, output=True, output_type='json') + # probably need to parse this + # pylint thinks results is a string + # pylint: disable=no-member + if results['returncode'] != 0 and 'items' in results['results']: + return results + + service = None + deploymentconfig = None + # pylint: disable=invalid-sequence-index + for res in results['results']['items']: + if res['kind'] == 'DeploymentConfig': + deploymentconfig = DeploymentConfig(res) + elif res['kind'] == 'Service': + service = Service(res) + + # Verify we got a service and a deploymentconfig + if not service or not deploymentconfig: + return results + + # results will need to get parsed here and modifications added + deploymentconfig = DeploymentConfig(self.add_modifications(deploymentconfig)) + + # modify service ip + if self.svc_ip: + service.put('spec.clusterIP', self.svc_ip) + if self.portal_ip: + service.put('spec.portalIP', self.portal_ip) + + # the dry-run doesn't apply the selector correctly + service.put('spec.selector', self.service.get_selector()) + + # need to create the service and the deploymentconfig + service_file = Utils.create_tmp_file_from_contents('service', service.yaml_dict) + deployment_file = Utils.create_tmp_file_from_contents('deploymentconfig', deploymentconfig.yaml_dict) + + return {"service": service, + "service_file": service_file, + "service_update": False, + "deployment": deploymentconfig, + "deployment_file": deployment_file, + "deployment_update": False} + + def create(self): + '''Create a registry''' + results = [] + self.needs_update() + # if the object is none, then we need to create it + # if the object needs an update, then we should call replace + # Handle the deploymentconfig + if self.deploymentconfig is None: + results.append(self._create(self.prepared_registry['deployment_file'])) + elif self.prepared_registry['deployment_update']: + results.append(self._replace(self.prepared_registry['deployment_file'])) + + # Handle the service + if self.service is None: + results.append(self._create(self.prepared_registry['service_file'])) + elif self.prepared_registry['service_update']: + results.append(self._replace(self.prepared_registry['service_file'])) + + # Clean up returned results + rval = 0 + for result in results: + # pylint: disable=invalid-sequence-index + if 'returncode' in result and result['returncode'] != 0: + rval = result['returncode'] + + return {'returncode': rval, 'results': results} + + def update(self): + '''run update for the registry. This performs a replace if required''' + # Store the current service IP + if self.service: + svcip = self.service.get('spec.clusterIP') + if svcip: + self.svc_ip = svcip + portip = self.service.get('spec.portalIP') + if portip: + self.portal_ip = portip + + results = [] + if self.prepared_registry['deployment_update']: + results.append(self._replace(self.prepared_registry['deployment_file'])) + if self.prepared_registry['service_update']: + results.append(self._replace(self.prepared_registry['service_file'])) + + # Clean up returned results + rval = 0 + for result in results: + if result['returncode'] != 0: + rval = result['returncode'] + + return {'returncode': rval, 'results': results} + + def add_modifications(self, deploymentconfig): + ''' update a deployment config with changes ''' + # The environment variable for REGISTRY_HTTP_SECRET is autogenerated + # We should set the generated deploymentconfig to the in memory version + # the following modifications will overwrite if needed + if self.deploymentconfig: + result = self.deploymentconfig.get_env_var('REGISTRY_HTTP_SECRET') + if result: + deploymentconfig.update_env_var('REGISTRY_HTTP_SECRET', result['value']) + + # Currently we know that our deployment of a registry requires a few extra modifications + # Modification 1 + # we need specific environment variables to be set + for key, value in self.config.config_options['env_vars'].get('value', {}).items(): + if not deploymentconfig.exists_env_key(key): + deploymentconfig.add_env_value(key, value) + else: + deploymentconfig.update_env_var(key, value) + + # Modification 2 + # we need specific volume variables to be set + for volume in self.volumes: + deploymentconfig.update_volume(volume) + + for vol_mount in self.volume_mounts: + deploymentconfig.update_volume_mount(vol_mount) + + # Modification 3 + # 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.yaml_dict + + def needs_update(self): + ''' check to see if we need to update ''' + exclude_list = ['clusterIP', 'portalIP', 'type', 'protocol'] + if self.service is None or \ + not Utils.check_def_equal(self.prepared_registry['service'].yaml_dict, + self.service.yaml_dict, + exclude_list, + debug=self.verbose): + self.prepared_registry['service_update'] = True + + exclude_list = ['dnsPolicy', + 'terminationGracePeriodSeconds', + 'restartPolicy', 'timeoutSeconds', + 'livenessProbe', 'readinessProbe', + 'terminationMessagePath', + 'securityContext', + 'imagePullPolicy', + 'protocol', # ports.portocol: TCP + 'type', # strategy: {'type': 'rolling'} + 'defaultMode', # added on secrets + 'activeDeadlineSeconds', # added in 1.5 for timeouts + ] + + if self.deploymentconfig is None or \ + not Utils.check_def_equal(self.prepared_registry['deployment'].yaml_dict, + self.deploymentconfig.yaml_dict, + exclude_list, + debug=self.verbose): + self.prepared_registry['deployment_update'] = True + + return self.prepared_registry['deployment_update'] or self.prepared_registry['service_update'] or False + + # In the future, we would like to break out each ansible state into a function. + # pylint: disable=too-many-branches,too-many-return-statements + @staticmethod + def run_ansible(params, check_mode): + '''run idempotent ansible code''' + + rconfig = RegistryConfig(params['name'], + params['namespace'], + params['kubeconfig'], + {'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}, + 'mount_host': {'value': params['mount_host'], 'include': True}, + 'env_vars': {'value': params['env_vars'], 'include': False}, + 'volume_mounts': {'value': params['volume_mounts'], 'include': False}, + 'edits': {'value': params['edits'], 'include': False}, + 'enforce_quota': {'value': params['enforce_quota'], 'include': True}, + 'daemonset': {'value': params['daemonset'], 'include': True}, + 'tls_key': {'value': params['tls_key'], 'include': True}, + 'tls_certificate': {'value': params['tls_certificate'], 'include': True}, + }) + + + ocregistry = Registry(rconfig, params['debug']) + + api_rval = ocregistry.get() + + state = params['state'] + ######## + # get + ######## + if state == 'list': + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': False, 'results': api_rval, 'state': state} + + ######## + # Delete + ######## + if state == 'absent': + if not ocregistry.exists(): + return {'changed': False, 'state': state} + + if check_mode: + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a delete.'} + + # Unsure as to why this is angry with the return type. + # pylint: disable=redefined-variable-type + api_rval = ocregistry.delete() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': api_rval, 'state': state} + + if state == 'present': + ######## + # Create + ######## + if not ocregistry.exists(): + + if check_mode: + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed a create.'} + + api_rval = ocregistry.create() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': api_rval, 'state': state} + + ######## + # Update + ######## + if not params['force'] and not ocregistry.needs_update(): + return {'changed': False, 'state': state} + + if check_mode: + return {'changed': True, 'msg': 'CHECK_MODE: Would have performed an update.'} + + api_rval = ocregistry.update() + + if api_rval['returncode'] != 0: + return {'failed': True, 'msg': api_rval} + + return {'changed': True, 'results': api_rval, 'state': state} + + return {'failed': True, 'msg': 'Unknown state passed. %s' % state} diff --git a/roles/lib_openshift/src/class/oc_adm_router.py b/roles/lib_openshift/src/class/oc_adm_router.py new file mode 100644 index 000000000..7b163b120 --- /dev/null +++ b/roles/lib_openshift/src/class/oc_adm_router.py @@ -0,0 +1,500 @@ +# 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__('default', 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'], rname=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 + 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() + + cmd = ['router', self.config.name, '-n', self.config.namespace] + 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() + + import time + # 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 ansible idempotent code''' + + 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}, + '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}, + 'expose_metrics': {'value': params['expose_metrics'], 'include': True}, + 'metrics_image': {'value': params['metrics_image'], '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} diff --git a/roles/lib_openshift/src/class/oc_obj.py b/roles/lib_openshift/src/class/oc_obj.py index 21129a50c..51d3ce996 100644 --- a/roles/lib_openshift/src/class/oc_obj.py +++ b/roles/lib_openshift/src/class/oc_obj.py @@ -77,7 +77,6 @@ class OCObject(OpenShiftCLI): if objects['returncode'] != 0: return objects - # pylint: disable=no-member data = None if files: data = Utils.get_resource_file(files[0], content_type) diff --git a/roles/lib_openshift/src/class/oc_route.py b/roles/lib_openshift/src/class/oc_route.py index cb743e19d..3935525f1 100644 --- a/roles/lib_openshift/src/class/oc_route.py +++ b/roles/lib_openshift/src/class/oc_route.py @@ -55,13 +55,15 @@ class OCRoute(OpenShiftCLI): def update(self): '''update the object''' - # need to update the tls information and the service name - return self._replace_content(self.kind, self.config.name, self.config.data) + return self._replace_content(self.kind, + self.config.name, + self.config.data, + force=(self.config.host != self.route.get_host())) def needs_update(self): ''' verify an update is needed ''' skip = [] - return not Utils.check_def_equal(self.config.data, self.route.yaml_dict, skip_keys=skip, debug=True) + return not Utils.check_def_equal(self.config.data, self.route.yaml_dict, skip_keys=skip, debug=self.verbose) @staticmethod def get_cert_data(path, content): diff --git a/roles/lib_openshift/src/class/oc_sdnvalidator.py b/roles/lib_openshift/src/class/oc_sdnvalidator.py new file mode 100644 index 000000000..da923337b --- /dev/null +++ b/roles/lib_openshift/src/class/oc_sdnvalidator.py @@ -0,0 +1,58 @@ +# pylint: skip-file +# flake8: noqa + +# pylint: disable=too-many-instance-attributes +class OCSDNValidator(OpenShiftCLI): + ''' Class to wrap the oc command line tools ''' + + def __init__(self, kubeconfig): + ''' Constructor for OCSDNValidator ''' + # namespace has no meaning for SDN validation, hardcode to 'default' + super(OCSDNValidator, self).__init__('default', kubeconfig) + + def get(self, kind, invalid_filter): + ''' return SDN information ''' + + rval = self._get(kind) + if rval['returncode'] != 0: + return False, rval, [] + + return True, rval, filter(invalid_filter, rval['results'][0]['items']) + + # pylint: disable=too-many-return-statements + @staticmethod + def run_ansible(params): + ''' run the idempotent ansible code + + params comes from the ansible portion of this module + ''' + + sdnvalidator = OCSDNValidator(params['kubeconfig']) + all_invalid = {} + failed = False + + checks = ( + ( + 'hostsubnet', + lambda x: x['metadata']['name'] != x['host'], + u'hostsubnets where metadata.name != host', + ), + ( + 'netnamespace', + lambda x: x['metadata']['name'] != x['netname'], + u'netnamespaces where metadata.name != netname', + ), + ) + + for resource, invalid_filter, invalid_msg in checks: + success, rval, invalid = sdnvalidator.get(resource, invalid_filter) + if not success: + return {'failed': True, 'msg': 'Failed to GET {}.'.format(resource), 'state': 'list', 'results': rval} + if invalid: + failed = True + all_invalid[invalid_msg] = invalid + + if failed: + return {'failed': True, 'msg': 'All SDN objects are not valid.', 'state': 'list', 'results': all_invalid} + + return {'msg': 'All SDN objects are valid.'} diff --git a/roles/lib_openshift/src/class/oc_secret.py b/roles/lib_openshift/src/class/oc_secret.py index 5eac27572..deb36a9fa 100644 --- a/roles/lib_openshift/src/class/oc_secret.py +++ b/roles/lib_openshift/src/class/oc_secret.py @@ -29,7 +29,7 @@ class OCSecret(OpenShiftCLI): if results['returncode'] == 0 and results['results'][0]: results['exists'] = True if self.decode: - if results['results'][0].has_key('data'): + if 'data' in results['results'][0]: for sname, value in results['results'][0]['data'].items(): results['decoded'][sname] = base64.b64decode(value) diff --git a/roles/lib_openshift/src/class/oc_service.py b/roles/lib_openshift/src/class/oc_service.py index d4cc83a59..20cf23df5 100644 --- a/roles/lib_openshift/src/class/oc_service.py +++ b/roles/lib_openshift/src/class/oc_service.py @@ -22,7 +22,7 @@ class OCService(OpenShiftCLI): kubeconfig='/etc/origin/master/admin.kubeconfig', verbose=False): ''' Constructor for OCVolume ''' - super(OCService, self).__init__(namespace, kubeconfig) + super(OCService, self).__init__(namespace, kubeconfig, verbose) self.namespace = namespace self.config = ServiceConfig(sname, namespace, ports, selector, labels, cluster_ip, portal_ip, session_affinity, service_type) @@ -93,7 +93,9 @@ class OCService(OpenShiftCLI): params['portalip'], params['ports'], params['session_affinity'], - params['service_type']) + params['service_type'], + params['kubeconfig'], + params['debug']) state = params['state'] |