[BUILD] Enable importCustomCertsEnabled in docker images collector script
[oom/offline-installer.git] / ansible / library / rancher_k8s_environment.py
1 #!/usr/bin/python
2
3 DOCUMENTATION='''
4 ---
5 module: rancher_k8s_environment
6 description:
7   - This module will create or delete Kubernetes environment.
8   - It will also delete other environments when variables are set accordingly.
9 notes:
10   - It identifies environment only by name. Expect problems with same named environments.
11   - All hosts running Kubernetes cluster should have same OS otherwise there
12     is possibility of misbehavement.
13 options:
14   server:
15     required: true
16     description:
17       - Url of rancher server i.e. "http://10.0.0.1:8080".
18   name:
19     required: true
20     descritpion:
21       - Name of the environment to create/remove.
22   descr:
23     description:
24       - Description of environment to create.
25   state:
26     description:
27       - If "present" environment will be created or setup depending if it exists.
28         With multiple environments with same name expect error.
29         If "absent" environment will be removed. If multiple environments have same
30         name all will be deleted.
31     default: present
32     choices: [present, absent]
33   delete_not_k8s:
34     description:
35       - Indicates if environments with different orchestration than Kubernetes should
36         be deleted.
37     type: bool
38     default: yes
39   delete_other_k8s:
40     description:
41       - Indicates if environments with different name than specified should
42         be deleted.
43     type: bool
44     default: no
45   force:
46     description:
47       - Indicates if environment should be deleted and recreated.
48     type: bool
49     default: yes
50   host_os:
51     required: true
52     description:
53       - OS (family from ansible_os_family variable) of the hosts running cluster. If
54         "RedHat" then datavolume fix will be applied.
55         Fix described here:
56           https://github.com/rancher/rancher/issues/10015
57 '''
58
59 import json
60 import time
61
62 import requests
63 from ansible.module_utils.basic import AnsibleModule
64
65
66
67 def get_existing_environments(rancher_address):
68     req = requests.get('{}/v2-beta/projects'.format(rancher_address))
69     envs = req.json()['data']
70     return envs
71
72
73 def not_k8s_ids(environments):
74     envs = filter(lambda x: x['orchestration'] != 'kubernetes', environments)
75     return [env['id'] for env in envs]
76
77
78 def other_k8s_ids(environments, name):
79     envs = filter(lambda x: x['orchestration'] == 'kubernetes' and x['name'] != name,
80                   environments)
81     return [env['id'] for env in envs]
82
83
84 def env_ids_by_name(environments, name):
85     envs = filter(lambda x: x['name'] == name, environments)
86     return [env['id'] for env in envs]
87
88
89 def env_info_by_id(environments, env_id):
90     env = filter(lambda x: x['id'] == env_id, environments)
91     return [{'id': x['id'], 'name': x['name']} for x in env][0]
92
93
94 def delete_multiple_environments(rancher_address, env_ids):
95     deleted = []
96     for env_id in env_ids:
97         deleted.append(delete_environment(rancher_address, env_id))
98     return deleted
99
100
101 def delete_environment(rancher_address, env_id):
102     req = requests.delete('{}/v2-beta/projects/{}'.format(rancher_address, env_id))
103     deleted = req.json()['data'][0]
104     return {'id': deleted['id'],
105             'name': deleted['name'],
106             'orchestration': deleted['orchestration']}
107
108
109 def create_k8s_environment(rancher_address, name, descr):
110     k8s_template_id = None
111     for _ in range(10):
112         k8s_template = requests.get(
113             '{}/v2-beta/projecttemplates?name=Kubernetes'.format(rancher_address)).json()
114         if k8s_template['data']:
115             k8s_template_id = k8s_template['data'][0]['id']
116             break
117         time.sleep(3)
118     if k8s_template_id is None:
119         raise ValueError('Template for kubernetes not found.')
120     body = {
121         'name': name,
122         'description': descr,
123         'projectTemplateId': k8s_template_id,
124         'allowSystemRole': False,
125         'members': [],
126         'virtualMachine': False,
127         'servicesPortRange': None,
128         'projectLinks': []
129     }
130
131     body_json = json.dumps(body)
132     req = requests.post('{}/v2-beta/projects'.format(rancher_address), data=body_json)
133     created = req.json()
134     return {'id': created['id'], 'name': created['name']}
135
136
137 def get_kubelet_service(rancher_address, env_id):
138     for _ in range(10):
139         response = requests.get(
140             '{}/v2-beta/projects/{}/services/?name=kubelet'.format(rancher_address,
141                                                                    env_id))
142
143         if response.status_code >= 400:
144             # too early or too late for obtaining data
145             # small delay will improve our chances to collect it
146             time.sleep(1)
147             continue
148
149         content = response.json()
150
151         if content['data']:
152             return content['data'][0]
153
154         # this is unfortunate, response from service api received but data
155         # not available, lets try again
156         time.sleep(5)
157
158     return None
159
160
161 def fix_datavolume_rhel(rancher_address, env_id):
162     kubelet_svc = get_kubelet_service(rancher_address, env_id)
163     if kubelet_svc:
164         try:
165             data_volume_index = kubelet_svc['launchConfig']['dataVolumes'].index(
166                 '/sys:/sys:ro,rprivate')
167         except ValueError:
168             return 'Already changed'
169         kubelet_svc['launchConfig']['dataVolumes'][
170             data_volume_index] = '/sys/fs/cgroup:/sys/fs/cgroup:ro,rprivate'
171         body = {
172             'inServiceStrategy': {
173                 'batchSize': 1,
174                 'intervalMillis': 2000,
175                 'startFirst': False,
176                 'launchConfig': kubelet_svc['launchConfig'],
177                 'secondaryLaunchConfigs': []
178             }
179         }
180         body_json = json.dumps(body)
181         requests.post(
182             '{}/v2-beta/projects/{}/services/{}?action=upgrade'.format(rancher_address,
183                                                                        env_id,
184                                                                        kubelet_svc[
185                                                                            'id']),
186             data=body_json)
187         for _ in range(10):
188             req_svc = requests.get(
189                 '{}/v2-beta/projects/{}/services/{}'.format(rancher_address, env_id,
190                                                             kubelet_svc['id']))
191             req_svc_content = req_svc.json()
192             if 'finishupgrade' in req_svc_content['actions']:
193                 req_finish = requests.post(
194                     req_svc_content['actions']['finishupgrade'])
195                 return {
196                     'dataVolumes': req_finish.json()['upgrade']['inServiceStrategy'][
197                         'launchConfig']['dataVolumes']}
198             time.sleep(5)
199     else:
200         raise ValueError('Could not get kubelet service')
201
202
203 def create_registration_tokens(rancher_address, env_id):
204     body = {'name': str(env_id)}
205     body_json = json.dumps(body)
206     response = requests.post(
207         '{}/v2-beta/projects/{}/registrationtokens'.format(rancher_address, env_id,
208                                                            data=body_json))
209     for _ in range(10):
210         tokens = requests.get(response.json()['links']['self'])
211         tokens_content = tokens.json()
212         if tokens_content['image'] is not None and tokens_content[
213                 'registrationUrl'] is not None:
214             return {'image': tokens_content['image'],
215                     'reg_url': tokens_content['registrationUrl']}
216         time.sleep(3)
217     return None
218
219
220 def get_registration_tokens(rancher_address, env_id):
221     reg_tokens = requests.get(
222         '{}/v2-beta/projects/{}/registrationtokens'.format(rancher_address, env_id))
223     reg_tokens_content = reg_tokens.json()
224     tokens = reg_tokens_content['data']
225     if not tokens:
226         return None
227     return {'image': tokens[0]['image'], 'reg_url': tokens[0]['registrationUrl']}
228
229
230 def create_apikey(rancher_address, env_id):
231     body = {
232         'name': 'kubectl_env_{}'.format(env_id),
233         'description': "Provides access to kubectl"
234     }
235     body_json = json.dumps(body)
236     apikey_req = requests.post(
237         '{}/v2-beta/apikey'.format(rancher_address, env_id, data=body_json))
238     apikey_content = apikey_req.json()
239     return {'public': apikey_content['publicValue'],
240             'private': apikey_content['secretValue']}
241
242
243 def run_module():
244     module = AnsibleModule(
245         argument_spec=dict(
246             server=dict(type='str', required=True),
247             name=dict(type='str', required=True),
248             descr=dict(type='str'),
249             state=dict(type='str', choices=['present', 'absent'], default='present'),
250             delete_other_k8s=dict(type='bool', default=False),
251             delete_not_k8s=dict(type='bool', default=True),
252             force=dict(type='bool', default=True),
253             host_os=dict(type='str', required=True)
254         )
255     )
256
257     params = module.params
258     rancher_address = params['server']
259     name = params['name']
260     descr = params['descr']
261     delete_not_k8s = params['delete_not_k8s']
262     delete_other_k8s = params['delete_other_k8s']
263     force = params['force']
264     host_os = params['host_os']
265     state = params['state']
266
267     existing_envs = get_existing_environments(rancher_address)
268     same_name_ids = env_ids_by_name(existing_envs, name)
269
270     to_delete_ids = []
271     changes = {}
272
273     if delete_other_k8s:
274         to_delete_ids += other_k8s_ids(existing_envs, name)
275
276     if delete_not_k8s:
277         to_delete_ids += not_k8s_ids(existing_envs)
278     if force or state == 'absent':
279         to_delete_ids += same_name_ids
280
281     deleted = delete_multiple_environments(rancher_address, to_delete_ids)
282
283     if deleted:
284         changes['deleted'] = deleted
285         if state == 'absent':
286             module.exit_json(changed=True, deleted=changes['deleted'])
287     else:
288         if state == 'absent':
289             module.exit_json(changed=False)
290
291     if len(same_name_ids) > 1 and not force:
292         module.fail_json(msg='Multiple environments with same name. '
293                              'Use "force: yes" to delete '
294                              'all environments with same name.')
295
296     if same_name_ids and not force:
297         changes['environment'] = env_info_by_id(existing_envs, same_name_ids[0])
298         if host_os == 'RedHat':
299             try:
300                 rhel_fix = fix_datavolume_rhel(rancher_address, same_name_ids[0])
301                 changes['rhel_fix'] = rhel_fix
302             except ValueError as err:
303                 module.fail_json(
304                     msg='Error: {} Try to recreate k8s environment.'.format(err))
305
306         reg_tokens = get_registration_tokens(rancher_address, same_name_ids[0])
307         if not reg_tokens:
308             reg_tokens = create_registration_tokens(rancher_address, same_name_ids[0])
309         changes['registration_tokens'] = reg_tokens
310
311         apikey = create_apikey(rancher_address, same_name_ids[0])
312         changes['apikey'] = apikey
313         module.exit_json(changed=True, data=changes,
314                          msg='New environment was not created. Only set up was done')
315     try:
316         new_env = create_k8s_environment(rancher_address, name, descr)
317     except ValueError as err:
318         module.fail_json(msg='Error: {} Try to recreate k8s environment.'.format(err))
319
320     if host_os == 'RedHat':
321         try:
322             rhel_fix = fix_datavolume_rhel(rancher_address, new_env['id'])
323             changes['rhel_fix'] = rhel_fix
324         except ValueError as err:
325             module.fail_json(msg='Error: {} Try to recreate k8s environment.'.format(
326                 err))
327
328     reg_tokens = create_registration_tokens(rancher_address, new_env['id'])
329
330     apikey = create_apikey(rancher_address, new_env['id'])
331
332     changes['environment'] = new_env
333     changes['registration_tokens'] = reg_tokens
334     changes['apikey'] = apikey
335
336     module.exit_json(changed=True, data=changes)
337
338
339 if __name__ == '__main__':
340     run_module()