Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 1 | #!/usr/bin/python |
| 2 | |
| 3 | from ansible.module_utils.basic import AnsibleModule |
| 4 | |
| 5 | import requests |
| 6 | import json |
| 7 | import functools |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 8 | import time |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 9 | |
| 10 | DOCUMENTATION = """ |
| 11 | --- |
| 12 | module: rancher1_api |
| 13 | short_description: Client library for rancher API |
| 14 | description: |
| 15 | - This module modifies a rancher 1.6 using it's API (v1). |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 16 | - It supports some rancher features by the virtue of a 'mode'. |
| 17 | - 'modes' hide from you some necessary cruft and expose you to the only |
| 18 | important and interestig variables wich must be set. The mode mechanism |
| 19 | makes this module more easy to use and you don't have to create an |
| 20 | unnecessary boilerplate for the API. |
| 21 | - Only a few modes are/will be implemented so far - as they are/will be |
| 22 | needed. In the future the 'raw' mode can be added to enable you to craft |
| 23 | your own API requests, but that would be on the same level of a user |
| 24 | experience as running curl commands, and because the rancher 1.6 is already |
| 25 | obsoleted by the project, it would be a wasted effort. |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 26 | options: |
| 27 | rancher: |
| 28 | description: |
| 29 | - The domain name or the IP address and the port of the rancher |
| 30 | where API is exposed. |
| 31 | - For example: http://10.0.0.1:8080 |
| 32 | required: true |
| 33 | aliases: |
| 34 | - server |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 35 | - rancher_server |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 36 | - rancher_api |
| 37 | - api |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 38 | account_key: |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 39 | description: |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 40 | - The public and secret part of the API key-pair separated by colon. |
| 41 | - You can find all your keys in web UI. |
| 42 | - For example: |
| 43 | B1716C4133D3825051CB:3P2eb3QhokFKYUiXRNZLxvGNSRYgh6LHjuMicCHQ |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 44 | required: false |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 45 | mode: |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 46 | description: |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 47 | - A recognized mode how to deal with some concrete configuration task |
| 48 | in rancher API to ease the usage. |
| 49 | - The implemented modes so far are: |
| 50 | 'settings': |
| 51 | Many options under <api_server>/v1/settings API url and some can |
| 52 | be seen also under advanced options in the web UI. |
| 53 | 'access_control': |
| 54 | It setups user and password for the account (defaults to 'admin') |
| 55 | and it enables the local authentication - so the web UI and API |
| 56 | will require username/password (UI) or apikey (API). |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 57 | required: true |
| 58 | aliases: |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 59 | - rancher_mode |
| 60 | - api_mode |
| 61 | choices: |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 62 | - settings |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 63 | - access_control |
| 64 | data: |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 65 | description: |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 66 | - Dictionary with key/value pairs. The actual names and meaning of pairs |
| 67 | depends on the used mode. |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 68 | - settings mode: |
| 69 | option - Option/path in JSON API (url). |
| 70 | value - A new value to replace the current one. |
| 71 | - access_control mode: |
| 72 | account_id - The unique ID of the account - the default rancher admin |
| 73 | has '1a1'. Better way would be to just create arbitrary username and |
| 74 | set credentials for that, but due to time constraints, the route with |
| 75 | an ID is simpler. The designated '1a1' could be hardcoded and hidden |
| 76 | but if the user will want to use some other account (there are many), |
| 77 | then it can be just changed to some other ID. |
| 78 | password - A new password in a plaintext. |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 79 | required: true |
| 80 | timeout: |
| 81 | description: |
| 82 | - How long in seconds to wait for a response before raising error |
| 83 | required: false |
| 84 | default: 10.0 |
| 85 | """ |
| 86 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 87 | default_timeout = 10.0 |
| 88 | |
| 89 | |
| 90 | class ModeError(Exception): |
| 91 | pass |
| 92 | |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 93 | |
| 94 | def _decorate_rancher_api_request(request_method): |
| 95 | |
| 96 | @functools.wraps(request_method) |
| 97 | def wrap_request(*args, **kwargs): |
| 98 | |
| 99 | response = request_method(*args, **kwargs) |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 100 | authorized = True |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 101 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 102 | if response.status_code == 401: |
| 103 | authorized = False |
| 104 | elif response.status_code != requests.codes.ok: |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 105 | response.raise_for_status() |
| 106 | |
| 107 | try: |
| 108 | json_data = response.json() |
| 109 | except Exception: |
| 110 | json_data = None |
| 111 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 112 | return json_data, authorized |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 113 | |
| 114 | return wrap_request |
| 115 | |
| 116 | |
| 117 | @_decorate_rancher_api_request |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 118 | def get_rancher_api_value(url, headers=None, timeout=default_timeout, |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 119 | username=None, password=None): |
| 120 | |
| 121 | if username and password: |
| 122 | return requests.get(url, headers=headers, |
| 123 | timeout=timeout, |
| 124 | allow_redirects=False, |
| 125 | auth=(username, password)) |
| 126 | else: |
| 127 | return requests.get(url, headers=headers, |
| 128 | timeout=timeout, |
| 129 | allow_redirects=False) |
| 130 | |
| 131 | |
| 132 | @_decorate_rancher_api_request |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 133 | def set_rancher_api_value(url, payload, headers=None, timeout=default_timeout, |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 134 | username=None, password=None, method='PUT'): |
| 135 | |
| 136 | if method == 'PUT': |
| 137 | request_set_method = requests.put |
| 138 | elif method == 'POST': |
| 139 | request_set_method = requests.post |
| 140 | else: |
| 141 | raise ModeError('ERROR: Wrong request method: %s' % str(method)) |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 142 | |
| 143 | if username and password: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 144 | return request_set_method(url, headers=headers, |
| 145 | timeout=timeout, |
| 146 | allow_redirects=False, |
| 147 | data=json.dumps(payload), |
| 148 | auth=(username, password)) |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 149 | else: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 150 | return request_set_method(url, headers=headers, |
| 151 | timeout=timeout, |
| 152 | allow_redirects=False, |
| 153 | data=json.dumps(payload)) |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 154 | |
| 155 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 156 | def create_rancher_api_url(server, mode, option): |
| 157 | request_url = server.strip('/') + '/v1/' |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 158 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 159 | if mode == 'raw': |
| 160 | request_url += option.strip('/') |
| 161 | elif mode == 'settings': |
| 162 | request_url += 'settings/' + option.strip('/') |
| 163 | elif mode == 'access_control': |
| 164 | request_url += option.strip('/') |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 165 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 166 | return request_url |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 167 | |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 168 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 169 | def get_keypair(keypair): |
| 170 | if keypair: |
| 171 | keypair = keypair.split(':') |
| 172 | if len(keypair) == 2: |
| 173 | return keypair[0], keypair[1] |
| 174 | |
| 175 | return None, None |
| 176 | |
| 177 | |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 178 | def mode_access_control(api_url, data=None, headers=None, |
| 179 | timeout=default_timeout, access_key=None, |
| 180 | secret_key=None, dry_run=False): |
| 181 | |
| 182 | # returns true if local auth was enabled or false if passwd changed |
| 183 | def is_admin_enabled(json_data, password): |
| 184 | try: |
| 185 | if json_data['type'] == "localAuthConfig" and \ |
| 186 | json_data['accessMode'] == "unrestricted" and \ |
| 187 | json_data['username'] == "admin" and \ |
| 188 | json_data['password'] == password and \ |
| 189 | json_data['enabled']: |
| 190 | return True |
| 191 | except Exception: |
| 192 | pass |
| 193 | |
| 194 | try: |
| 195 | if json_data['type'] == "error" and \ |
| 196 | json_data['code'] == "IncorrectPassword": |
| 197 | return False |
| 198 | except Exception: |
| 199 | pass |
| 200 | |
| 201 | # this should never happen |
| 202 | raise ModeError('ERROR: Unknown status of the local authentication') |
| 203 | |
| 204 | def create_localauth_payload(password): |
| 205 | payload = { |
| 206 | "enabled": True, |
| 207 | "accessMode": "unrestricted", |
| 208 | "username": "admin", |
| 209 | "password": password |
| 210 | } |
| 211 | |
| 212 | return payload |
| 213 | |
| 214 | def get_admin_password_id(): |
| 215 | # assemble request URL |
| 216 | request_url = api_url + 'accounts/' + data['account_id'].strip('/') \ |
| 217 | + '/credentials' |
| 218 | |
| 219 | # API get current value |
| 220 | try: |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 221 | json_response, authorized = \ |
| 222 | get_rancher_api_value(request_url, |
| 223 | username=access_key, |
| 224 | password=secret_key, |
| 225 | headers=headers, |
| 226 | timeout=timeout) |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 227 | except requests.HTTPError as e: |
| 228 | raise ModeError(str(e)) |
| 229 | except requests.Timeout as e: |
| 230 | raise ModeError(str(e)) |
| 231 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 232 | if not authorized or not json_response: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 233 | raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in ' |
| 234 | + 'the response') |
| 235 | |
| 236 | try: |
| 237 | for item in json_response['data']: |
| 238 | if item['type'] == 'password' and \ |
| 239 | item['accountId'] == data['account_id']: |
| 240 | return item['id'] |
| 241 | except Exception: |
| 242 | pass |
| 243 | |
| 244 | return None |
| 245 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 246 | def remove_password(password_id, action): |
| 247 | if action == 'deactivate': |
| 248 | action_status = 'deactivating' |
| 249 | elif action == 'remove': |
| 250 | action_status = 'removing' |
| 251 | |
| 252 | request_url = api_url + 'passwords/' + password_id + \ |
| 253 | '/?action=' + action |
| 254 | |
| 255 | try: |
| 256 | json_response, authorized = \ |
| 257 | set_rancher_api_value(request_url, |
| 258 | {}, |
| 259 | username=access_key, |
| 260 | password=secret_key, |
| 261 | headers=headers, |
| 262 | method='POST', |
| 263 | timeout=timeout) |
| 264 | except requests.HTTPError as e: |
| 265 | raise ModeError(str(e)) |
| 266 | except requests.Timeout as e: |
| 267 | raise ModeError(str(e)) |
| 268 | |
| 269 | if not authorized or not json_response: |
| 270 | raise ModeError('ERROR: BAD RESPONSE (POST) - no json value in ' |
| 271 | + 'the response') |
| 272 | |
| 273 | if json_response['state'] != action_status: |
| 274 | raise ModeError("ERROR: Failed to '%s' the password: %s" % |
| 275 | (action, password_id)) |
| 276 | |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 277 | # check if data contains all required fields |
| 278 | try: |
| 279 | if not isinstance(data['account_id'], str) or data['account_id'] == '': |
| 280 | raise ModeError("ERROR: 'account_id' must contain an id of the " |
| 281 | + "affected account") |
| 282 | except KeyError: |
| 283 | raise ModeError("ERROR: Mode 'access_control' requires the field: " |
| 284 | + "'account_id': %s" % str(data)) |
| 285 | try: |
| 286 | if not isinstance(data['password'], str) or data['password'] == '': |
| 287 | raise ModeError("ERROR: 'password' must contain some password") |
| 288 | except KeyError: |
| 289 | raise ModeError("ERROR: Mode 'access_control' requires the field: " |
| 290 | + "'password': %s" % str(data)) |
| 291 | |
| 292 | # assemble request URL |
| 293 | request_url = api_url + 'localauthconfigs' |
| 294 | |
| 295 | # API get current value |
| 296 | try: |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 297 | json_response, authorized = \ |
| 298 | get_rancher_api_value(request_url, |
| 299 | username=access_key, |
| 300 | password=secret_key, |
| 301 | headers=headers, |
| 302 | timeout=timeout) |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 303 | except requests.HTTPError as e: |
| 304 | raise ModeError(str(e)) |
| 305 | except requests.Timeout as e: |
| 306 | raise ModeError(str(e)) |
| 307 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 308 | if not authorized or not json_response: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 309 | raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in the ' |
| 310 | + 'response') |
| 311 | |
| 312 | # we will check if local auth is enabled or not |
| 313 | enabled = False |
| 314 | try: |
| 315 | for item in json_response['data']: |
| 316 | if item['type'] == 'localAuthConfig' and \ |
| 317 | item['accessMode'] == 'unrestricted' and \ |
| 318 | item['enabled']: |
| 319 | enabled = True |
| 320 | break |
| 321 | except Exception: |
| 322 | enabled = False |
| 323 | |
| 324 | if dry_run: |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 325 | # we will not set anything and only signal potential change |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 326 | if enabled: |
| 327 | changed = False |
| 328 | else: |
| 329 | changed = True |
| 330 | else: |
| 331 | # we will try to enable again with the same password |
| 332 | localauth_payload = create_localauth_payload(data['password']) |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 333 | try: |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 334 | json_response, authorized = \ |
| 335 | set_rancher_api_value(request_url, |
| 336 | localauth_payload, |
| 337 | username=access_key, |
| 338 | password=secret_key, |
| 339 | headers=headers, |
| 340 | method='POST', |
| 341 | timeout=timeout) |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 342 | except requests.HTTPError as e: |
| 343 | raise ModeError(str(e)) |
| 344 | except requests.Timeout as e: |
| 345 | raise ModeError(str(e)) |
| 346 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 347 | # here we ignore authorized status - we will try to reset password |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 348 | if not json_response: |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 349 | raise ModeError('ERROR: BAD RESPONSE (POST) - no json value in ' |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 350 | + 'the response') |
| 351 | |
| 352 | # we check if the admin was already set or not... |
| 353 | if enabled and is_admin_enabled(json_response, data['password']): |
| 354 | # it was enabled before and password is the same - no change |
| 355 | changed = False |
| 356 | elif is_admin_enabled(json_response, data['password']): |
| 357 | # we enabled it for the first time |
| 358 | changed = True |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 359 | # ...and reset password if needed (unauthorized access) |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 360 | else: |
| 361 | # local auth is enabled but the password differs |
| 362 | # we must reset the admin's password |
| 363 | password_id = get_admin_password_id() |
| 364 | |
| 365 | if password_id is None: |
| 366 | raise ModeError("ERROR: admin's password is set, but we " |
| 367 | + "cannot identify it") |
| 368 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 369 | # One of the way to reset the password is to remove it first |
| 370 | # TODO - refactor this |
| 371 | remove_password(password_id, 'deactivate') |
| 372 | time.sleep(2) |
| 373 | remove_password(password_id, 'remove') |
| 374 | time.sleep(1) |
| 375 | |
| 376 | try: |
| 377 | json_response, authorized = \ |
| 378 | set_rancher_api_value(request_url, |
| 379 | localauth_payload, |
| 380 | username=access_key, |
| 381 | password=secret_key, |
| 382 | headers=headers, |
| 383 | method='POST', |
| 384 | timeout=timeout) |
| 385 | except requests.HTTPError as e: |
| 386 | raise ModeError(str(e)) |
| 387 | except requests.Timeout as e: |
| 388 | raise ModeError(str(e)) |
| 389 | |
| 390 | # finally we signal the change |
| 391 | changed = True |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 392 | |
| 393 | if changed: |
| 394 | msg = "Local authentication is enabled, admin has assigned password" |
| 395 | else: |
| 396 | msg = "Local authentication was already enabled, admin's password " \ |
| 397 | + "is unchanged" |
| 398 | |
| 399 | return changed, msg |
| 400 | |
| 401 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 402 | def mode_settings(api_url, data=None, headers=None, timeout=default_timeout, |
| 403 | access_key=None, secret_key=None, dry_run=False): |
| 404 | |
| 405 | def is_valid_rancher_api_option(json_data): |
| 406 | |
| 407 | try: |
| 408 | api_activeValue = json_data['activeValue'] |
| 409 | api_source = json_data['source'] |
| 410 | except Exception: |
| 411 | return False |
| 412 | |
| 413 | if api_activeValue is None and api_source is None: |
| 414 | return False |
| 415 | |
| 416 | return True |
| 417 | |
| 418 | def create_rancher_api_payload(json_data, new_value): |
| 419 | |
| 420 | payload = {} |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 421 | differs = False |
| 422 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 423 | try: |
| 424 | api_id = json_data['id'] |
| 425 | api_activeValue = json_data['activeValue'] |
| 426 | api_name = json_data['name'] |
| 427 | api_source = json_data['source'] |
| 428 | except Exception: |
| 429 | raise ValueError |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 430 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 431 | payload.update({"activeValue": api_activeValue, |
| 432 | "id": api_id, |
| 433 | "name": api_name, |
| 434 | "source": api_source, |
| 435 | "value": new_value}) |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 436 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 437 | if api_activeValue != new_value: |
| 438 | differs = True |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 439 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 440 | return differs, payload |
| 441 | |
| 442 | # check if data contains all required fields |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 443 | try: |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 444 | if not isinstance(data['option'], str) or data['option'] == '': |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 445 | raise ModeError("ERROR: 'option' must contain a name of the " |
| 446 | + "option") |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 447 | except KeyError: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 448 | raise ModeError("ERROR: Mode 'settings' requires the field: 'option': " |
| 449 | + "%s" % str(data)) |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 450 | try: |
| 451 | if not isinstance(data['value'], str) or data['value'] == '': |
| 452 | raise ModeError("ERROR: 'value' must contain a value") |
| 453 | except KeyError: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 454 | raise ModeError("ERROR: Mode 'settings' requires the field: 'value': " |
| 455 | + "%s" % str(data)) |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 456 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 457 | # assemble request URL |
| 458 | request_url = api_url + 'settings/' + data['option'].strip('/') |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 459 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 460 | # API get current value |
| 461 | try: |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 462 | json_response, authorized = \ |
| 463 | get_rancher_api_value(request_url, |
| 464 | username=access_key, |
| 465 | password=secret_key, |
| 466 | headers=headers, |
| 467 | timeout=timeout) |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 468 | except requests.HTTPError as e: |
| 469 | raise ModeError(str(e)) |
| 470 | except requests.Timeout as e: |
| 471 | raise ModeError(str(e)) |
| 472 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 473 | if not authorized or not json_response: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 474 | raise ModeError('ERROR: BAD RESPONSE (GET) - no json value in the ' |
| 475 | + 'response') |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 476 | |
| 477 | if is_valid_rancher_api_option(json_response): |
| 478 | valid = True |
| 479 | try: |
| 480 | differs, payload = create_rancher_api_payload(json_response, |
| 481 | data['value']) |
| 482 | except ValueError: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 483 | raise ModeError('ERROR: INVALID JSON - missing json values in ' |
| 484 | + 'the response') |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 485 | else: |
| 486 | valid = False |
| 487 | |
| 488 | if valid and differs and dry_run: |
| 489 | # ansible dry-run mode |
| 490 | changed = True |
| 491 | elif valid and differs: |
| 492 | # API set new value |
| 493 | try: |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 494 | json_response, authorized = \ |
| 495 | set_rancher_api_value(request_url, |
| 496 | payload, |
| 497 | username=access_key, |
| 498 | password=secret_key, |
| 499 | headers=headers, |
| 500 | timeout=timeout) |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 501 | except requests.HTTPError as e: |
| 502 | raise ModeError(str(e)) |
| 503 | except requests.Timeout as e: |
| 504 | raise ModeError(str(e)) |
| 505 | |
Petr Ospalý | 3a6558a | 2019-04-08 08:39:41 +0200 | [diff] [blame] | 506 | if not authorized or not json_response: |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 507 | raise ModeError('ERROR: BAD RESPONSE (PUT) - no json value in ' |
| 508 | + 'the response') |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 509 | else: |
| 510 | changed = True |
| 511 | else: |
| 512 | changed = False |
| 513 | |
| 514 | if changed: |
| 515 | msg = "Option '%s' is now set to the new value: %s" \ |
| 516 | % (data['option'], data['value']) |
| 517 | else: |
| 518 | msg = "Option '%s' is unchanged." % (data['option']) |
| 519 | |
| 520 | return changed, msg |
| 521 | |
| 522 | |
| 523 | def mode_handler(server, rancher_mode, data=None, timeout=default_timeout, |
| 524 | account_key=None, dry_run=False): |
| 525 | |
| 526 | changed = False |
| 527 | msg = 'UNKNOWN: UNAPPLICABLE MODE' |
| 528 | |
| 529 | # check API key-pair |
| 530 | if account_key: |
| 531 | access_key, secret_key = get_keypair(account_key) |
| 532 | if not (access_key and secret_key): |
| 533 | raise ModeError('ERROR: INVALID API KEY-PAIR') |
| 534 | |
| 535 | # all requests share these headers |
| 536 | http_headers = {'Content-Type': 'application/json', |
| 537 | 'Accept': 'application/json'} |
| 538 | |
| 539 | # assemble API url |
| 540 | api_url = server.strip('/') + '/v1/' |
| 541 | |
| 542 | if rancher_mode == 'settings': |
| 543 | changed, msg = mode_settings(api_url, data=data, |
| 544 | headers=http_headers, |
| 545 | timeout=timeout, |
| 546 | access_key=access_key, |
| 547 | secret_key=secret_key, |
| 548 | dry_run=dry_run) |
| 549 | elif rancher_mode == 'access_control': |
Petr Ospalý | 72b09b1 | 2019-04-08 04:55:47 +0200 | [diff] [blame] | 550 | changed, msg = mode_access_control(api_url, data=data, |
| 551 | headers=http_headers, |
| 552 | timeout=timeout, |
| 553 | access_key=access_key, |
| 554 | secret_key=secret_key, |
| 555 | dry_run=dry_run) |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 556 | |
| 557 | return changed, msg |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 558 | |
| 559 | |
| 560 | def main(): |
| 561 | module = AnsibleModule( |
| 562 | argument_spec=dict( |
| 563 | rancher=dict(type='str', required=True, |
| 564 | aliases=['server', |
| 565 | 'rancher_api', |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 566 | 'rancher_server', |
| 567 | 'api']), |
| 568 | account_key=dict(type='str', required=False), |
| 569 | mode=dict(required=True, |
| 570 | choices=['settings', 'access_control'], |
| 571 | aliases=['api_mode']), |
| 572 | data=dict(type='dict', required=True), |
| 573 | timeout=dict(type='float', default=default_timeout), |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 574 | ), |
| 575 | supports_check_mode=True |
| 576 | ) |
| 577 | |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 578 | rancher_server = module.params['rancher'] |
| 579 | rancher_account_key = module.params['account_key'] |
| 580 | rancher_mode = module.params['mode'] |
| 581 | rancher_data = module.params['data'] |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 582 | rancher_timeout = module.params['timeout'] |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 583 | |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 584 | try: |
Petr Ospalý | 9dee201 | 2019-04-05 09:57:03 +0200 | [diff] [blame] | 585 | changed, msg = mode_handler(rancher_server, |
| 586 | rancher_mode, |
| 587 | data=rancher_data, |
| 588 | account_key=rancher_account_key, |
| 589 | timeout=rancher_timeout, |
| 590 | dry_run=module.check_mode) |
| 591 | except ModeError as e: |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 592 | module.fail_json(msg=str(e)) |
Petr Ospalý | 2bfe0f9 | 2019-03-26 22:13:00 +0100 | [diff] [blame] | 593 | |
| 594 | module.exit_json(changed=changed, msg=msg) |
| 595 | |
| 596 | |
| 597 | if __name__ == '__main__': |
| 598 | main() |