diff --git a/Dockerfile b/Dockerfile index 7e6eac422..9ff8dc4e2 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,7 +76,7 @@ RUN pip install --upgrade pip==20.2.4 setuptools==49.6.0 wheel==0.34.2 -i ${PIP_ ARG VERSION ENV VERSION=$VERSION - +ENV ANSIBLE_LIBRARY=/opt/jumpserver/apps/ops/ansible/modules ADD . . RUN cd utils \ && bash -ixeu build.sh \ diff --git a/apps/assets/automations/change_secret/database/mongodb/main.yml b/apps/assets/automations/change_secret/database/mongodb/main.yml new file mode 100644 index 000000000..02a568e0b --- /dev/null +++ b/apps/assets/automations/change_secret/database/mongodb/main.yml @@ -0,0 +1,43 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test MongoDB connection + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + register: db_info + + - name: Display MongoDB version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change MongoDB password + mongodb_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + db: "{{ jms_asset.specific.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + mongodb_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/database/mongodb/manifest.yml b/apps/assets/automations/change_secret/database/mongodb/manifest.yml new file mode 100644 index 000000000..a59c0033b --- /dev/null +++ b/apps/assets/automations/change_secret/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: change_secret_mongodb +name: Change password for MongoDB +category: database +type: + - mongodb +method: change_secret diff --git a/apps/assets/automations/change_secret/database/oracle/main.yml b/apps/assets/automations/change_secret/database/oracle/main.yml new file mode 100644 index 000000000..c7b20a8db --- /dev/null +++ b/apps/assets/automations/change_secret/database/oracle/main.yml @@ -0,0 +1,45 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test Oracle connection + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + register: db_info + + - name: Display Oracle version + debug: + var: db_info.server_version + when: db_info is succeeded + + - name: Change Oracle password + oracle_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" + when: db_info is succeeded + register: change_info + + - name: Verify password + oracle_ping: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ account.mode }}" + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/database/oracle/manifest.yml b/apps/assets/automations/change_secret/database/oracle/manifest.yml new file mode 100644 index 000000000..19f109ba6 --- /dev/null +++ b/apps/assets/automations/change_secret/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: change_secret_oracle +name: Change password for Oracle +category: database +type: + - oracle +method: change_secret diff --git a/apps/assets/automations/change_secret/database/sqlserver/main.yml b/apps/assets/automations/change_secret/database/sqlserver/main.yml new file mode 100644 index 000000000..a617a1434 --- /dev/null +++ b/apps/assets/automations/change_secret/database/sqlserver/main.yml @@ -0,0 +1,47 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test SQLServer connection + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version + register: db_info + + - name: SQLServer version + set_fact: + info: + version: "{{ db_info.query_results[0][0][0][0].splitlines()[0] }}" + - debug: + var: info + + - name: Change SQLServer password + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: "ALTER LOGIN {{ account.username }} WITH PASSWORD = '{{ account.secret }}'; select @@version" + when: db_info is succeeded + register: change_info + + - name: Verify password + community.general.mssql_script: + login_user: "{{ account.username }}" + login_password: "{{ account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version + when: + - db_info is succeeded + - change_info is succeeded diff --git a/apps/assets/automations/change_secret/database/sqlserver/manifest.yml b/apps/assets/automations/change_secret/database/sqlserver/manifest.yml new file mode 100644 index 000000000..799c9e623 --- /dev/null +++ b/apps/assets/automations/change_secret/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: change_secret_sqlserver +name: Change password for SQLServer +category: database +type: + - sqlserver +method: change_secret diff --git a/apps/assets/automations/change_secret/manager.py b/apps/assets/automations/change_secret/manager.py index a8b7dd515..35ccba90a 100644 --- a/apps/assets/automations/change_secret/manager.py +++ b/apps/assets/automations/change_secret/manager.py @@ -155,6 +155,8 @@ class ChangeSecretManager(BasePlaybookManager): 'secret': new_secret, 'private_key_path': private_key_path } + if asset.platform.type == 'oracle': + h['account']['mode'] = 'sysdba' if account.privileged else None inventory_hosts.append(h) method_hosts.append(h['name']) self.method_hosts_mapper[method_attr] = method_hosts diff --git a/apps/assets/automations/gather_accounts/database/mongodb/main.yml b/apps/assets/automations/gather_accounts/database/mongodb/main.yml new file mode 100644 index 000000000..fd7a296b7 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mongodb/main.yml @@ -0,0 +1,22 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + community.mongodb.mongodb_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + filter: users + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.users }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/mongodb/manifest.yml b/apps/assets/automations/gather_accounts/database/mongodb/manifest.yml new file mode 100644 index 000000000..a002848b4 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_mongodb +name: Gather account from MongoDB +category: database +type: + - mongodb +method: gather_accounts diff --git a/apps/assets/automations/gather_accounts/database/mysql/main.yml b/apps/assets/automations/gather_accounts/database/mysql/main.yml index 4b166322a..cc934f20f 100644 --- a/apps/assets/automations/gather_accounts/database/mysql/main.yml +++ b/apps/assets/automations/gather_accounts/database/mysql/main.yml @@ -1,7 +1,7 @@ - hosts: mysql gather_facts: no vars: - ansible_python_interpreter: /Users/xiaofeng/Desktop/jumpserver/venv/bin/python + ansible_python_interpreter: /usr/local/bin/python tasks: - name: Get info @@ -9,7 +9,7 @@ login_user: "{{ jms_account.username }}" login_password: "{{ jms_account.secret }}" login_host: "{{ jms_asset.address }}" - login_port: 1234 + login_port: "{{ jms_asset.port }}" filter: users register: db_info diff --git a/apps/assets/automations/gather_accounts/database/oracle/main.yml b/apps/assets/automations/gather_accounts/database/oracle/main.yml new file mode 100644 index 000000000..a0e20ff7b --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/oracle/main.yml @@ -0,0 +1,23 @@ +- hosts: oralce + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + oracle_info: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + filter: users + register: db_info + + - name: Define info by set_fact + set_fact: + info: "{{ db_info.users }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_accounts/database/oracle/manifest.yml b/apps/assets/automations/gather_accounts/database/oracle/manifest.yml new file mode 100644 index 000000000..4753f1495 --- /dev/null +++ b/apps/assets/automations/gather_accounts/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: gather_accounts_oracle +name: Gather account from Oracle +category: database +type: + - oracle +method: gather_accounts diff --git a/apps/assets/automations/gather_facts/database/mongodb/main.yml b/apps/assets/automations/gather_facts/database/mongodb/main.yml new file mode 100644 index 000000000..37ce8bbd3 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/mongodb/main.yml @@ -0,0 +1,22 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + register: db_info + + - name: Define info by set_fact + set_fact: + info: + version: "{{ db_info.server_version }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_facts/database/mongodb/manifest.yml b/apps/assets/automations/gather_facts/database/mongodb/manifest.yml new file mode 100644 index 000000000..9b832e838 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: gather_facts_mongodb +name: Gather facts from MongoDB +category: database +type: + - mongodb +method: gather_facts diff --git a/apps/assets/automations/gather_facts/database/oracle/main.yml b/apps/assets/automations/gather_facts/database/oracle/main.yml new file mode 100644 index 000000000..21ab639a4 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/oracle/main.yml @@ -0,0 +1,23 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Get info + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + register: db_info + + - name: Define info by set_fact + set_fact: + info: + version: "{{ db_info.server_version }}" + + - debug: + var: info diff --git a/apps/assets/automations/gather_facts/database/oracle/manifest.yml b/apps/assets/automations/gather_facts/database/oracle/manifest.yml new file mode 100644 index 000000000..d350579d6 --- /dev/null +++ b/apps/assets/automations/gather_facts/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: gather_facts_oracle +name: Gather facts from Oracle +category: database +type: + - oracle +method: gather_facts diff --git a/apps/assets/automations/ping/database/mongodb/main.yml b/apps/assets/automations/ping/database/mongodb/main.yml new file mode 100644 index 000000000..867c51ace --- /dev/null +++ b/apps/assets/automations/ping/database/mongodb/main.yml @@ -0,0 +1,13 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test MongoDB connection + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" diff --git a/apps/assets/automations/ping/database/mongodb/manifest.yml b/apps/assets/automations/ping/database/mongodb/manifest.yml new file mode 100644 index 000000000..45b90eb72 --- /dev/null +++ b/apps/assets/automations/ping/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: mongodb_ping +name: Ping MongoDB +category: database +type: + - mongodb +method: ping diff --git a/apps/assets/automations/ping/database/oracle/main.yml b/apps/assets/automations/ping/database/oracle/main.yml new file mode 100644 index 000000000..fefad7148 --- /dev/null +++ b/apps/assets/automations/ping/database/oracle/main.yml @@ -0,0 +1,14 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test Oracle connection + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" diff --git a/apps/assets/automations/ping/database/oracle/manifest.yml b/apps/assets/automations/ping/database/oracle/manifest.yml new file mode 100644 index 000000000..3912941c3 --- /dev/null +++ b/apps/assets/automations/ping/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: oracle_ping +name: Ping Oracle +category: database +type: + - oracle +method: ping diff --git a/apps/assets/automations/ping/database/sqlserver/main.yml b/apps/assets/automations/ping/database/sqlserver/main.yml new file mode 100644 index 000000000..839a785a5 --- /dev/null +++ b/apps/assets/automations/ping/database/sqlserver/main.yml @@ -0,0 +1,15 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Test SQLServer connection + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version diff --git a/apps/assets/automations/ping/database/sqlserver/manifest.yml b/apps/assets/automations/ping/database/sqlserver/manifest.yml new file mode 100644 index 000000000..b5cd6bf88 --- /dev/null +++ b/apps/assets/automations/ping/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: sqlserver_ping +name: Ping SQLServer +category: database +type: + - sqlserver +method: ping diff --git a/apps/assets/automations/push_account/database/mongodb/main.yml b/apps/assets/automations/push_account/database/mongodb/main.yml new file mode 100644 index 000000000..d516251db --- /dev/null +++ b/apps/assets/automations/push_account/database/mongodb/main.yml @@ -0,0 +1,16 @@ +- hosts: mongodb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Add user account.username + mongodb_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + db: "{{ jms_asset.specific.db_name }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" diff --git a/apps/assets/automations/push_account/database/mongodb/manifest.yml b/apps/assets/automations/push_account/database/mongodb/manifest.yml new file mode 100644 index 000000000..9de93f5e7 --- /dev/null +++ b/apps/assets/automations/push_account/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_mongodb +name: Push account from MongoDB +category: database +type: + - mongodb +method: push_account diff --git a/apps/assets/automations/push_account/database/oracle/main.yml b/apps/assets/automations/push_account/database/oracle/main.yml new file mode 100644 index 000000000..5812dfee1 --- /dev/null +++ b/apps/assets/automations/push_account/database/oracle/main.yml @@ -0,0 +1,16 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Add user account.username + oracle_user: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" + name: "{{ account.username }}" + password: "{{ account.secret }}" diff --git a/apps/assets/automations/push_account/database/oracle/manifest.yml b/apps/assets/automations/push_account/database/oracle/manifest.yml new file mode 100644 index 000000000..da1faed6f --- /dev/null +++ b/apps/assets/automations/push_account/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: push_account_oracle +name: Push account from Oracle +category: database +type: + - oracle +method: push_account diff --git a/apps/assets/automations/verify_account/database/mongodb/main.yml b/apps/assets/automations/verify_account/database/mongodb/main.yml new file mode 100644 index 000000000..1cf79b694 --- /dev/null +++ b/apps/assets/automations/verify_account/database/mongodb/main.yml @@ -0,0 +1,13 @@ +- hosts: mongdb + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + mongodb_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" diff --git a/apps/assets/automations/verify_account/database/mongodb/manifest.yml b/apps/assets/automations/verify_account/database/mongodb/manifest.yml new file mode 100644 index 000000000..24cc398c2 --- /dev/null +++ b/apps/assets/automations/verify_account/database/mongodb/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_mongodb +name: Verify account from MongoDB +category: database +type: + - mongodb +method: verify_account diff --git a/apps/assets/automations/verify_account/database/oracle/main.yml b/apps/assets/automations/verify_account/database/oracle/main.yml new file mode 100644 index 000000000..ed4091401 --- /dev/null +++ b/apps/assets/automations/verify_account/database/oracle/main.yml @@ -0,0 +1,14 @@ +- hosts: oracle + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + oracle_ping: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + login_database: "{{ jms_asset.specific.db_name }}" + mode: "{{ jms_account.mode }}" diff --git a/apps/assets/automations/verify_account/database/oracle/manifest.yml b/apps/assets/automations/verify_account/database/oracle/manifest.yml new file mode 100644 index 000000000..b58a7f888 --- /dev/null +++ b/apps/assets/automations/verify_account/database/oracle/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_oracle +name: Verify account from Oracle +category: database +type: + - oracle +method: verify_account diff --git a/apps/assets/automations/verify_account/database/sqlserver/main.yml b/apps/assets/automations/verify_account/database/sqlserver/main.yml new file mode 100644 index 000000000..256803702 --- /dev/null +++ b/apps/assets/automations/verify_account/database/sqlserver/main.yml @@ -0,0 +1,15 @@ +- hosts: sqlserver + gather_facts: no + vars: + ansible_python_interpreter: /usr/local/bin/python + + tasks: + - name: Verify account + community.general.mssql_script: + login_user: "{{ jms_account.username }}" + login_password: "{{ jms_account.secret }}" + login_host: "{{ jms_asset.address }}" + login_port: "{{ jms_asset.port }}" + name: '{{ jms_asset.specific.db_name }}' + script: | + SELECT @@version diff --git a/apps/assets/automations/verify_account/database/sqlserver/manifest.yml b/apps/assets/automations/verify_account/database/sqlserver/manifest.yml new file mode 100644 index 000000000..8af52ab0b --- /dev/null +++ b/apps/assets/automations/verify_account/database/sqlserver/manifest.yml @@ -0,0 +1,6 @@ +id: verify_account_sqlserver +name: Verify account from SQLServer +category: database +type: + - sqlserver +method: verify_account diff --git a/apps/ops/ansible/inventory.py b/apps/ops/ansible/inventory.py index 1e006ae77..f71801b76 100644 --- a/apps/ops/ansible/inventory.py +++ b/apps/ops/ansible/inventory.py @@ -112,6 +112,9 @@ class JMSInventory: 'secret': account.secret, 'secret_type': account.secret_type } if account else None } + if host['jms_account'] and asset.platform.type == 'oracle': + host['jms_account']['mode'] = 'sysdba' if account.privileged else None + ansible_config = dict(automation.ansible_config) ansible_connection = ansible_config.get('ansible_connection', 'ssh') host.update(ansible_config) diff --git a/apps/ops/ansible/modules/__init__.py b/apps/ops/ansible/modules/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/ansible/modules/mongodb_ping.py b/apps/ops/ansible/modules/mongodb_ping.py new file mode 100644 index 000000000..d018cd852 --- /dev/null +++ b/apps/ops/ansible/modules/mongodb_ping.py @@ -0,0 +1,126 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: mongodb_ping +short_description: Check remote MongoDB server availability +description: +- Simple module to check remote MongoDB server availability. + +requirements: + - "pymongo" +''' + +EXAMPLES = ''' +- name: > + Ping MongoDB server using non-default credentials and SSL + registering the return values into the result variable for future use + mongodb_ping: + login_db: test_db + login_host: jumpserver + login_user: jms + login_password: secret_pass + ssl: True + ssl_ca_certs: "/tmp/ca.crt" + ssl_certfile: "/tmp/tls.key" #cert and key in one file + connection_options: + - "tlsAllowInvalidHostnames=true" +''' + +RETURN = ''' +is_available: + description: MongoDB server availability. + returned: always + type: bool + sample: true +server_version: + description: MongoDB server version. + returned: always + type: str + sample: '4.0.0' +conn_err_msg: + description: Connection error message. + returned: always + type: str + sample: '' +''' + + +from pymongo.errors import PyMongoError +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_native +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_common import ( + mongodb_common_argument_spec, + mongo_auth, + get_mongodb_client, +) + + +class MongoDBPing(object): + def __init__(self, module, client): + self.module = module + self.client = client + self.is_available = False + self.conn_err_msg = '' + self.version = '' + + def do(self): + self.get_mongodb_version() + return self.is_available, self.version + + def get_err(self): + return self.conn_err_msg + + def get_mongodb_version(self): + try: + server_info = self.client.server_info() + self.is_available = True + self.version = server_info.get('version', '') + except PyMongoError as err: + self.is_available = False + self.version = '' + self.conn_err_msg = err + + +# ========================================= +# Module execution. +# + + +def main(): + argument_spec = mongodb_common_argument_spec() + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + client = None + result = { + 'changed': False, 'is_available': False, 'server_version': '' + } + try: + client = get_mongodb_client(module, directConnection=True) + client = mongo_auth(module, client, directConnection=True) + except Exception as e: + module.fail_json(msg='Unable to connect to database: %s' % to_native(e)) + + mongodb_ping = MongoDBPing(module, client) + result["is_available"], result["server_version"] = mongodb_ping.do() + conn_err_msg = mongodb_ping.get_err() + if conn_err_msg: + module.fail_json(msg='Unable to connect to database: %s' % conn_err_msg) + + try: + client.close() + except Exception: + pass + + return module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/mongodb_user.py b/apps/ops/ansible/modules/mongodb_user.py new file mode 100644 index 000000000..93493fd58 --- /dev/null +++ b/apps/ops/ansible/modules/mongodb_user.py @@ -0,0 +1,426 @@ +#!/usr/bin/python + +# Modified from ansible_collections.community.mongodb.plugins.modules.mongodb_user + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: mongodb_user +short_description: Adds or removes a user from a MongoDB database +description: + - Adds or removes a user from a MongoDB database. +version_added: "1.0.0" + +extends_documentation_fragment: + - community.mongodb.login_options + - community.mongodb.ssl_options + +options: + replica_set: + description: + - Replica set to connect to (automatically connects to primary for writes). + type: str + database: + description: + - The name of the database to add/remove the user from. + required: true + type: str + aliases: [db] + name: + description: + - The name of the user to add or remove. + required: true + aliases: [user] + type: str + password: + description: + - The password to use for the user. + type: str + aliases: [pass] + roles: + type: list + elements: raw + description: + - > + The database user roles valid values could either be one or more of the following strings: + 'read', 'readWrite', 'dbAdmin', 'userAdmin', 'clusterAdmin', 'readAnyDatabase', 'readWriteAnyDatabase', 'userAdminAnyDatabase', + 'dbAdminAnyDatabase' + - "Or the following dictionary '{ db: DATABASE_NAME, role: ROLE_NAME }'." + - "This param requires pymongo 2.5+. If it is a string, mongodb 2.4+ is also required. If it is a dictionary, mongo 2.6+ is required." + state: + description: + - The database user state. + default: present + choices: [absent, present] + type: str + update_password: + default: always + choices: [always, on_create] + description: + - C(always) will always update passwords and cause the module to return changed. + - C(on_create) will only set the password for newly created users. + - This must be C(always) to use the localhost exception when adding the first admin user. + - This option is effectively ignored when using x.509 certs. It is defaulted to 'on_create' to maintain a \ + a specific module behaviour when the login_database is '$external'. + type: str + create_for_localhost_exception: + type: path + description: + - This is parmeter is only useful for handling special treatment around the localhost exception. + - If C(login_user) is defined, then the localhost exception is not active and this parameter has no effect. + - If this file is NOT present (and C(login_user) is not defined), then touch this file after successfully adding the user. + - If this file is present (and C(login_user) is not defined), then skip this task. + +notes: + - Requires the pymongo Python package on the remote host, version 2.4.2+. This + can be installed using pip or the OS package manager. Newer mongo server versions require newer + pymongo versions. @see http://api.mongodb.org/python/current/installation.html +requirements: + - "pymongo" +author: + - "Elliott Foster (@elliotttf)" + - "Julien Thebault (@Lujeni)" +''' + +EXAMPLES = ''' +- name: Create 'burgers' database user with name 'bob' and password '12345'. + community.mongodb.mongodb_user: + database: burgers + name: bob + password: 12345 + state: present + +- name: Create a database user via SSL (MongoDB must be compiled with the SSL option and configured properly) + community.mongodb.mongodb_user: + database: burgers + name: bob + password: 12345 + state: present + ssl: True + +- name: Delete 'burgers' database user with name 'bob'. + community.mongodb.mongodb_user: + database: burgers + name: bob + state: absent + +- name: Define more users with various specific roles (if not defined, no roles is assigned, and the user will be added via pre mongo 2.2 style) + community.mongodb.mongodb_user: + database: burgers + name: ben + password: 12345 + roles: read + state: present + +- name: Define roles + community.mongodb.mongodb_user: + database: burgers + name: jim + password: 12345 + roles: readWrite,dbAdmin,userAdmin + state: present + +- name: Define roles + community.mongodb.mongodb_user: + database: burgers + name: joe + password: 12345 + roles: readWriteAnyDatabase + state: present + +- name: Add a user to database in a replica set, the primary server is automatically discovered and written to + community.mongodb.mongodb_user: + database: burgers + name: bob + replica_set: belcher + password: 12345 + roles: readWriteAnyDatabase + state: present + +# add a user 'oplog_reader' with read only access to the 'local' database on the replica_set 'belcher'. This is useful for oplog access (MONGO_OPLOG_URL). +# please notice the credentials must be added to the 'admin' database because the 'local' database is not synchronized and can't receive user credentials +# To login with such user, the connection string should be MONGO_OPLOG_URL="mongodb://oplog_reader:oplog_reader_password@server1,server2/local?authSource=admin" +# This syntax requires mongodb 2.6+ and pymongo 2.5+ +- name: Roles as a dictionary + community.mongodb.mongodb_user: + login_user: root + login_password: root_password + database: admin + user: oplog_reader + password: oplog_reader_password + state: present + replica_set: belcher + roles: + - db: local + role: read + +- name: Adding a user with X.509 Member Authentication + community.mongodb.mongodb_user: + login_host: "mongodb-host.test" + login_port: 27001 + login_database: "$external" + database: "admin" + name: "admin" + password: "test" + roles: + - dbAdminAnyDatabase + ssl: true + ssl_ca_certs: "/tmp/ca.crt" + ssl_certfile: "/tmp/tls.key" #cert and key in one file + state: present + auth_mechanism: "MONGODB-X509" + connection_options: + - "tlsAllowInvalidHostnames=true" +''' + +RETURN = ''' +user: + description: The name of the user to add or remove. + returned: success + type: str +''' + +import os +import traceback +from operator import itemgetter + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import binary_type, text_type +from ansible.module_utils._text import to_native, to_bytes +from ansible_collections.community.mongodb.plugins.module_utils.mongodb_common import ( + missing_required_lib, + mongodb_common_argument_spec, + mongo_auth, + PYMONGO_IMP_ERR, + pymongo_found, + get_mongodb_client, +) + + +def user_find(client, user, db_name): + """Check if the user exists. + + Args: + client (cursor): Mongodb cursor on admin database. + user (str): User to check. + db_name (str): User's database. + + Returns: + dict: when user exists, False otherwise. + """ + try: + for mongo_user in client[db_name].command('usersInfo')['users']: + if mongo_user['user'] == user: + # NOTE: there is no 'db' field in mongo 2.4. + if 'db' not in mongo_user: + return mongo_user + # Workaround to make the condition works with AWS DocumentDB, + # since all users are in the admin database. + if mongo_user["db"] in [db_name, "admin"]: + return mongo_user + except Exception as excep: + if hasattr(excep, 'code') and excep.code == 11: # 11=UserNotFound + pass # Allow return False + else: + raise + return False + + +def user_add(module, client, db_name, user, password, roles): + # pymongo's user_add is a _create_or_update_user so we won't know if it was changed or updated + # without reproducing a lot of the logic in database.py of pymongo + db = client[db_name] + + try: + exists = user_find(client, user, db_name) + except Exception as excep: + # We get this exception: "not authorized on admin to execute command" + # when auth is enabled on a new instance. The loalhost exception should + # allow us to create the first user. If the localhost exception does not apply, + # then user creation will also fail with unauthorized. So, ignore Unauthorized here. + if hasattr(excep, 'code') and excep.code == 13: # 13=Unauthorized + exists = False + else: + raise + + if exists: + user_add_db_command = 'updateUser' + if not roles: + roles = None + else: + user_add_db_command = 'createUser' + + user_dict = {} + + if password is not None: + user_dict["pwd"] = password + if roles is not None: + user_dict["roles"] = roles + + db.command(user_add_db_command, user, **user_dict) + + +def user_remove(module, client, db_name, user): + exists = user_find(client, user, db_name) + if exists: + if module.check_mode: + module.exit_json(changed=True, user=user) + db = client[db_name] + db.command("dropUser", user) + else: + module.exit_json(changed=False, user=user) + + +def check_if_roles_changed(uinfo, roles, db_name): + # We must be aware of users which can read the oplog on a replicaset + # Such users must have access to the local DB, but since this DB does not store users credentials + # and is not synchronized among replica sets, the user must be stored on the admin db + # Therefore their structure is the following : + # { + # "_id" : "admin.oplog_reader", + # "user" : "oplog_reader", + # "db" : "admin", # <-- admin DB + # "roles" : [ + # { + # "role" : "read", + # "db" : "local" # <-- local DB + # } + # ] + # } + + def make_sure_roles_are_a_list_of_dict(roles, db_name): + output = list() + for role in roles: + if isinstance(role, (binary_type, text_type)): + new_role = {"role": role, "db": db_name} + output.append(new_role) + else: + output.append(role) + return output + + roles_as_list_of_dict = make_sure_roles_are_a_list_of_dict(roles, db_name) + uinfo_roles = uinfo.get('roles', []) + + if sorted(roles_as_list_of_dict, key=itemgetter('db')) == sorted(uinfo_roles, key=itemgetter('db')): + return False + return True + + +# ========================================= +# Module execution. +# + +def main(): + argument_spec = mongodb_common_argument_spec() + argument_spec.update( + database=dict(required=True, aliases=['db']), + name=dict(required=True, aliases=['user']), + password=dict(aliases=['pass'], no_log=True), + replica_set=dict(default=None), + roles=dict(default=None, type='list', elements='raw'), + state=dict(default='present', choices=['absent', 'present']), + update_password=dict(default="always", choices=["always", "on_create"], no_log=False), + create_for_localhost_exception=dict(default=None, type='path'), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + login_user = module.params['login_user'] + + # Certs don't have a password but we want this module behaviour + if module.params['login_database'] == '$external': + module.params['update_password'] = 'on_create' + + if not pymongo_found: + module.fail_json(msg=missing_required_lib('pymongo'), + exception=PYMONGO_IMP_ERR) + + create_for_localhost_exception = module.params['create_for_localhost_exception'] + b_create_for_localhost_exception = ( + to_bytes(create_for_localhost_exception, errors='surrogate_or_strict') + if create_for_localhost_exception is not None else None + ) + + db_name = module.params['database'] + user = module.params['name'] + password = module.params['password'] + roles = module.params['roles'] or [] + state = module.params['state'] + update_password = module.params['update_password'] + + try: + directConnection = False + if module.params['replica_set'] is None: + directConnection = True + client = get_mongodb_client(module, directConnection=directConnection) + client = mongo_auth(module, client, directConnection=directConnection) + except Exception as e: + module.fail_json(msg='Unable to connect to database: %s' % to_native(e)) + + if state == 'present': + if password is None and update_password == 'always': + module.fail_json(msg='password parameter required when adding a user unless update_password is set to on_create') + + if login_user is None and create_for_localhost_exception is not None: + if os.path.exists(b_create_for_localhost_exception): + try: + client.close() + except Exception: + pass + module.exit_json(changed=False, user=user, skipped=True, msg="The path in create_for_localhost_exception exists.") + + try: + if update_password != 'always': + uinfo = user_find(client, user, db_name) + if uinfo: + password = None + if not check_if_roles_changed(uinfo, roles, db_name): + module.exit_json(changed=False, user=user) + + if module.check_mode: + module.exit_json(changed=True, user=user) + user_add(module, client, db_name, user, password, roles) + except Exception as e: + module.fail_json(msg='Unable to add or update user: %s' % to_native(e), exception=traceback.format_exc()) + finally: + try: + client.close() + except Exception: + pass + # Here we can check password change if mongo provide a query for that : https://jira.mongodb.org/browse/SERVER-22848 + # newuinfo = user_find(client, user, db_name) + # if uinfo['role'] == newuinfo['role'] and CheckPasswordHere: + # module.exit_json(changed=False, user=user) + + if login_user is None and create_for_localhost_exception is not None: + # localhost exception applied. + try: + # touch the file + open(b_create_for_localhost_exception, 'wb').close() + except Exception as e: + module.fail_json( + changed=True, + msg='Added user but unable to touch create_for_localhost_exception file %s: %s' % (create_for_localhost_exception, to_native(e)), + exception=traceback.format_exc() + ) + + elif state == 'absent': + try: + user_remove(module, client, db_name, user) + except Exception as e: + module.fail_json(msg='Unable to remove user: %s' % to_native(e), exception=traceback.format_exc()) + finally: + try: + client.close() + except Exception: + pass + module.exit_json(changed=True, user=user) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/oracle_info.py b/apps/ops/ansible/modules/oracle_info.py new file mode 100644 index 000000000..005206be9 --- /dev/null +++ b/apps/ops/ansible/modules/oracle_info.py @@ -0,0 +1,261 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +--- +module: oracle_info +short_description: Gather information about Oracle servers +description: +- Gathers information about Oracle servers. + +options: + filter: + description: + - Limit the collected information by comma separated string or YAML list. + - Allowable values are C(version), C(databases), C(settings), C(users). + - By default, collects all subsets. + - You can use '!' before value (for example, C(!users)) to exclude it from the information. + - If you pass including and excluding values to the filter, for example, I(filter=!settings,version), + the excluding values, C(!settings) in this case, will be ignored. + type: list + elements: str + login_db: + description: + - Database name to connect to. + - It makes sense if I(login_user) is allowed to connect to a specific database only. + type: str + exclude_fields: + description: + - List of fields which are not needed to collect. + - "Supports elements: C(db_size). Unsupported elements will be ignored." + type: list + elements: str +''' + +EXAMPLES = r''' +- name: Get Oracle version with non-default credentials + oracle_info: + login_user: mysuperuser + login_password: mysuperpass + login_database: service_name + filter: version + +- name: Collect all info except settings and users by sys + oracle_info: + login_user: sys + login_password: sys_pass + login_database: service_name + filter: "!settings,!users" + exclude_fields: db_size +''' + +RETURN = r''' +version: + description: Database server version. + returned: if not excluded by filter + type: dict + sample: { "version": {"full": "11.2.0.1.0"} } + contains: + full: + description: Full server version. + returned: if not excluded by filter + type: str + sample: "11.2.0.1.0" +databases: + description: Information about databases. + returned: if not excluded by filter + type: dict + sample: + - { "USERS": { "size": 5242880 }, "EXAMPLE": { "size": 104857600 } } + contains: + size: + description: Database size in bytes. + returned: if not excluded by filter + type: dict + sample: { 'size': 656594 } +settings: + description: Global settings (variables) information. + returned: if not excluded by filter + type: dict + sample: + - { "result_cache_mode": "MANUAL", "instance_type": "RDBMS" } +users: + description: Users information. + returned: if not excluded by filter + type: dict + sample: + - { "USERS": { "TEST": { "USERNAME": "TEST", "ACCOUNT_STATUS": "OPEN" } } } +''' + +from ansible.module_utils.basic import AnsibleModule + +from ops.ansible.modules_utils.oracle_common import ( + OracleClient, oracle_common_argument_spec +) + + +class OracleInfo(object): + def __init__(self, module, oracle_client): + self.module = module + self.oracle_client = oracle_client + self.info = { + 'version': {}, 'databases': {}, + 'settings': {}, 'users': {}, + } + + def get_info(self, filter_, exclude_fields): + include_list = [] + exclude_list = [] + + if filter_: + partial_info = {} + + for fi in filter_: + if fi.lstrip('!') not in self.info: + self.module.warn('filter element: %s is not allowable, ignored' % fi) + continue + + if fi[0] == '!': + exclude_list.append(fi.lstrip('!')) + else: + include_list.append(fi) + + if include_list: + self.__collect(exclude_fields, set(include_list)) + + for i in self.info: + if i in include_list: + partial_info[i] = self.info[i] + else: + not_in_exclude_list = list(set(self.info) - set(exclude_list)) + self.__collect(exclude_fields, set(not_in_exclude_list)) + + for i in self.info: + if i not in exclude_list: + partial_info[i] = self.info[i] + return partial_info + else: + self.__collect(exclude_fields, set(self.info)) + return self.info + + def __collect(self, exclude_fields, wanted): + """Collect all possible subsets.""" + if 'version' in wanted: + self.__get_version() + + if 'settings' in wanted: + self.__get_settings() + + if 'databases' in wanted: + self.__get_databases(exclude_fields) + # + if 'users' in wanted: + self.__get_users() + + def __get_version(self): + version_sql = 'SELECT VERSION FROM PRODUCT_COMPONENT_VERSION where ROWNUM=1' + rtn, err = self.oracle_client.execute(version_sql, exception_to_fail=True) + self.info['version'] = {'full': rtn.get('version')} + + def __get_settings(self): + """Get global variables (instance settings).""" + def _set_settings_value(item_dict): + try: + self.info['settings'][item_dict['name']] = item_dict['value'] + except KeyError: + pass + + settings_sql = "SELECT name, value FROM V$PARAMETER" + rtn, err = self.oracle_client.execute(settings_sql, exception_to_fail=True) + + if isinstance(rtn, dict): + _set_settings_value(rtn) + elif isinstance(rtn, list): + for i in rtn: + _set_settings_value(i) + + def __get_users(self): + """Get user info.""" + def _set_users_value(item_dict): + try: + tablespace = item_dict.pop('default_tablespace') + username = item_dict.pop('username') + partial_users = self.info['users'].get(tablespace, {}) + partial_users[username] = item_dict + self.info['users'][tablespace] = partial_users + except KeyError: + pass + + users_sql = "SELECT * FROM dba_users" + rtn, err = self.oracle_client.execute(users_sql, exception_to_fail=True) + if isinstance(rtn, dict): + _set_users_value(rtn) + elif isinstance(rtn, list): + for i in rtn: + _set_users_value(i) + + def __get_databases(self, exclude_fields): + """Get info about databases.""" + def _set_databases_value(item_dict): + try: + tablespace_name = item_dict.pop('tablespace_name') + size = item_dict.get('size') + partial_params = {} + if size: + partial_params['size'] = size + self.info['databases'][tablespace_name] = partial_params + except KeyError: + pass + + database_sql = 'SELECT ' \ + ' tablespace_name, sum(bytes) as "size"' \ + 'FROM dba_data_files GROUP BY tablespace_name' + if exclude_fields and 'db_size' in exclude_fields: + database_sql = "SELECT " \ + " tablespace_name " \ + "FROM dba_data_files GROUP BY tablespace_name" + + rtn, err = self.oracle_client.execute(database_sql, exception_to_fail=True) + if isinstance(rtn, dict): + _set_databases_value(rtn) + elif isinstance(rtn, list): + for i in rtn: + _set_databases_value(i) + + +# =========================================== +# Module execution. +# + + +def main(): + argument_spec = oracle_common_argument_spec() + argument_spec.update( + filter=dict(type='list'), + exclude_fields=dict(type='list'), + ) + + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + filter_ = module.params['filter'] + exclude_fields = module.params['exclude_fields'] + + if filter_: + filter_ = [f.strip() for f in filter_] + + if exclude_fields: + exclude_fields = set([f.strip() for f in exclude_fields]) + + oracle_client = OracleClient(module) + oracle = OracleInfo(module, oracle_client) + + module.exit_json(changed=False, **oracle.get_info(filter_, exclude_fields)) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/oracle_ping.py b/apps/ops/ansible/modules/oracle_ping.py new file mode 100644 index 000000000..df1069d11 --- /dev/null +++ b/apps/ops/ansible/modules/oracle_ping.py @@ -0,0 +1,107 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: oracle_ping +short_description: Check remote Oracle server availability +description: +- Simple module to check remote Oracle server availability. + +requirements: + - "oracledb" +''' + +EXAMPLES = ''' +- name: > + Ping Oracle server using non-default credentials and SSL + registering the return values into the result variable for future use + oracle_ping: + login_host: jumpserver + login_port: 1521 + login_user: jms + login_password: secret_pass + login_database: test_db +''' + +RETURN = ''' +is_available: + description: Oracle server availability. + returned: always + type: bool + sample: true +server_version: + description: Oracle server version. + returned: always + type: str + sample: '4.0.0' +conn_err_msg: + description: Connection error message. + returned: always + type: str + sample: '' +''' + +from ansible.module_utils.basic import AnsibleModule +from ops.ansible.modules_utils.oracle_common import ( + OracleClient, oracle_common_argument_spec +) + + +class OracleDBPing(object): + def __init__(self, module, oracle_client): + self.module = module + self.oracle_client = oracle_client + self.is_available = False + self.conn_err_msg = '' + self.version = '' + + def do(self): + self.get_oracle_version() + return self.is_available, self.version + + def get_err(self): + return self.conn_err_msg + + def get_oracle_version(self): + version_sql = 'SELECT VERSION FROM PRODUCT_COMPONENT_VERSION where ROWNUM=1' + rtn, err = self.oracle_client.execute(version_sql) + if err: + self.conn_err_msg = err + else: + self.version = rtn.get('version') + self.is_available = True + + +# ========================================= +# Module execution. +# + + +def main(): + argument_spec = oracle_common_argument_spec() + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + result = { + 'changed': False, 'is_available': False, 'server_version': '' + } + oracle_client = OracleClient(module) + + oracle_ping = OracleDBPing(module, oracle_client) + result["is_available"], result["server_version"] = oracle_ping.do() + conn_err_msg = oracle_ping.get_err() + oracle_client.close() + if conn_err_msg: + module.fail_json(msg='Unable to connect to database: %s' % conn_err_msg) + + return module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules/oracle_user.py b/apps/ops/ansible/modules/oracle_user.py new file mode 100644 index 000000000..9e23fe70c --- /dev/null +++ b/apps/ops/ansible/modules/oracle_user.py @@ -0,0 +1,215 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +module: oracle_user +short_description: Adds or removes a user from a Oracle database +description: + - Adds or removes a user from a Oracle database. + +options: + authentication_type: + description: + - Authentication type of the user(default password) + required: false + type: str + choices: ['external', 'global', 'no_authentication', 'password'] + default_tablespace: + description: + - The default tablespace for the user + - If not provided, the default is used + required: false + type: str + oracle_home: + description: + - Define the directory into which all Oracle software is installed. + - Define ORACLE_HOME environment variable if set. + type: str + state: + description: + - The database user state. + default: present + choices: [absent, present] + type: str + update_password: + default: always + choices: [always, on_create] + description: + - C(always) will always update passwords and cause the module to return changed. + - C(on_create) will only set the password for newly created users. + type: str + temporary_tablespace: + description: + - The default temporary tablespace for the user + - If not provided, the default is used + required: false + type: str + name: + description: + - The name of the user to add or remove. + required: true + aliases: [user] + type: str + password: + description: + - The password to use for the user. + type: str + aliases: [pass] + +requirements: + - "oracledb" +''' + +EXAMPLES = ''' +- name: Create default tablespace user with name 'jms' and password '123456'. + oracle_user: + hostname: "remote server" + login_database: "helowin" + login_username: "system" + login_password: "123456" + name: "jms" + password: "123456" + +- name: Delete user with name 'jms'. + oracle_user: + hostname: "remote server" + login_database: "helowin" + login_username: "system" + login_password: "123456" + name: "jms" + state: "absent" +''' + +RETURN = ''' +name: + description: The name of the user to add or remove. + returned: success + type: str +''' + +from ansible.module_utils.basic import AnsibleModule + +from ops.ansible.modules_utils.oracle_common import ( + OracleClient, oracle_common_argument_spec +) + + +def user_find(oracle_client, username): + user = None + username = username.upper() + user_find_sql = "select username, " \ + " authentication_type, " \ + " default_tablespace, " \ + " temporary_tablespace " \ + "from dba_users where username='%s'" % username + rtn, err = oracle_client.execute(user_find_sql) + if isinstance(rtn, dict): + user = rtn + return user + + +def user_add( + module, oracle_client, username, password, auth_type, + default_tablespace, temporary_tablespace +): + username = username.upper() + extend_sql = None + user = user_find(oracle_client, username) + auth_type = auth_type.lower() + identified_suffix_map = { + 'external': 'identified externally ', + 'global': 'identified globally ', + 'password': 'identified by "%s" ', + } + if user: + user_sql = "alter user %s " % username + user_sql += identified_suffix_map.get(auth_type, 'no authentication ') % password + + if default_tablespace and default_tablespace.lower() != user['default_tablespace'].lower(): + user_sql += 'default tablespace %s quota unlimited on %s ' % (default_tablespace, default_tablespace) + if temporary_tablespace and temporary_tablespace.lower() != user['temporary_tablespace'].lower(): + user_sql += 'temporary tablespace %s ' % temporary_tablespace + else: + user_sql = "create user %s " % username + user_sql += identified_suffix_map.get(auth_type, 'no authentication ') % password + if default_tablespace: + user_sql += 'default tablespace %s quota unlimited on %s ' % (default_tablespace, default_tablespace) + if temporary_tablespace: + user_sql += 'temporary tablespace %s ' % temporary_tablespace + extend_sql = 'grant connect to %s' % username + + rtn, err = oracle_client.execute(user_sql) + if err: + module.fail_json(msg='Cannot add/edit user %s: %s' % (username, err), changed=False) + else: + if extend_sql: + oracle_client.execute(extend_sql) + module.exit_json(msg='User %s has been created.' % username, changed=True, name=username) + + +def user_remove(module, oracle_client, username): + user = user_find(oracle_client, username) + + if user: + rtn, err = oracle_client.execute('drop user %s cascade' % username) + if err: + module.fail_json(msg='Cannot drop user %s: %s' % (username, err), changed=False) + else: + module.exit_json(msg='User %s dropped.' % username, changed=True, name=username) + else: + module.exit_json(msg="User %s doesn't exist." % username, changed=False, name=username) + + +# ========================================= +# Module execution. +# + +def main(): + argument_spec = oracle_common_argument_spec() + argument_spec.update( + authentication_type=dict( + type='str', required=False, + choices=['external', 'global', 'no_authentication', 'password'] + ), + default_tablespace=dict(required=False, aliases=['db']), + name=dict(required=True, aliases=['user']), + password=dict(aliases=['pass'], no_log=True), + state=dict(type='str', default='present', choices=['absent', 'present']), + update_password=dict(default="always", choices=["always", "on_create"], no_log=False), + temporary_tablespace=dict(type='str', default=None), + ) + module = AnsibleModule( + argument_spec=argument_spec, + supports_check_mode=True, + ) + + authentication_type = module.params['authentication_type'] or 'password' + default_tablespace = module.params['default_tablespace'] + user = module.params['name'] + password = module.params['password'] + state = module.params['state'] + update_password = module.params['update_password'] + temporary_tablespace = module.params['temporary_tablespace'] + + oracle_client = OracleClient(module) + if state == 'present': + if password is None and update_password == 'always': + module.fail_json( + msg='password parameter required when adding a user unless update_password is set to on_create' + ) + user_add( + module, oracle_client, username=user, password=password, + auth_type=authentication_type, default_tablespace=default_tablespace, + temporary_tablespace=temporary_tablespace + ) + elif state == 'absent': + user_remove(oracle_client) + module.exit_json(changed=True, user=user) + + +if __name__ == '__main__': + main() diff --git a/apps/ops/ansible/modules_utils/__init__.py b/apps/ops/ansible/modules_utils/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/apps/ops/ansible/modules_utils/oracle_common.py b/apps/ops/ansible/modules_utils/oracle_common.py new file mode 100644 index 000000000..c88f04373 --- /dev/null +++ b/apps/ops/ansible/modules_utils/oracle_common.py @@ -0,0 +1,94 @@ +import os + +import oracledb + +from oracledb.exceptions import DatabaseError +from ansible.module_utils._text import to_native + + +def oracle_common_argument_spec(): + """ + Returns a dict containing common options shared across the Oracle modules. + """ + options = dict( + login_user=dict(type='str', required=False), + login_password=dict(type='str', required=False, no_log=True), + login_database=dict(type='str', required=False, default='test'), + login_host=dict(type='str', required=False, default='localhost'), + login_port=dict(type='int', required=False, default=1521), + oracle_home=dict(type='str', required=False), + mode=dict(type='str', required=False), + ) + return options + + +class OracleClient(object): + def __init__(self, module): + self.module = module + self._conn = None + self._cursor = None + self.connect_params = {} + + self.init_params() + + def init_params(self): + params = self.module.params + hostname = params['login_host'] + port = params['login_port'] + service_name = params['login_database'] + username = params['login_user'] + password = params['login_password'] + oracle_home = params['oracle_home'] + mode = params['mode'] + + if oracle_home: + os.environ.setdefault('ORACLE_HOME', oracle_home) + if mode == 'sysdba': + self.connect_params['mode'] = oracledb.SYSDBA + + self.connect_params['host'] = hostname + self.connect_params['port'] = port + self.connect_params['user'] = username + self.connect_params['password'] = password + self.connect_params['service_name'] = service_name + + @property + def cursor(self): + if self._cursor is None: + try: + oracledb.init_oracle_client(lib_dir='/Users/jiangweidong/Downloads/instantclient_19_8') + self._conn = oracledb.connect(**self.connect_params) + self._cursor = self._conn.cursor() + except DatabaseError as err: + self.module.fail_json( + msg="Unable to connect to database: %s, %s" % (to_native(err), self.connect_params) + ) + return self._cursor + + def execute(self, sql, exception_to_fail=False): + sql = sql[:-1] if sql.endswith(';') else sql + result, error = None, None + try: + self.cursor.execute(sql) + sql_header = self.cursor.description or [] + column_names = [description[0].lower() for description in sql_header] + if column_names: + result = [dict(zip(column_names, row)) for row in self.cursor] + result = result[0] if len(result) == 1 else result + else: + result = None + except DatabaseError as err: + error = err + if exception_to_fail and error: + self.module.fail_json(msg='Cannot execute sql: %s' % to_native(error)) + return result, error + + def close(self): + try: + if self._cursor: + self._cursor.close() + if self._conn: + self._conn.close() + except: + pass +