blob: d3d8ac02dcbd8545bf23f2f40e5da176f21e78cf [file] [log] [blame]
#!/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()