blob: d3d8ac02dcbd8545bf23f2f40e5da176f21e78cf [file] [log] [blame]
Petr Ospalýa0eff592018-12-19 13:54:09 +01001#!/usr/bin/python
2
3DOCUMENTATION='''
4---
5module: rancher_k8s_environment
6description:
7 - This module will create or delete Kubernetes environment.
8 - It will also delete other environments when variables are set accordingly.
9notes:
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.
13options:
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
59import json
60import time
61
62import requests
63from ansible.module_utils.basic import AnsibleModule
64
65
66
67def 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
73def not_k8s_ids(environments):
74 envs = filter(lambda x: x['orchestration'] != 'kubernetes', environments)
75 return [env['id'] for env in envs]
76
77
78def 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
84def 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
89def 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
94def 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
101def 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
109def 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
137def 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
161def 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
203def 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
220def 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
230def 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
243def 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
339if __name__ == '__main__':
340 run_module()
341