1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# Copyright: (c) 2018, F5 Networks Inc. 5# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 6 7from __future__ import absolute_import, division, print_function 8__metaclass__ = type 9 10DOCUMENTATION = r''' 11--- 12module: bigip_device_auth_ldap 13short_description: Manage LDAP device authentication settings on BIG-IP 14description: 15 - Manage LDAP device authentication settings on BIG-IP. 16version_added: "1.0.0" 17options: 18 servers: 19 description: 20 - Specifies the LDAP servers the system must use to obtain 21 authentication information. You must specify a server when you 22 create an LDAP configuration object. 23 type: list 24 elements: str 25 port: 26 description: 27 - Specifies the port the system uses for access to the remote host server. 28 - When configuring LDAP device authentication for the first time, the default 29 port is C(389) if this parameter is not specified. 30 type: int 31 remote_directory_tree: 32 description: 33 - Specifies the file location (tree) of the user authentication database on the 34 server. 35 type: str 36 scope: 37 description: 38 - Specifies the level of the remote Active Directory or LDAP directory the 39 system should search for the user authentication. 40 type: str 41 choices: 42 - sub 43 - one 44 - base 45 bind_dn: 46 description: 47 - Specifies the distinguished name for the Active Directory or LDAP server user 48 ID. 49 - The BIG-IP client authentication module does not support Active Directory or 50 LDAP servers that do not perform bind referral when authenticating referred 51 accounts. 52 - Therefore, if you plan to use Active Directory or LDAP as your authentication 53 source and want to use referred accounts, make sure your servers perform bind 54 referral. 55 type: str 56 bind_password: 57 description: 58 - Specifies a password for the Active Directory or LDAP server user ID. 59 type: str 60 user_template: 61 description: 62 - Specifies the distinguished name of the user who is logging on. 63 - You specify the template as a variable that the system replaces with user-specific 64 information during the logon attempt. 65 - For example, you could specify a user template such as C(%s@siterequest.com) or 66 C(uxml:id=%s,ou=people,dc=siterequest,dc=com). 67 - When a user attempts to log on, the system replaces C(%s) with the name the user 68 specified in the Basic Authentication dialog box, and passes that as the 69 distinguished name for the bind operation. 70 - The system passes the associated password as the password for the bind operation. 71 - This field can contain only one C(%s) and cannot contain any other format 72 specifiers. 73 type: str 74 check_member_attr: 75 description: 76 - Checks the member attribute of the user in the remote LDAP or AD group. 77 type: bool 78 ssl: 79 description: 80 - Specifies whether the system uses an SSL port to communicate with the LDAP server. 81 type: str 82 choices: 83 - "yes" 84 - "no" 85 - start-tls 86 ca_cert: 87 description: 88 - Specifies the name of an SSL certificate from a certificate authority (CA). 89 - To remove this value, use the reserved value C(none). 90 type: str 91 aliases: [ ssl_ca_cert ] 92 client_key: 93 description: 94 - Specifies the name of an SSL client key. 95 - To remove this value, use the reserved value C(none). 96 type: str 97 aliases: [ ssl_client_key ] 98 client_cert: 99 description: 100 - Specifies the name of an SSL client certificate. 101 - To remove this value, use the reserved value C(none). 102 type: str 103 aliases: [ ssl_client_cert ] 104 validate_certs: 105 description: 106 - Specifies whether the system checks an SSL peer, as a result of which the 107 system requires and verifies the server certificate. 108 type: bool 109 aliases: [ ssl_check_peer ] 110 login_ldap_attr: 111 description: 112 - Specifies the LDAP directory attribute containing the local user name that is 113 associated with the selected directory entry. 114 - If this parameter is not specified, when configuring LDAP device authentication for the first time, 115 the default port is C(samaccountname). 116 type: str 117 fallback_to_local: 118 description: 119 - Specifies the system uses the Local authentication method if the remote 120 authentication method is not available. 121 - Option only available on C(TMOS 13.0.0) and above. 122 type: bool 123 state: 124 description: 125 - When C(present), ensures the device authentication method exists. 126 - When C(absent), ensures the device authentication method does not exist. 127 - When C(state) equal to (absent), before you can delete the LDAP configuration, the system must set auth to 128 some alternative. The system ships with a system auth called C(local), therefore the system authentication type 129 will be set to that value on the device upon removal of LDAP configuration. 130 type: str 131 choices: 132 - present 133 - absent 134 default: present 135 update_password: 136 description: 137 - C(always) will always update the C(bind_password). 138 - C(on_create) will only set the C(bind_password) for newly created authentication 139 mechanisms. 140 type: str 141 choices: 142 - always 143 - on_create 144 default: always 145 use_for_auth: 146 description: 147 - Specifies whether or not this auth source is put in use on the system. 148 - If C(yes), the module sets the current system auth type to the value of C(ldap). 149 - If C(no), the module sets the authentication type to C(local), similar behavior to when C(state) is C(absent), 150 without removing the configured LDAP resource. 151 type: bool 152extends_documentation_fragment: f5networks.f5_modules.f5 153author: 154 - Tim Rupp (@caphrim007) 155 - Wojciech Wypior (@wojtek0806) 156''' 157 158EXAMPLES = r''' 159- name: Create an LDAP authentication object 160 bigip_device_auth_ldap: 161 name: foo 162 provider: 163 password: secret 164 server: lb.mydomain.com 165 user: admin 166 delegate_to: localhost 167''' 168 169RETURN = r''' 170servers: 171 description: LDAP servers used by the system to obtain authentication information. 172 returned: changed 173 type: list 174 sample: ['192.168.1.1', '192.168.1.2'] 175port: 176 description: The port the system uses for access to the remote LDAP server. 177 returned: changed 178 type: int 179 sample: 389 180remote_directory_tree: 181 description: File location (tree) of the user authentication database on the server. 182 returned: changed 183 type: str 184 sample: "CN=Users,DC=FOOBAR,DC=LOCAL" 185scope: 186 description: The level of the remote Active Directory or LDAP directory searched for user authentication. 187 returned: changed 188 type: str 189 sample: base 190bind_dn: 191 description: The distinguished name for the Active Directory or LDAP server user ID. 192 returned: changed 193 type: str 194 sample: "user@foobar.local" 195user_template: 196 description: The distinguished name of the user who is logging on. 197 returned: changed 198 type: str 199 sample: "uid=%s,ou=people,dc=foobar,dc=local" 200check_member_attr: 201 description: The user's member attribute in the remote LDAP or AD group. 202 returned: changed 203 type: bool 204 sample: yes 205ssl: 206 description: Specifies whether the system uses an SSL port to communicate with the LDAP server. 207 returned: changed 208 type: str 209 sample: start-tls 210ca_cert: 211 description: The name of an SSL certificate from a certificate authority. 212 returned: changed 213 type: str 214 sample: My-Trusted-CA-Bundle.crt 215client_key: 216 description: The name of an SSL client key. 217 returned: changed 218 type: str 219 sample: MyKey.key 220client_cert: 221 description: The name of an SSL client certificate. 222 returned: changed 223 type: str 224 sample: MyCert.crt 225validate_certs: 226 description: Indicates if the system checks an SSL peer. 227 returned: changed 228 type: bool 229 sample: yes 230login_ldap_attr: 231 description: The LDAP directory attribute containing the local user name associated with the selected directory entry. 232 returned: changed 233 type: str 234 sample: samaccountname 235fallback_to_local: 236 description: Specifies the system uses the Local authentication method as fallback 237 returned: changed 238 type: bool 239 sample: yes 240''' 241from datetime import datetime 242from ansible.module_utils.basic import AnsibleModule 243 244from ..module_utils.bigip import F5RestClient 245from ..module_utils.common import ( 246 F5ModuleError, AnsibleF5Parameters, transform_name, f5_argument_spec, flatten_boolean, fq_name 247) 248from ..module_utils.compare import cmp_str_with_none 249from ..module_utils.icontrol import tmos_version 250from ..module_utils.teem import send_teem 251 252 253class Parameters(AnsibleF5Parameters): 254 api_map = { 255 'bindDn': 'bind_dn', 256 'bindPw': 'bind_password', 257 'userTemplate': 'user_template', 258 'fallback': 'fallback_to_local', 259 'loginAttribute': 'login_ldap_attr', 260 'sslCheckPeer': 'validate_certs', 261 'sslClientCert': 'client_cert', 262 'sslClientKey': 'client_key', 263 'sslCaCertFile': 'ca_cert', 264 'checkRolesGroup': 'check_member_attr', 265 'searchBaseDn': 'remote_directory_tree', 266 } 267 268 api_attributes = [ 269 'bindDn', 270 'bindPw', 271 'checkRolesGroup', 272 'loginAttribute', 273 'port', 274 'scope', 275 'searchBaseDn', 276 'servers', 277 'ssl', 278 'sslCaCertFile', 279 'sslCheckPeer', 280 'sslClientCert', 281 'sslClientKey', 282 'userTemplate', 283 ] 284 285 returnables = [ 286 'bind_dn', 287 'bind_password', 288 'check_member_attr', 289 'fallback_to_local', 290 'login_ldap_attr', 291 'port', 292 'remote_directory_tree', 293 'scope', 294 'servers', 295 'ssl', 296 'ca_cert', 297 'validate_certs', 298 'client_cert', 299 'client_key', 300 'user_template', 301 ] 302 303 updatables = [ 304 'bind_dn', 305 'bind_password', 306 'check_member_attr', 307 'fallback_to_local', 308 'login_ldap_attr', 309 'port', 310 'remote_directory_tree', 311 'scope', 312 'servers', 313 'ssl', 314 'ca_cert', 315 'validate_certs', 316 'client_cert', 317 'client_key', 318 'user_template', 319 'auth_source', 320 ] 321 322 @property 323 def ca_cert(self): 324 if self._values['ca_cert'] is None: 325 return None 326 elif self._values['ca_cert'] in ['none', '']: 327 return '' 328 return fq_name(self.partition, self._values['ca_cert']) 329 330 @property 331 def client_key(self): 332 if self._values['client_key'] is None: 333 return None 334 elif self._values['client_key'] in ['none', '']: 335 return '' 336 return fq_name(self.partition, self._values['client_key']) 337 338 @property 339 def client_cert(self): 340 if self._values['client_cert'] is None: 341 return None 342 elif self._values['client_cert'] in ['none', '']: 343 return '' 344 return fq_name(self.partition, self._values['client_cert']) 345 346 @property 347 def validate_certs(self): 348 return flatten_boolean(self._values['validate_certs']) 349 350 @property 351 def check_member_attr(self): 352 return flatten_boolean(self._values['check_member_attr']) 353 354 @property 355 def login_ldap_attr(self): 356 if self._values['login_ldap_attr'] is None: 357 return None 358 elif self._values['login_ldap_attr'] in ['none', '']: 359 return '' 360 return self._values['login_ldap_attr'] 361 362 @property 363 def user_template(self): 364 if self._values['user_template'] is None: 365 return None 366 elif self._values['user_template'] in ['none', '']: 367 return '' 368 return self._values['user_template'] 369 370 @property 371 def ssl(self): 372 if self._values['ssl'] is None: 373 return None 374 elif self._values['ssl'] == 'start-tls': 375 return 'start-tls' 376 return flatten_boolean(self._values['ssl']) 377 378 @property 379 def fallback_to_local(self): 380 return flatten_boolean(self._values['fallback_to_local']) 381 382 383class ApiParameters(Parameters): 384 pass 385 386 387class ModuleParameters(Parameters): 388 @property 389 def use_for_auth(self): 390 return flatten_boolean(self._values['use_for_auth']) 391 392 @property 393 def auth_source(self): 394 if self._values['use_for_auth'] is None: 395 return None 396 if self.use_for_auth == 'yes': 397 return 'ldap' 398 if self.use_for_auth == 'no': 399 return 'local' 400 401 402class Changes(Parameters): 403 def to_return(self): 404 result = {} 405 try: 406 for returnable in self.returnables: 407 result[returnable] = getattr(self, returnable) 408 result = self._filter_params(result) 409 except Exception: 410 raise 411 return result 412 413 414class UsableChanges(Changes): 415 @property 416 def validate_certs(self): 417 if self._values['validate_certs'] is None: 418 return None 419 elif self._values['validate_certs'] == 'yes': 420 return 'enabled' 421 return 'disabled' 422 423 @property 424 def fallback_to_local(self): 425 if self._values['fallback_to_local'] is None: 426 return None 427 elif self._values['fallback_to_local'] == 'yes': 428 return 'true' 429 return 'false' 430 431 @property 432 def check_member_attr(self): 433 if self._values['check_member_attr'] is None: 434 return None 435 elif self._values['check_member_attr'] == 'yes': 436 return 'enabled' 437 return 'disabled' 438 439 @property 440 def ssl(self): 441 if self._values['ssl'] is None: 442 return None 443 elif self._values['ssl'] == 'start-tls': 444 return 'start-tls' 445 elif self._values['ssl'] == 'yes': 446 return 'enabled' 447 return 'disabled' 448 449 450class ReportableChanges(Changes): 451 @property 452 def bind_password(self): 453 return None 454 455 @property 456 def validate_certs(self): 457 return flatten_boolean(self._values['validate_certs']) 458 459 @property 460 def check_member_attr(self): 461 return flatten_boolean(self._values['check_member_attr']) 462 463 @property 464 def ssl(self): 465 if self._values['ssl'] is None: 466 return None 467 elif self._values['ssl'] == 'start-tls': 468 return 'start-tls' 469 return flatten_boolean(self._values['ssl']) 470 471 472class Difference(object): 473 def __init__(self, want, have=None): 474 self.want = want 475 self.have = have 476 477 def compare(self, param): 478 try: 479 result = getattr(self, param) 480 return result 481 except AttributeError: 482 return self.__default(param) 483 484 def __default(self, param): 485 attr1 = getattr(self.want, param) 486 try: 487 attr2 = getattr(self.have, param) 488 if attr1 != attr2: 489 return attr1 490 except AttributeError: 491 return attr1 492 493 @property 494 def login_ldap_attr(self): 495 return cmp_str_with_none(self.want.login_ldap_attr, self.have.login_ldap_attr) 496 497 @property 498 def user_template(self): 499 return cmp_str_with_none(self.want.user_template, self.have.user_template) 500 501 @property 502 def ca_cert(self): 503 return cmp_str_with_none(self.want.ca_cert, self.have.ca_cert) 504 505 @property 506 def client_key(self): 507 return cmp_str_with_none(self.want.client_key, self.have.client_key) 508 509 @property 510 def client_cert(self): 511 return cmp_str_with_none(self.want.client_cert, self.have.client_cert) 512 513 @property 514 def bind_password(self): 515 if self.want.bind_password != self.have.bind_password and self.want.update_password == 'always': 516 return self.want.bind_password 517 518 519class ModuleManager(object): 520 def __init__(self, *args, **kwargs): 521 self.module = kwargs.get('module', None) 522 self.client = F5RestClient(**self.module.params) 523 self.want = ModuleParameters(params=self.module.params) 524 self.have = ApiParameters() 525 self.changes = UsableChanges() 526 527 def _set_changed_options(self): 528 changed = {} 529 for key in Parameters.returnables: 530 if getattr(self.want, key) is not None: 531 changed[key] = getattr(self.want, key) 532 if changed: 533 self.changes = UsableChanges(params=changed) 534 535 def _update_changed_options(self): 536 diff = Difference(self.want, self.have) 537 updatables = Parameters.updatables 538 changed = dict() 539 for k in updatables: 540 change = diff.compare(k) 541 if change is None: 542 continue 543 else: 544 if isinstance(change, dict): 545 changed.update(change) 546 else: 547 changed[k] = change 548 if changed: 549 self.changes = UsableChanges(params=changed) 550 return True 551 return False 552 553 def _announce_deprecations(self, result): 554 warnings = result.pop('__warnings', []) 555 for warning in warnings: 556 self.client.module.deprecate( 557 msg=warning['msg'], 558 version=warning['version'] 559 ) 560 561 def update_auth_source_on_device(self, source): 562 """Set the system auth source. 563 564 Configuring the authentication source is only one step in the process of setting 565 up an auth source. The other step is to inform the system of the auth source 566 you want to use. 567 568 This method is used for situations where 569 570 * The ``use_for_auth`` parameter is set to ``yes`` 571 * The ``use_for_auth`` parameter is set to ``no`` 572 * The ``state`` parameter is set to ``absent`` 573 574 When ``state`` equal to ``absent``, before you can delete the LDAP configuration, 575 you must set the system auth to "something else". The system ships with a system 576 auth called "local", so this is the logical "something else" to use. 577 578 When ``use_for_auth`` is no, the same situation applies as when ``state`` equal 579 to ``absent`` is done above. 580 581 When ``use_for_auth`` is ``yes``, this method will set the current system auth 582 state to the value of source_type. 583 584 Arguments: 585 source (string): The source that you want to set on the device. 586 """ 587 params = dict( 588 type=source 589 ) 590 uri = 'https://{0}:{1}/mgmt/tm/auth/source/'.format( 591 self.client.provider['server'], 592 self.client.provider['server_port'] 593 ) 594 595 resp = self.client.api.patch(uri, json=params) 596 try: 597 response = resp.json() 598 except ValueError as ex: 599 raise F5ModuleError(str(ex)) 600 601 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 602 return True 603 raise F5ModuleError(resp.content) 604 605 def update_fallback_on_device(self, fallback): 606 params = dict( 607 fallback=fallback 608 ) 609 uri = 'https://{0}:{1}/mgmt/tm/auth/source/'.format( 610 self.client.provider['server'], 611 self.client.provider['server_port'] 612 ) 613 614 resp = self.client.api.patch(uri, json=params) 615 try: 616 response = resp.json() 617 except ValueError as ex: 618 raise F5ModuleError(str(ex)) 619 620 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 621 return True 622 raise F5ModuleError(resp.content) 623 624 def exec_module(self): 625 start = datetime.now().isoformat() 626 version = tmos_version(self.client) 627 changed = False 628 result = dict() 629 state = self.want.state 630 631 if state == "present": 632 changed = self.present() 633 elif state == "absent": 634 changed = self.absent() 635 636 reportable = ReportableChanges(params=self.changes.to_return()) 637 changes = reportable.to_return() 638 result.update(**changes) 639 result.update(dict(changed=changed)) 640 self._announce_deprecations(result) 641 send_teem(start, self.client, self.module, version) 642 return result 643 644 def present(self): 645 if self.exists(): 646 return self.update() 647 else: 648 return self.create() 649 650 def absent(self): 651 if self.exists(): 652 return self.remove() 653 return False 654 655 def should_update(self): 656 result = self._update_changed_options() 657 if result: 658 return True 659 return False 660 661 def update(self): 662 self.have = self.read_current_from_device() 663 if not self.should_update(): 664 return False 665 if self.module.check_mode: 666 return True 667 self.update_on_device() 668 if self.want.fallback_to_local == 'yes': 669 self.update_fallback_on_device('true') 670 elif self.want.fallback_to_local == 'no': 671 self.update_fallback_on_device('false') 672 if self.want.use_for_auth and self.changes.auth_source: 673 self.update_auth_source_on_device(self.changes.auth_source) 674 return True 675 676 def remove(self): 677 if self.module.check_mode: 678 return True 679 self.update_auth_source_on_device('local') 680 self.remove_from_device() 681 if self.exists(): 682 raise F5ModuleError("Failed to delete the resource.") 683 return True 684 685 def create(self): 686 self._set_changed_options() 687 if self.module.check_mode: 688 return True 689 self.create_on_device() 690 if self.want.fallback_to_local == 'yes': 691 self.update_fallback_on_device('true') 692 elif self.want.fallback_to_local == 'no': 693 self.update_fallback_on_device('false') 694 if self.want.use_for_auth: 695 self.update_auth_source_on_device(self.want.auth_source) 696 return True 697 698 def exists(self): 699 errors = [401, 403, 409, 500, 501, 502, 503, 504] 700 uri = "https://{0}:{1}/mgmt/tm/auth/ldap/{2}".format( 701 self.client.provider['server'], 702 self.client.provider['server_port'], 703 transform_name('Common', 'system-auth') 704 ) 705 resp = self.client.api.get(uri) 706 try: 707 response = resp.json() 708 except ValueError as ex: 709 raise F5ModuleError(str(ex)) 710 711 if resp.status == 404 or 'code' in response and response['code'] == 404: 712 return False 713 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 714 return True 715 716 if resp.status in errors or 'code' in response and response['code'] in errors: 717 if 'message' in response: 718 raise F5ModuleError(response['message']) 719 else: 720 raise F5ModuleError(resp.content) 721 722 def create_on_device(self): 723 params = self.changes.api_params() 724 params['name'] = 'system-auth' 725 params['partition'] = 'Common' 726 uri = "https://{0}:{1}/mgmt/tm/auth/ldap/".format( 727 self.client.provider['server'], 728 self.client.provider['server_port'], 729 ) 730 resp = self.client.api.post(uri, json=params) 731 try: 732 response = resp.json() 733 except ValueError as ex: 734 raise F5ModuleError(str(ex)) 735 736 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 737 return True 738 raise F5ModuleError(resp.content) 739 740 def update_on_device(self): 741 params = self.changes.api_params() 742 if not params: 743 return 744 uri = "https://{0}:{1}/mgmt/tm/auth/ldap/{2}".format( 745 self.client.provider['server'], 746 self.client.provider['server_port'], 747 transform_name('Common', 'system-auth') 748 ) 749 resp = self.client.api.patch(uri, json=params) 750 try: 751 response = resp.json() 752 except ValueError as ex: 753 raise F5ModuleError(str(ex)) 754 755 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 756 return True 757 raise F5ModuleError(resp.content) 758 759 def remove_from_device(self): 760 uri = "https://{0}:{1}/mgmt/tm/auth/ldap/{2}".format( 761 self.client.provider['server'], 762 self.client.provider['server_port'], 763 transform_name('Common', 'system-auth') 764 ) 765 response = self.client.api.delete(uri) 766 767 if response.status in [200, 201]: 768 return True 769 raise F5ModuleError(response.content) 770 771 def read_current_from_device(self): 772 uri = "https://{0}:{1}/mgmt/tm/auth/ldap/{2}".format( 773 self.client.provider['server'], 774 self.client.provider['server_port'], 775 transform_name('Common', 'system-auth') 776 ) 777 resp = self.client.api.get(uri) 778 try: 779 response = resp.json() 780 except ValueError as ex: 781 raise F5ModuleError(str(ex)) 782 783 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 784 response.update(self.read_current_auth_source_from_device()) 785 return ApiParameters(params=response) 786 raise F5ModuleError(resp.content) 787 788 def read_current_auth_source_from_device(self): 789 uri = "https://{0}:{1}/mgmt/tm/auth/source".format( 790 self.client.provider['server'], 791 self.client.provider['server_port'], 792 ) 793 resp = self.client.api.get(uri) 794 try: 795 response = resp.json() 796 except ValueError as ex: 797 raise F5ModuleError(str(ex)) 798 result = {} 799 if resp.status in [200, 201] or 'code' in response and response['code'] in [200, 201]: 800 if 'fallback' in response: 801 result['fallback'] = response['fallback'] 802 if 'type' in response: 803 result['auth_source'] = response['type'] 804 return result 805 raise F5ModuleError(resp.content) 806 807 808class ArgumentSpec(object): 809 def __init__(self): 810 self.supports_check_mode = True 811 argument_spec = dict( 812 servers=dict( 813 type='list', 814 elements='str', 815 ), 816 port=dict(type='int'), 817 remote_directory_tree=dict(), 818 scope=dict( 819 choices=['sub', 'one', 'base'] 820 ), 821 bind_dn=dict(), 822 bind_password=dict(no_log=True), 823 user_template=dict(), 824 check_member_attr=dict(type='bool'), 825 ssl=dict( 826 choices=['yes', 'no', 'start-tls'] 827 ), 828 ca_cert=dict(aliases=['ssl_ca_cert']), 829 client_key=dict(aliases=['ssl_client_key']), 830 client_cert=dict(aliases=['ssl_client_cert']), 831 validate_certs=dict(type='bool', aliases=['ssl_check_peer']), 832 login_ldap_attr=dict(), 833 fallback_to_local=dict(type='bool'), 834 use_for_auth=dict(type='bool'), 835 update_password=dict( 836 default='always', 837 choices=['always', 'on_create'] 838 ), 839 state=dict(default='present', choices=['absent', 'present']), 840 ) 841 self.argument_spec = {} 842 self.argument_spec.update(f5_argument_spec) 843 self.argument_spec.update(argument_spec) 844 845 846def main(): 847 spec = ArgumentSpec() 848 849 module = AnsibleModule( 850 argument_spec=spec.argument_spec, 851 supports_check_mode=spec.supports_check_mode, 852 ) 853 854 try: 855 mm = ModuleManager(module=module) 856 results = mm.exec_module() 857 module.exit_json(**results) 858 except F5ModuleError as ex: 859 module.fail_json(msg=str(ex)) 860 861 862if __name__ == '__main__': 863 main() 864