| #!/usr/bin/python |
| |
| DOCUMENTATION=''' |
| --- |
| module: rancher_k8s_environment |
| description: |
| - This module will create or delete Kubernetes environment. |
| - It will also delete other environments when variables are set accordingly. |
| notes: |
| - It identifies environment only by name. Expect problems with same named environments. |
| - All hosts running Kubernetes cluster should have same OS otherwise there |
| is possibility of misbehavement. |
| options: |
| server: |
| required: true |
| description: |
| - Url of rancher server i.e. "http://10.0.0.1:8080". |
| name: |
| required: true |
| descritpion: |
| - Name of the environment to create/remove. |
| descr: |
| description: |
| - Description of environment to create. |
| state: |
| description: |
| - If "present" environment will be created or setup depending if it exists. |
| With multiple environments with same name expect error. |
| If "absent" environment will be removed. If multiple environments have same |
| name all will be deleted. |
| default: present |
| choices: [present, absent] |
| delete_not_k8s: |
| description: |
| - Indicates if environments with different orchestration than Kubernetes should |
| be deleted. |
| type: bool |
| default: yes |
| delete_other_k8s: |
| description: |
| - Indicates if environments with different name than specified should |
| be deleted. |
| type: bool |
| default: no |
| force: |
| description: |
| - Indicates if environment should be deleted and recreated. |
| type: bool |
| default: yes |
| host_os: |
| required: true |
| description: |
| - OS (family from ansible_os_family variable) of the hosts running cluster. If |
| "RedHat" then datavolume fix will be applied. |
| Fix described here: |
| https://github.com/rancher/rancher/issues/10015 |
| ''' |
| |
| import json |
| import time |
| |
| import requests |
| from ansible.module_utils.basic import AnsibleModule |
| |
| |
| |
| def get_existing_environments(rancher_address): |
| req = requests.get('{}/v2-beta/projects'.format(rancher_address)) |
| envs = req.json()['data'] |
| return envs |
| |
| |
| def not_k8s_ids(environments): |
| envs = filter(lambda x: x['orchestration'] != 'kubernetes', environments) |
| return [env['id'] for env in envs] |
| |
| |
| def other_k8s_ids(environments, name): |
| envs = filter(lambda x: x['orchestration'] == 'kubernetes' and x['name'] != name, |
| environments) |
| return [env['id'] for env in envs] |
| |
| |
| def env_ids_by_name(environments, name): |
| envs = filter(lambda x: x['name'] == name, environments) |
| return [env['id'] for env in envs] |
| |
| |
| def env_info_by_id(environments, env_id): |
| env = filter(lambda x: x['id'] == env_id, environments) |
| return [{'id': x['id'], 'name': x['name']} for x in env][0] |
| |
| |
| def delete_multiple_environments(rancher_address, env_ids): |
| deleted = [] |
| for env_id in env_ids: |
| deleted.append(delete_environment(rancher_address, env_id)) |
| return deleted |
| |
| |
| def delete_environment(rancher_address, env_id): |
| req = requests.delete('{}/v2-beta/projects/{}'.format(rancher_address, env_id)) |
| deleted = req.json()['data'][0] |
| return {'id': deleted['id'], |
| 'name': deleted['name'], |
| 'orchestration': deleted['orchestration']} |
| |
| |
| def create_k8s_environment(rancher_address, name, descr): |
| k8s_template_id = None |
| for _ in range(10): |
| k8s_template = requests.get( |
| '{}/v2-beta/projecttemplates?name=Kubernetes'.format(rancher_address)).json() |
| if k8s_template['data']: |
| k8s_template_id = k8s_template['data'][0]['id'] |
| break |
| time.sleep(3) |
| if k8s_template_id is None: |
| raise ValueError('Template for kubernetes not found.') |
| body = { |
| 'name': name, |
| 'description': descr, |
| 'projectTemplateId': k8s_template_id, |
| 'allowSystemRole': False, |
| 'members': [], |
| 'virtualMachine': False, |
| 'servicesPortRange': None, |
| 'projectLinks': [] |
| } |
| |
| body_json = json.dumps(body) |
| req = requests.post('{}/v2-beta/projects'.format(rancher_address), data=body_json) |
| created = req.json() |
| return {'id': created['id'], 'name': created['name']} |
| |
| |
| def get_kubelet_service(rancher_address, env_id): |
| for _ in range(10): |
| response = requests.get( |
| '{}/v2-beta/projects/{}/services/?name=kubelet'.format(rancher_address, |
| env_id)) |
| |
| if response.status_code >= 400: |
| # too early or too late for obtaining data |
| # small delay will improve our chances to collect it |
| time.sleep(1) |
| continue |
| |
| content = response.json() |
| |
| if content['data']: |
| return content['data'][0] |
| |
| # this is unfortunate, response from service api received but data |
| # not available, lets try again |
| time.sleep(5) |
| |
| return None |
| |
| |
| def fix_datavolume_rhel(rancher_address, env_id): |
| kubelet_svc = get_kubelet_service(rancher_address, env_id) |
| if kubelet_svc: |
| try: |
| data_volume_index = kubelet_svc['launchConfig']['dataVolumes'].index( |
| '/sys:/sys:ro,rprivate') |
| except ValueError: |
| return 'Already changed' |
| kubelet_svc['launchConfig']['dataVolumes'][ |
| data_volume_index] = '/sys/fs/cgroup:/sys/fs/cgroup:ro,rprivate' |
| body = { |
| 'inServiceStrategy': { |
| 'batchSize': 1, |
| 'intervalMillis': 2000, |
| 'startFirst': False, |
| 'launchConfig': kubelet_svc['launchConfig'], |
| 'secondaryLaunchConfigs': [] |
| } |
| } |
| body_json = json.dumps(body) |
| requests.post( |
| '{}/v2-beta/projects/{}/services/{}?action=upgrade'.format(rancher_address, |
| env_id, |
| kubelet_svc[ |
| 'id']), |
| data=body_json) |
| for _ in range(10): |
| req_svc = requests.get( |
| '{}/v2-beta/projects/{}/services/{}'.format(rancher_address, env_id, |
| kubelet_svc['id'])) |
| req_svc_content = req_svc.json() |
| if 'finishupgrade' in req_svc_content['actions']: |
| req_finish = requests.post( |
| req_svc_content['actions']['finishupgrade']) |
| return { |
| 'dataVolumes': req_finish.json()['upgrade']['inServiceStrategy'][ |
| 'launchConfig']['dataVolumes']} |
| time.sleep(5) |
| else: |
| raise ValueError('Could not get kubelet service') |
| |
| |
| def create_registration_tokens(rancher_address, env_id): |
| body = {'name': str(env_id)} |
| body_json = json.dumps(body) |
| response = requests.post( |
| '{}/v2-beta/projects/{}/registrationtokens'.format(rancher_address, env_id, |
| data=body_json)) |
| for _ in range(10): |
| tokens = requests.get(response.json()['links']['self']) |
| tokens_content = tokens.json() |
| if tokens_content['image'] is not None and tokens_content[ |
| 'registrationUrl'] is not None: |
| return {'image': tokens_content['image'], |
| 'reg_url': tokens_content['registrationUrl']} |
| time.sleep(3) |
| return None |
| |
| |
| def get_registration_tokens(rancher_address, env_id): |
| reg_tokens = requests.get( |
| '{}/v2-beta/projects/{}/registrationtokens'.format(rancher_address, env_id)) |
| reg_tokens_content = reg_tokens.json() |
| tokens = reg_tokens_content['data'] |
| if not tokens: |
| return None |
| return {'image': tokens[0]['image'], 'reg_url': tokens[0]['registrationUrl']} |
| |
| |
| def create_apikey(rancher_address, env_id): |
| body = { |
| 'name': 'kubectl_env_{}'.format(env_id), |
| 'description': "Provides access to kubectl" |
| } |
| body_json = json.dumps(body) |
| apikey_req = requests.post( |
| '{}/v2-beta/apikey'.format(rancher_address, env_id, data=body_json)) |
| apikey_content = apikey_req.json() |
| return {'public': apikey_content['publicValue'], |
| 'private': apikey_content['secretValue']} |
| |
| |
| def run_module(): |
| module = AnsibleModule( |
| argument_spec=dict( |
| server=dict(type='str', required=True), |
| name=dict(type='str', required=True), |
| descr=dict(type='str'), |
| state=dict(type='str', choices=['present', 'absent'], default='present'), |
| delete_other_k8s=dict(type='bool', default=False), |
| delete_not_k8s=dict(type='bool', default=True), |
| force=dict(type='bool', default=True), |
| host_os=dict(type='str', required=True) |
| ) |
| ) |
| |
| params = module.params |
| rancher_address = params['server'] |
| name = params['name'] |
| descr = params['descr'] |
| delete_not_k8s = params['delete_not_k8s'] |
| delete_other_k8s = params['delete_other_k8s'] |
| force = params['force'] |
| host_os = params['host_os'] |
| state = params['state'] |
| |
| existing_envs = get_existing_environments(rancher_address) |
| same_name_ids = env_ids_by_name(existing_envs, name) |
| |
| to_delete_ids = [] |
| changes = {} |
| |
| if delete_other_k8s: |
| to_delete_ids += other_k8s_ids(existing_envs, name) |
| |
| if delete_not_k8s: |
| to_delete_ids += not_k8s_ids(existing_envs) |
| if force or state == 'absent': |
| to_delete_ids += same_name_ids |
| |
| deleted = delete_multiple_environments(rancher_address, to_delete_ids) |
| |
| if deleted: |
| changes['deleted'] = deleted |
| if state == 'absent': |
| module.exit_json(changed=True, deleted=changes['deleted']) |
| else: |
| if state == 'absent': |
| module.exit_json(changed=False) |
| |
| if len(same_name_ids) > 1 and not force: |
| module.fail_json(msg='Multiple environments with same name. ' |
| 'Use "force: yes" to delete ' |
| 'all environments with same name.') |
| |
| if same_name_ids and not force: |
| changes['environment'] = env_info_by_id(existing_envs, same_name_ids[0]) |
| if host_os == 'RedHat': |
| try: |
| rhel_fix = fix_datavolume_rhel(rancher_address, same_name_ids[0]) |
| changes['rhel_fix'] = rhel_fix |
| except ValueError as err: |
| module.fail_json( |
| msg='Error: {} Try to recreate k8s environment.'.format(err)) |
| |
| reg_tokens = get_registration_tokens(rancher_address, same_name_ids[0]) |
| if not reg_tokens: |
| reg_tokens = create_registration_tokens(rancher_address, same_name_ids[0]) |
| changes['registration_tokens'] = reg_tokens |
| |
| apikey = create_apikey(rancher_address, same_name_ids[0]) |
| changes['apikey'] = apikey |
| module.exit_json(changed=True, data=changes, |
| msg='New environment was not created. Only set up was done') |
| try: |
| new_env = create_k8s_environment(rancher_address, name, descr) |
| except ValueError as err: |
| module.fail_json(msg='Error: {} Try to recreate k8s environment.'.format(err)) |
| |
| if host_os == 'RedHat': |
| try: |
| rhel_fix = fix_datavolume_rhel(rancher_address, new_env['id']) |
| changes['rhel_fix'] = rhel_fix |
| except ValueError as err: |
| module.fail_json(msg='Error: {} Try to recreate k8s environment.'.format( |
| err)) |
| |
| reg_tokens = create_registration_tokens(rancher_address, new_env['id']) |
| |
| apikey = create_apikey(rancher_address, new_env['id']) |
| |
| changes['environment'] = new_env |
| changes['registration_tokens'] = reg_tokens |
| changes['apikey'] = apikey |
| |
| module.exit_json(changed=True, data=changes) |
| |
| |
| if __name__ == '__main__': |
| run_module() |
| |