diff --git a/.codacy.yml b/.codacy.yml index a21c364..a1af41c 100644 --- a/.codacy.yml +++ b/.codacy.yml @@ -1,4 +1,6 @@ --- engines: pylint: - python_version: 3 \ No newline at end of file + python_version: 3 +exclude_paths: + - "plugins/modules/cockroach_**" # should be removed once cockroach modules are tested diff --git a/README.md b/README.md index cdccfb7..6a4d9d7 100644 --- a/README.md +++ b/README.md @@ -296,6 +296,13 @@ Name | Description [community.missing_collection.checkly_snippets_info](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.checkly_snippets_info_module.rst)|Get information about checkly snippets. [community.missing_collection.checkly_variables](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.checkly_variables_module.rst)|Management of the checkly environment variables. [community.missing_collection.checkly_variables_info](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.checkly_variables_info_module.rst)|Get information about checkly environment variables. +[community.missing_collection.cockroach_cert](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.cockroach_cert_module.rst)|Manage user certificates in a cockroach cluster +[community.missing_collection.cockroach_cluster](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.cockroach_cluster_module.rst)|Manage a cockroach cluster +[community.missing_collection.cockroach_cluster_settings](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.cockroach_cluster_settings_module.rst)|Manage settings in a Cockroach cluster +[community.missing_collection.cockroach_db](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.cockroach_db_module.rst)|Manage databases in a cockroach cluster +[community.missing_collection.cockroach_info](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.cockroach_info_module.rst)|Returns facts about a Cockroach Cluster +[community.missing_collection.cockroach_privs](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.cockroach_privs_module.rst)|Manage user privileges in a cockroach db +[community.missing_collection.cockroach_user](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.cockroach_user_module.rst)|Manage users in a cockroach cluster [community.missing_collection.consul_coordinate_info](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.consul_coordinate_info_module.rst)|Get information from Consul (Coordinate). [community.missing_collection.consul_health](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.consul_health_module.rst)|Get information from Consul (Health). [community.missing_collection.consul_members](https://github.com/116davinder/ansible.missing_collection/blob/master/docs/community.missing_collection.consul_members_module.rst)|Get information from Consul (Members). diff --git a/docs/community.missing_collection.cockroach_cert_module.rst b/docs/community.missing_collection.cockroach_cert_module.rst new file mode 100644 index 0000000..7187975 --- /dev/null +++ b/docs/community.missing_collection.cockroach_cert_module.rst @@ -0,0 +1,79 @@ +.. _community.missing_collection.cockroach_cert_module: + + +******************************************* +community.missing_collection.cockroach_cert +******************************************* + +**Manage user certificates in a cockroach cluster** + + +Version added: 0.4.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Manage user certificates in a cockroach cluster + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ name + +
+ - + / required +
+
+ Default:
"None"
+
+
The name of the user to generate certificate for
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + - name: create a certificate for a user + cockroach_cert: + name: user1 + path: "/var/lib/cockroach" + + + + +Status +------ + + +Authors +~~~~~~~ + +- Oscar C, based on the work of Mikael Sandstrom, oravirt@gmail.com, @oravirt diff --git a/docs/community.missing_collection.cockroach_cluster_module.rst b/docs/community.missing_collection.cockroach_cluster_module.rst new file mode 100644 index 0000000..5e5b26b --- /dev/null +++ b/docs/community.missing_collection.cockroach_cluster_module.rst @@ -0,0 +1,88 @@ +.. _community.missing_collection.cockroach_cluster_module: + + +********************************************** +community.missing_collection.cockroach_cluster +********************************************** + +**Manage a cockroach cluster** + + +Version added: 0.4.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Manage a cockroach cluster + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ name + +
+ - + / required +
+
+ Default:
"None"
+
+
The name of the service
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + - name: create a master node + cockroach_cluster: + path: "/var/lib/cockroach" + host: "{{ inventory_hostname }}" + state: started + + - name: join a node to a cluster + cockroach_cluster: + path: "/var/lib/cockroach" + host: "{{ inventory_hostname }}" + join: true + cluster_master: "node1:{{ port }}" + state: started + + + + +Status +------ + + +Authors +~~~~~~~ + +- Mikael Sandström, oravirt@gmail.com, @oravirt diff --git a/docs/community.missing_collection.cockroach_cluster_settings_module.rst b/docs/community.missing_collection.cockroach_cluster_settings_module.rst new file mode 100644 index 0000000..c0fa0d5 --- /dev/null +++ b/docs/community.missing_collection.cockroach_cluster_settings_module.rst @@ -0,0 +1,99 @@ +.. _community.missing_collection.cockroach_cluster_settings_module: + + +******************************************************* +community.missing_collection.cockroach_cluster_settings +******************************************************* + +**Manage settings in a Cockroach cluster** + + +Version added: 0.4.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Manage settings in a Cockroach cluster + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ name + +
+ - + / required +
+
+ Default:
"None"
+
+
The name of the setting
+
+
+ value + +
+ - + / required +
+
+ Default:
"None"
+
+
The value of the setting
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + - name: manage a setting + cockroach_cluster_settings: + name: 'diagnostics.reporting.enabled' + value: False + path: /var/lib/cockroach + host: "{{ inventory_hostname }}" + state: present + + + + +Status +------ + + +Authors +~~~~~~~ + +- Mikael Sandström, oravirt@gmail.com, @oravirt diff --git a/docs/community.missing_collection.cockroach_db_module.rst b/docs/community.missing_collection.cockroach_db_module.rst new file mode 100644 index 0000000..2e66462 --- /dev/null +++ b/docs/community.missing_collection.cockroach_db_module.rst @@ -0,0 +1,80 @@ +.. _community.missing_collection.cockroach_db_module: + + +***************************************** +community.missing_collection.cockroach_db +***************************************** + +**Manage databases in a cockroach cluster** + + +Version added: 0.4.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Manage databases in a cockroach cluster + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ name + +
+ - + / required +
+
+ Default:
"None"
+
+
The name of the database
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + # Create a database + cockroach_db: name=db1 path=/var/lib/cockroach host={{ inventory_hostname }} state=present + + # Drop a database + cockroach_db: name=db1 path=/var/lib/cockroach host={{ inventory_hostname }} state=absent + + + + +Status +------ + + +Authors +~~~~~~~ + +- Mikael Sandstrom, oravirt@gmail.com, @oravirt diff --git a/docs/community.missing_collection.cockroach_info_module.rst b/docs/community.missing_collection.cockroach_info_module.rst new file mode 100644 index 0000000..cb6599f --- /dev/null +++ b/docs/community.missing_collection.cockroach_info_module.rst @@ -0,0 +1,155 @@ +.. _community.missing_collection.cockroach_info_module: + + +******************************************* +community.missing_collection.cockroach_info +******************************************* + +**Returns facts about a Cockroach Cluster** + + +Version added: 0.4.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Returns facts about a Cockroach Cluster + + + +Requirements +------------ +The below requirements are needed on the host that executes this module. + +- psycopg2 + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ certs_dir + +
+ - +
+
+ +
Path to certificates on the cluster host
+
+
+ host + +
+ - +
+
+ Default:
"localhost"
+
+
The cluster host
+
+
+ port + +
+ - +
+
+ Default:
26257
+
+
The cluster port
+
+
+ user + +
+ - + / required +
+
+ Default:
"root"
+
+
The cluster user to connect as
+
+
+ + +Notes +----- + +.. note:: + - psycopg2 needs to be installed + + + +Examples +-------- + +.. code-block:: yaml + + - name: Gather Facts about CRDB Cluster + hosts: clusterhosts + become: true + vars: + cockroach_user: cockroach + path: /var/lib/cockroach/2.1.6 + certs_dir: /var/lib/cockroach/certs + user: root + tasks: + - name: facts + cockroach_info: + user={{ user |default(omit)}} + path={{ path |default(omit)}} + host={{ ansible_fqdn }} + certs_dir={{ certs_dir |default (omit)}} + tags: facts + become_user: "{{ cockroach_user }}" + register: facts + + - debug: msg="version - {{facts.ansible_facts.cockroach_version}}, node_id - {{facts.ansible_facts.node_id}}" + - debug: msg="org - {{ facts.ansible_facts.cluster_settings['cluster.organization'] }}{% if facts.ansible_facts.enterprise_license is defined %}, license - {{ facts.ansible_facts.enterprise_license }}{% endif %}" + + + + +Status +------ + + +Authors +~~~~~~~ + +- Mikael Sandström, oravirt@gmail.com, @oravirt diff --git a/docs/community.missing_collection.cockroach_privs_module.rst b/docs/community.missing_collection.cockroach_privs_module.rst new file mode 100644 index 0000000..6182ebe --- /dev/null +++ b/docs/community.missing_collection.cockroach_privs_module.rst @@ -0,0 +1,112 @@ +.. _community.missing_collection.cockroach_privs_module: + + +******************************************** +community.missing_collection.cockroach_privs +******************************************** + +**Manage user privileges in a cockroach db** + + +Version added: 0.4.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Manage user privileges in a cockroach db + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ db + +
+ - + / required +
+
+ Default:
"None"
+
+
Name of the database to set permissions in
+
+
+ name + +
+ - + / required +
+
+ Default:
"None"
+
+
the name of the user to set permissions
+
+
+ privs + +
+ - + / required +
+
+ Default:
"None"
+
+
Privileges for the user
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + # Grant ALL privileges to user1 on db1 + cockroach_privs: name=user1 db=db1 privs=ALL path=/var/lib/cockroach + + + + +Status +------ + + +Authors +~~~~~~~ + +- Oscar C +- Mikael Sandstrom @oravirt diff --git a/docs/community.missing_collection.cockroach_user_module.rst b/docs/community.missing_collection.cockroach_user_module.rst new file mode 100644 index 0000000..d45e631 --- /dev/null +++ b/docs/community.missing_collection.cockroach_user_module.rst @@ -0,0 +1,81 @@ +.. _community.missing_collection.cockroach_user_module: + + +******************************************* +community.missing_collection.cockroach_user +******************************************* + +**Manage users in a cockroach cluster** + + +Version added: 0.4.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- Manage users in a cockroach cluster + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + +
ParameterChoices/DefaultsComments
+
+ name + +
+ - + / required +
+
+ Default:
"None"
+
+
The name of the user
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + # Create a user + cockroach_user: name=user1 path=/var/lib/cockroach host={{ inventory_hostname }} state=present + + # Delete a user + cockroach_user: name=user1 path=/var/lib/cockroach host={{ inventory_hostname }} state=absent + + + + +Status +------ + + +Authors +~~~~~~~ + +- Oscar C +- Mikael Sandström, oravirt@gmail.com, @oravirt diff --git a/docs/community.missing_collection.doh_module.rst b/docs/community.missing_collection.doh_module.rst index 8e52198..442dd1c 100644 --- a/docs/community.missing_collection.doh_module.rst +++ b/docs/community.missing_collection.doh_module.rst @@ -17,10 +17,11 @@ Version added: 0.4.0 Synopsis -------- -- DNS Lookup over HTTPS from various Public DOH Servers like Google/Cloudflare/Quad9. +- DNS Lookup over HTTPS from various Public DOH Servers like Google/Cloudflare/Quad9/Alibaba. - https://developers.cloudflare.com/1.1.1.1/encrypted-dns/dns-over-https/make-api-requests/dns-json - https://developers.google.com/speed/public-dns/docs/doh/json - https://www.quad9.net/news/blog/doh-with-quad9-dns-servers/ +- https://www.alibabacloud.com/help/en/doc-detail/171666.html @@ -110,6 +111,7 @@ Parameters
  • google
  • cloudflare ←
  • quad9
  • +
  • alibaba
  • @@ -161,6 +163,12 @@ Examples name: "example.com" type: "MX" + - name: fetch A record from Alibaba DNS over HTTPS + community.missing_collection.doh: + source: "alibaba" + name: "example.com" + type: "A" + Return Values diff --git a/plugins/modules/cockroach_cert.py b/plugins/modules/cockroach_cert.py new file mode 100644 index 0000000..411e29b --- /dev/null +++ b/plugins/modules/cockroach_cert.py @@ -0,0 +1,111 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = """ +--- +module: cockroach_cert +short_description: Manage user certificates in a cockroach cluster +description: + - Manage user certificates in a cockroach cluster +version_added: "0.4.0" +options: + name: + description: + - The name of the user to generate certificate for + required: true + default: None + +notes: +author: Oscar C, based on the work of Mikael Sandstrom, oravirt@gmail.com, @oravirt +""" + +EXAMPLES = """ +- name: create a certificate for a user + cockroach_cert: + name: user1 + path: "/var/lib/cockroach" +""" + +from ansible.module_utils.basic import * +import os + + +# Check if the service exists +def check_user_has_certificate(module, msg, path, name, certs_dir): + command = "%s/cockroach cert list" % (path) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + + # module.fail_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + # module.exit_json(msg=rc) + if rc != 0: + msg[0] = "Something went wrong, stderr: %s" % (stderr) + + if "client." + name.lower() + ".crt" in stdout.lower(): + return True + else: + return False + + +def create_certificate_for_user(module, msg, path, name, certs_dir, ca_key): + command = "%s/cockroach cert create-client %s" % (path, name) + if certs_dir: + command += " --certs-dir=%s" % (certs_dir) + if ca_key: + command += " --ca-key=%s" % (ca_key) + + # module.fail_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Creating user certificate for %s failed. %s" % (name, stderr) + return False + else: + return True + + +def main(): + + msg = [""] + + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=["user_name", "user"]), + path=dict(required=False), + certs_dir=dict(required=False), + ca_key=dict(required=False), + ), + ) + + name = module.params["name"] + path = module.params["path"] + certs_dir = module.params["certs_dir"] + ca_key = module.params["ca_key"] + + if not path: + try: + command = "cockroach version" + module.run_command(command) + except OSError as e: + msg[0] = "Couldnt find cockroach executable. Check the path. stderr: %s" % ( + e + ) + module.fail_json(msg=msg[0], failed=True) + + if not check_user_has_certificate(module, msg, path, name, certs_dir): + if create_certificate_for_user(module, msg, path, name, certs_dir, ca_key): + msg[0] = "Successfully created certificate for user %s " % (name) + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "User %s already has certificate" % (name) + module.exit_json(msg=msg[0], changed=False) + + module.exit_json(msg="Unhandled exit", changed=False) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/cockroach_cluster.py b/plugins/modules/cockroach_cluster.py new file mode 100644 index 0000000..97bf1b5 --- /dev/null +++ b/plugins/modules/cockroach_cluster.py @@ -0,0 +1,227 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = """ +--- +module: cockroach_cluster +short_description: Manage a cockroach cluster +description: + - Manage a cockroach cluster +version_added: "0.4.0" +options: + name: + description: + - The name of the service + required: true + default: None +author: Mikael Sandström, oravirt@gmail.com, @oravirt +""" + +EXAMPLES = """ +- name: create a master node + cockroach_cluster: + path: "/var/lib/cockroach" + host: "{{ inventory_hostname }}" + state: started + +- name: join a node to a cluster + cockroach_cluster: + path: "/var/lib/cockroach" + host: "{{ inventory_hostname }}" + join: true + cluster_master: "node1:{{ port }}" + state: started +""" + +from ansible.module_utils.basic import * +import os + +# Check if the service exists +def check_node_started(module, msg, path): + + command = "ps -ef" + (rc, stdout, stderr) = module.run_command(command) + searchstring = "%s/cockroach" % (path) + if rc != 0: + msg[0] = "Something went wrong, stderr: %s" % (stderr) + + if searchstring in stdout.lower(): + return True + else: + return False + + +def start_node( + module, + msg, + path, + store, + host, + port, + http_port, + join, + cache, + log_dir, + certs_dir, + locality, + cluster_master, +): + + command = "nohup %s/cockroach start --http-port=%s --port=%s --host=%s " % ( + path, + http_port, + port, + host, + ) + if store: + command += " --store=%s" % (store) + if join: + command += " --join=%s:%s" % (cluster_master, port) + if cache: + command += " --cache=%s" % (cache) + if locality: + command += " --locality=%s" % (locality) + if log_dir: + command += " --log-dir %s" % (log_dir) + if not certs_dir: + command += " --insecure " + elif certs_dir: + command += " --certs-dir %s" % (certs_dir) + command += " --background" + + # module.exit_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Starting failed. %s" % (stderr) + return False + else: + msg[0] = "Successfully started node - %s " % (stdout) + return True + + +def stop_node(module, msg, path, host, port, certs_dir): + + command = "%s/cockroach quit --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Failed to stop node. %s" % (stderr) + return False + else: + msg[0] = "Successfully stopped node - %s" % (stdout.strip()) + return True + + +def status_node(module, msg, path): + + command = "%s/cockroach node status" % (path) + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Failed to check status for node: %s" % (stderr) + return False + else: + msg[0] = stdout + return True + + +def main(): + + msg = [""] + + module = AnsibleModule( + argument_spec=dict( + store=dict(required=False), + state=dict(default="started", choices=["started", "stopped", "status"]), + path=dict(required=False), + cache=dict(required=False), + log_dir=dict(required=False), + certs_dir=dict(required=False), + locality=dict(required=False), + port=dict(default=26257), + http_port=dict(default=8080), + host=dict(default="localhost"), + join=dict(required=False, type=bool), + cluster_master=dict(required=False), + ), + ) + + store = module.params["store"] + state = module.params["state"] + path = module.params["path"] + cache = module.params["cache"] + log_dir = module.params["log_dir"] + certs_dir = module.params["certs_dir"] + locality = module.params["locality"] + port = module.params["port"] + http_port = module.params["http_port"] + host = module.params["host"] + join = module.params["join"] + cluster_master = module.params["cluster_master"] + + if not path: + try: + command = "cockroach version" + module.run_command(command) + except OSError as e: + msg[0] = "Couldnt find cockroach executable. Check the path. stderr: %s" % ( + e + ) + module.fail_json(msg=msg[0], failed=True) + + if not store: + store = "%s/cockroach-data" % (path) + + if state == "started": + if not check_node_started(module, msg, path): + if start_node( + module, + msg, + path, + store, + host, + port, + http_port, + join, + cache, + log_dir, + certs_dir, + locality, + cluster_master, + ): + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "Already running" + module.exit_json(msg=msg[0], changed=False) + + elif state == "stopped": + if check_node_started(module, msg, path): + if stop_node(module, msg, path, host, port, certs_dir): + # msg[0] = 'Successfully dropped database %s ' % (name) + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "Node already stopped" + module.exit_json(msg=msg[0], changed=False) + + elif state == "status": + if check_node_started(module, msg, path): + if status_node(module, msg, path, port, host, certs_dir): + # msg[0] = 'Successfully dropped database %s ' % (name) + module.exit_json(msg=msg[0], changed=False) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "Node not running" + module.exit_json(msg=msg[0], changed=False) + + module.exit_json(msg="Unhandled exit", changed=False) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/cockroach_cluster_settings.py b/plugins/modules/cockroach_cluster_settings.py new file mode 100644 index 0000000..0ee3f82 --- /dev/null +++ b/plugins/modules/cockroach_cluster_settings.py @@ -0,0 +1,123 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = """ +--- +module: cockroach_cluster_settings +short_description: Manage settings in a Cockroach cluster +description: + - Manage settings in a Cockroach cluster +version_added: "0.4.0" +options: + name: + description: + - The name of the setting + required: true + default: None + value: + description: + - The value of the setting + required: true + default: None +author: Mikael Sandström, oravirt@gmail.com, @oravirt +""" + +EXAMPLES = """ +- name: manage a setting + cockroach_cluster_settings: + name: 'diagnostics.reporting.enabled' + value: False + path: /var/lib/cockroach + host: "{{ inventory_hostname }}" + state: present +""" +import os + + +def get_current_setting(module, msg, path, host, port, name, value, certs_dir): + + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += ' --execute "show cluster setting %s"' % (name) + + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg = "Something went wrong, stderr: %s" % (stderr) + + if value.lower() in stdout.lower(): + return True + else: + return False + + +def enforce_setting(module, msg, path, host, port, name, value, certs_dir): + + currval = get_current_setting(module, msg, path, host, port, name, value, certs_dir) + if currval: + module.exit_json(msg="Nothing to change", changed=False) + + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += " --execute \"set cluster setting %s = '%s' \"" % (name, value) + + (rc, stdout, stderr) = module.run_command(command) + + if rc != 0: + msg = "stderr: %s" % (stderr) + module.fail_json(msg=msg) + else: + return True + + +def main(): + + msg = [""] + + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True), + value=dict(required=True), + host=dict(required=False, default="localhost"), + port=dict(required=False, default=26257), + state=dict(default="present", choices=["present", "absent"]), + path=dict(required=False), + certs_dir=dict(required=False), + ), + ) + + name = module.params["name"] + value = module.params["value"] + host = module.params["host"] + port = module.params["port"] + state = module.params["state"] + path = module.params["path"] + certs_dir = module.params["certs_dir"] + + if not path: + try: + command = "cockroach version" + module.run_command(command) + except OSError as e: + msg = "Couldnt find cockroach executable. Check the path. stderr: %s" % (e) + module.fail_json(msg=msg, failed=True) + + if state == "present": + if enforce_setting(module, msg, path, host, port, name, value, certs_dir): + msg = "Successfully changed cluster setting: %s to %s" % (name, value) + module.exit_json(msg=msg, changed=True) + else: + module.fail_json(msg=msg, changed=False) + + module.exit_json(msg="Unhandled exit", changed=False) + + +from ansible.module_utils.basic import * + +if __name__ == "__main__": + main() diff --git a/plugins/modules/cockroach_db.py b/plugins/modules/cockroach_db.py new file mode 100755 index 0000000..af4be3f --- /dev/null +++ b/plugins/modules/cockroach_db.py @@ -0,0 +1,156 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = """ +--- +module: cockroach_db +short_description: Manage databases in a cockroach cluster +description: + - Manage databases in a cockroach cluster +version_added: 0.4.0 +options: + name: + description: + - The name of the database + required: true + default: None + +notes: +author: Mikael Sandstrom, oravirt@gmail.com, @oravirt +""" + +EXAMPLES = """ +# Create a database +cockroach_db: name=db1 path=/var/lib/cockroach host={{ inventory_hostname }} state=present + +# Drop a database +cockroach_db: name=db1 path=/var/lib/cockroach host={{ inventory_hostname }} state=absent + +""" +import os + + +# Check if the service exists +def check_database_exists(module, msg, path, host, port, name, certs_dir): + + # command = "%s/cockroach sql --host=%s --port=%s --insecure --execute \"show databases\"" % (path, host, port) + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += ( + " --execute \"SELECT datname FROM pg_catalog.pg_database where lower(datname) = lower('%s') \"" # nosec + % (name) + ) + + # module.fail_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + # module.exit_json(msg=rc) + if rc != 0: + msg[0] = "Something went wrong, stderr: %s" % (stderr) + + if name.lower() in stdout.lower(): + return True + else: + return False + + +def create_database(module, msg, path, host, port, name, certs_dir): + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += ' --execute "create database %s"' % (name) + + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Creating database %s failed. %s, command: %s" % ( + name, + stderr, + command, + ) + return False + else: + return True + + +def remove_database(module, msg, path, host, port, name, certs_dir): + + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += ' --execute "drop database %s"' % (name) + + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Removing database %s failed: %s" % (name, stderr) + return False + else: + return True + + +def main(): + + msg = [""] + + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=["database_name", "db"]), + host=dict(required=False, default="localhost"), + port=dict(required=False, default=26257), + state=dict(default="present", choices=["present", "absent"]), + path=dict(required=False), + certs_dir=dict(required=False), + ), + ) + + name = module.params["name"] + host = module.params["host"] + port = module.params["port"] + state = module.params["state"] + path = module.params["path"] + certs_dir = module.params["certs_dir"] + + if not path: + try: + command = "cockroach version" + module.run_command(command) + except OSError as e: + msg[0] = "Couldnt find cockroach executable. Check the path. stderr: %s" % ( + e + ) + module.fail_json(msg=msg[0], failed=True) + + if state == "present": + if not check_database_exists(module, msg, path, host, port, name, certs_dir): + if create_database(module, msg, path, host, port, name, certs_dir): + msg[0] = "Successfully created database %s " % (name) + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "Database %s already exists" % (name) + module.exit_json(msg=msg[0], changed=False) + + elif state == "absent": + if check_database_exists(module, msg, path, host, port, name, certs_dir): + if remove_database(module, msg, path, host, port, name, certs_dir): + msg[0] = "Successfully dropped database %s " % (name) + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "Database %s doesn't exist" % (name) + module.exit_json(msg=msg[0], changed=False) + + module.exit_json(msg="Unhandled exit", changed=False) + + +from ansible.module_utils.basic import * + +if __name__ == "__main__": + main() diff --git a/plugins/modules/cockroach_info.py b/plugins/modules/cockroach_info.py new file mode 100644 index 0000000..c583415 --- /dev/null +++ b/plugins/modules/cockroach_info.py @@ -0,0 +1,231 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = """ +--- +module: cockroach_info +short_description: Returns facts about a Cockroach Cluster +description: + - Returns facts about a Cockroach Cluster +version_added: 0.4.0 +options: + host: + description: + - The cluster host + required: false + default: localhost + port: + description: + - The cluster port + required: false + default: 26257 + user: + description: + - The cluster user to connect as + required: True + default: root + certs_dir: + description: + - Path to certificates on the cluster host + +notes: + - psycopg2 needs to be installed +requirements: [ "psycopg2" ] +author: Mikael Sandström, oravirt@gmail.com, @oravirt +""" + +EXAMPLES = """ +- name: Gather Facts about CRDB Cluster + hosts: clusterhosts + become: true + vars: + cockroach_user: cockroach + path: /var/lib/cockroach/2.1.6 + certs_dir: /var/lib/cockroach/certs + user: root + tasks: + - name: facts + cockroach_info: + user={{ user |default(omit)}} + path={{ path |default(omit)}} + host={{ ansible_fqdn }} + certs_dir={{ certs_dir |default (omit)}} + tags: facts + become_user: "{{ cockroach_user }}" + register: facts + + - debug: msg="version - {{facts.ansible_facts.cockroach_version}}, node_id - {{facts.ansible_facts.node_id}}" + - debug: msg="org - {{ facts.ansible_facts.cluster_settings['cluster.organization'] }}{% if facts.ansible_facts.enterprise_license is defined %}, license - {{ facts.ansible_facts.enterprise_license }}{% endif %}" + +""" +from datetime import datetime + +try: + from OpenSSL import crypto as c +except ImportError: + pyopenssl_exists = False +else: + pyopenssl_exists = True + +try: + import psycopg2 +except ImportError: + psycopg2_exists = False +else: + psycopg2_exists = True + + +def exec_sql_get(module, cur, query): + + try: + cur.execute(query) + result = cur.fetchall() + except psycopg2.DatabaseError as e: + msg = e + module.fail_json(msg=msg, failed=True) + + return result + + +def get_enterprise_license(module, path, host, port, certs_dir): + + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += ' --execute "show cluster setting enterprise.license"' + + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg = "Something went wrong, stderr: %s, command: %s" % (stderr, command) + module.fail_json(msg) + return stdout + + +def get_cert_expiry_date(cert): + datefmt = "%Y%m%d%H%M%SZ" + cert = c.load_certificate(c.FILETYPE_PEM, file(cert).read()) + return datetime.datetime.strptime(cert.get_notAfter(), datefmt) + + +# Ansible code +def main(): + + msg = [""] + + module = AnsibleModule( + argument_spec=dict( + host=dict(required=False, default="localhost"), + port=dict(required=False, default=26257), + path=dict(required=False), + user=dict(required=False, default="root"), + certs_dir=dict(required=False), + ), + ) + + host = module.params["host"] + port = module.params["port"] + path = module.params["path"] + user = module.params["user"] + certs_dir = module.params["certs_dir"] + + cacert = "%s/ca.crt" % (certs_dir) + nodecert = "%s/node.crt" % (certs_dir) + clientkey = "%s/client.%s.key" % (certs_dir, user) + clientcert = "%s/client.%s.crt" % (certs_dir, user) + + if not psycopg2_exists: + msg = "psycopg2 does not seem to exist. Please install (e.g pip install psycopg2-binary)" + module.fail_json(msg=msg) + + try: + + # dsn = psycopg2.make_dsn(host=host, port=port, database=database, user=user, + # sslmode='require', sslkey=sslkey,sslcert=sslcert) + # conn = psycopg2.connect(dsn) + if certs_dir: + conn = psycopg2.connect( + user=user, + sslmode="require", + # sslrootcert=rootcert, + sslkey=clientkey, + sslcert=clientcert, + port=port, + host=host, + ) + else: + conn = psycopg2.connect(user=user, sslmode="disable", port=port, host=host) + except psycopg2.DatabaseError as exc: + error = exc + msg = "Could not connect: %s" % (error) + module.fail_json(msg=msg) + + cur = conn.cursor() + + version_sql = ( + "select value from system.crdb_internal.node_build_info where field = 'Version'" + ) + node_id_sql = "select distinct(node_id) from system.crdb_internal.node_build_info" + database_sql = "select datname from system.pg_catalog.pg_database where datname not in ('system','defaultdb','postgres')" + user_sql = "select usename from system.pg_catalog.pg_user where usename not in ('adminui','root')" + cluster_settings_sql = ( + "select variable, value from system.crdb_internal.cluster_settings" + ) + cluster_id_sql = "select * from crdb_internal.cluster_id()" + version = exec_sql_get(module, cur, version_sql)[0][0] + node_id = exec_sql_get(module, cur, node_id_sql)[0][0] + cluster_id = exec_sql_get(module, cur, cluster_id_sql)[0][0] + cluster_settings = exec_sql_get(module, cur, cluster_settings_sql) + databases_ = exec_sql_get(module, cur, database_sql) + users_ = exec_sql_get(module, cur, user_sql) + + db_dict = [] + for d in databases_: + db_dict.append(d[0]) + user_dict = [] + for u in users_: + user_dict.append(u[0]) + + facts = {"cockroach_version": version.lstrip("v")} + facts.update({"node_id": node_id}) + facts.update({"cluster_id": cluster_id}) + facts.update({"databases": db_dict}) + facts.update({"dbusers": user_dict}) + facts.update({"cluster_settings": dict(cluster_settings)}) + + # Get Cluster settings + if path: + license = get_enterprise_license(module, path, host, port, certs_dir) + facts.update({"enterprise_license": license.split("\n")[1].rstrip("\n")}) + + # Get certificate information + if pyopenssl_exists and certs_dir: + currdate = datetime.datetime.now() + cert_dict = {} + rootcertexpiry = get_cert_expiry_date(clientcert) + nodecertexpiry = get_cert_expiry_date(nodecert) + cacertexpiry = get_cert_expiry_date(cacert) + cert_dict["certificate_information"] = {} + cert_dict["certificate_information"]["rootcert"] = clientcert + cert_dict["certificate_information"]["nodecert"] = nodecert + cert_dict["certificate_information"]["cacert"] = cacert + cert_dict["certificate_information"]["rootcert_expiration"] = rootcertexpiry + cert_dict["certificate_information"]["nodecert_expiration"] = nodecertexpiry + cert_dict["certificate_information"]["cacert_expiration"] = cacertexpiry + rootdelta = rootcertexpiry - currdate + nodedelta = nodecertexpiry - currdate + cadelta = cacertexpiry - currdate + # module.exit_json(msg=rootdelta.days) + cert_dict["certificate_information"]["rootcert_expiry_days"] = rootdelta.days + cert_dict["certificate_information"]["nodecert_expiry_days"] = nodedelta.days + cert_dict["certificate_information"]["cacert_expiry_days"] = cadelta.days + facts.update(cert_dict) + + module.exit_json(changed=False, ansible_facts=facts) + + +from ansible.module_utils.basic import * + +if __name__ == "__main__": + main() diff --git a/plugins/modules/cockroach_privs.py b/plugins/modules/cockroach_privs.py new file mode 100644 index 0000000..70c4e18 --- /dev/null +++ b/plugins/modules/cockroach_privs.py @@ -0,0 +1,125 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = """ +--- +module: cockroach_privs +short_description: Manage user privileges in a cockroach db +description: + - Manage user privileges in a cockroach db +version_added: 0.4.0 +options: + name: + description: + - the name of the user to set permissions + required: true + default: None + db: + description: + - Name of the database to set permissions in + required: true + default: None + privs: + description: + - Privileges for the user + required: true + default: None +author: + - Oscar C + - Mikael Sandstrom @oravirt +""" + +EXAMPLES = """ +# Grant ALL privileges to user1 on db1 +cockroach_privs: name=user1 db=db1 privs=ALL path=/var/lib/cockroach +""" +from ansible.module_utils.basic import AnsibleModule +import os + +# Check if the service exists +def check_user_privs(module, msg, path, host, port, name, certs_dir, db, privs): + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += ' --execute "SHOW GRANTS ON DATABASE %s FOR %s;"' % (db, name) + + # module.fail_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + # module.exit_json(msg=rc) + if rc != 0: + msg[0] = "Something went wrong, stderr: %s" % (stderr) + + if privs.lower() in stdout.lower(): + return True + else: + return False + + +def set_user_privs(module, msg, path, host, port, name, certs_dir, db, privs): + command = "%s/cockroach sql --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + command += ' --execute "GRANT %s ON DATABASE %s TO %s;"' % (privs, db, name) + + # module.fail_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Setting user privileges for db %s failed. %s" % (db, stderr) + return False + else: + return True + + +def main(): + + msg = [""] + + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=["username", "user"]), + host=dict(required=False, default="localhost"), + port=dict(required=False, default=26257), + path=dict(required=False), + certs_dir=dict(required=False), + db=dict(required=True), + privs=dict(required=False, default="ALL"), + ), + ) + + name = module.params["name"] + host = module.params["host"] + port = module.params["port"] + path = module.params["path"] + certs_dir = module.params["certs_dir"] + db = module.params["db"] + privs = module.params["privs"] + + if not path: + try: + command = "cockroach version" + module.run_command(command) + except OSError as e: + msg[0] = "Couldnt find cockroach executable. Check the path. stderr: %s" % ( + e + ) + module.fail_json(msg=msg[0], failed=True) + + if not check_user_privs(module, msg, path, host, port, name, certs_dir, db, privs): + if set_user_privs(module, msg, path, host, port, name, certs_dir, db, privs): + msg[0] = "Successfully set permissions for user %s on db %s " % (name, db) + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "User %s permissions already set on %s " % (name, db) + module.exit_json(msg=msg[0], changed=False) + + module.exit_json(msg="Unhandled exit", changed=False) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/cockroach_user.py b/plugins/modules/cockroach_user.py new file mode 100644 index 0000000..a868093 --- /dev/null +++ b/plugins/modules/cockroach_user.py @@ -0,0 +1,152 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +DOCUMENTATION = """ +--- +module: cockroach_user +short_description: Manage users in a cockroach cluster +description: + - Manage users in a cockroach cluster +version_added: 0.4.0 +options: + name: + description: + - The name of the user + required: true + default: None + +notes: +author: + - Oscar C + - Mikael Sandström, oravirt@gmail.com, @oravirt +""" + +EXAMPLES = """ +# Create a user +cockroach_user: name=user1 path=/var/lib/cockroach host={{ inventory_hostname }} state=present + +# Delete a user +cockroach_user: name=user1 path=/var/lib/cockroach host={{ inventory_hostname }} state=absent + +""" + +from ansible.module_utils.basic import * +import os + +# Check if the service exists +def check_user_exists(module, msg, path, host, port, name, certs_dir): + command = "%s/cockroach user ls --host=%s --port=%s " % (path, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + + # module.fail_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + # module.exit_json(msg=rc) + if rc != 0: + msg[0] = "Something went wrong, stderr: %s" % (stderr) + + if name.lower() in stdout.lower(): + return True + else: + return False + + +def create_user(module, msg, path, host, port, name, certs_dir): + command = "%s/cockroach user set %s --host=%s --port=%s " % ( + path, + name, + host, + port, + ) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + + # module.fail_json(msg=command) + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Creating user %s failed. %s. Command: %s" % (name, stderr, command) + return False + else: + return True + + +def remove_user(module, msg, path, host, port, name, certs_dir): + + command = "%s/cockroach user rm %s --host=%s --port=%s " % (path, name, host, port) + if not certs_dir: + command += " --insecure" + elif certs_dir: + command += " --certs-dir=%s" % (certs_dir) + # command += ' user rm %s' % (name) + + (rc, stdout, stderr) = module.run_command(command) + if rc != 0: + msg[0] = "Removing user %s failed: %s" % (name, stderr) + return False + else: + return True + + +def main(): + + msg = [""] + + module = AnsibleModule( + argument_spec=dict( + name=dict(required=True, aliases=["user_name", "user"]), + host=dict(required=False, default="localhost"), + port=dict(required=False, default=26257), + state=dict(default="present", choices=["present", "absent"]), + path=dict(required=False), + certs_dir=dict(required=False), + ), + ) + + name = module.params["name"] + host = module.params["host"] + port = module.params["port"] + state = module.params["state"] + path = module.params["path"] + certs_dir = module.params["certs_dir"] + + if not path: + try: + command = "cockroach version" + module.run_command(command) + except OSError as e: + msg[0] = "Couldnt find cockroach executable. Check the path. stderr: %s" % ( + e + ) + module.fail_json(msg=msg[0], failed=True) + + if state == "present": + if not check_user_exists(module, msg, path, host, port, name, certs_dir): + if create_user(module, msg, path, host, port, name, certs_dir): + msg[0] = "Successfully created user %s " % (name) + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "User %s already exists" % (name) + module.exit_json(msg=msg[0], changed=False) + + elif state == "absent": + if check_user_exists(module, msg, path, host, port, name, certs_dir): + if remove_user(module, msg, path, host, port, name, certs_dir): + msg[0] = "Successfully deleted user %s " % (name) + module.exit_json(msg=msg[0], changed=True) + else: + module.fail_json(msg=msg[0], changed=False) + else: + msg[0] = "User %s doesn't exist" % (name) + module.exit_json(msg=msg[0], changed=False) + + module.exit_json(msg="Unhandled exit", changed=False) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/doh.py b/plugins/modules/doh.py index 25dfe7c..94eddc5 100644 --- a/plugins/modules/doh.py +++ b/plugins/modules/doh.py @@ -12,10 +12,11 @@ module: doh short_description: DNS Lookup over HTTPS. description: - - DNS Lookup over HTTPS from various Public DOH Servers like Google/Cloudflare/Quad9. + - DNS Lookup over HTTPS from various Public DOH Servers like Google/Cloudflare/Quad9/Alibaba. - U(https://developers.cloudflare.com/1.1.1.1/encrypted-dns/dns-over-https/make-api-requests/dns-json) - U(https://developers.google.com/speed/public-dns/docs/doh/json) - U(https://www.quad9.net/news/blog/doh-with-quad9-dns-servers/) + - U(https://www.alibabacloud.com/help/en/doc-detail/171666.html) version_added: 0.4.0 options: source: @@ -23,7 +24,7 @@ - DNS over HTTPS can be queried from Google/Cloudflare/Quad9. required: false type: str - choices: ["google", "cloudflare", "quad9"] + choices: ["google", "cloudflare", "quad9", "alibaba"] default: "cloudflare" domain_name: description: @@ -72,6 +73,12 @@ source: "quad9" name: "example.com" type: "MX" + +- name: fetch A record from Alibaba DNS over HTTPS + community.missing_collection.doh: + source: "alibaba" + name: "example.com" + type: "A" """ RETURN = """ @@ -109,7 +116,7 @@ def main(): argument_spec = dict( - source=dict(choices=["google", "cloudflare", "quad9"], default="cloudflare"), + source=dict(choices=["google", "cloudflare", "quad9", "alibaba"], default="cloudflare"), domain_name=dict(required=True, aliases=["name"]), type=dict(default="A"), do=dict(type=bool, default=True), @@ -132,9 +139,10 @@ def main(): "cloudflare": "https://cloudflare-dns.com/dns-query", "google": "https://dns.google/resolve", "quad9": "https://dns.quad9.net:5053/dns-query", + "alibaba": "https://dns.alidns.com/resolve", } - if module.params["source"] in dns_urls.keys(): + if module.params["source"] in dns_urls: r = requests.get( url=dns_urls[module.params["source"]], params=params, headers=headers ) diff --git a/tests/integration/doh.yaml b/tests/integration/doh.yaml index 699334b..0e09606 100644 --- a/tests/integration/doh.yaml +++ b/tests/integration/doh.yaml @@ -20,3 +20,9 @@ source: "quad9" name: "example.com" type: "MX" + + - name: fetch A record from Alibaba DNS over HTTPS + doh: + source: "alibaba" + name: "example.com" + type: "A"