diff --git a/.gitignore b/.gitignore index 037877c..076cc4c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -__pycache__ +__pycache__/ *.bak* *.bak*/ diff --git a/plugins/module_utils/pvesh.py b/plugins/module_utils/pvesh.py new file mode 100644 index 0000000..d4d5656 --- /dev/null +++ b/plugins/module_utils/pvesh.py @@ -0,0 +1,105 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python + +import subprocess +import json +import re + +from ansible.module_utils.common.text.converters import to_text + +class ProxmoxShellError(Exception): + """Exception raised when an unexpected response code is thrown from pvesh.""" + def __init__(self, response): + self.status_code = response["status"] + self.message = response["message"] + + if "data" in response: + self.data = response["data"] + +def run_command(handler, resource, **params): + # pvesh strips these before handling, so might as well + resource = resource.strip('/') + # pvesh only has lowercase handlers + handler = handler.lower() + command = [ + "/usr/bin/pvesh", + handler, + resource, + "--output=json"] + for parameter, value in params.items(): + command += ["-{}".format(parameter), "{}".format(value)] + + pipe = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + (result, stderr) = pipe.communicate() + result = to_text(result) + stderr = to_text(stderr).splitlines() + + if len(stderr) == 0: + if not result: + return {u"status": 200} + + # Attempt to marshall the data into JSON + try: + data = json.loads(result) + except ValueError: + return {u"status": 200, u"data": result} + + # Otherwise return data as a string + return {u"status": 200, u"data": data} + + if len(stderr) >= 1: + # This will occur when a param's value is invalid + if stderr[0] == "400 Parameter verification failed.": + return {u"status": 400, u"message": "\n".join(stderr[1:-1])} + + if stderr[0] == "no '{}' handler for '{}'".format(handler, resource): + return {u"status": 405, u"message": stderr[0]} + + if handler == "get": + if any(re.match(pattern, stderr[0]) for pattern in [ + "^no such user \\('.{3,64}?'\\)$", + "^(group|role|pool) '[A-Za-z0-9\\.\\-_]+' does not exist$", + "^domain '[A-Za-z][A-Za-z0-9\\.\\-_]+' does not exist$"]): + return {u"status": 404, u"message": stderr[0]} + + # This will occur when a param is invalid + if len(stderr) >=2 and stderr[-2].startswith("400 unable to parse"): + return {u"status": 400, u"message": "\n".join(stderr[:-1])} + + return {u"status": 500, u"message": u"\n".join(stderr), u"data": result} + + return {u"status": 500, u"message": u"Unexpected result occurred but no error message was provided by pvesh."} + +def get(resource): + response = run_command("get", resource) + + if response["status"] == 404: + return None + + if response["status"] == 200: + return response["data"] + + raise ProxmoxShellError(response) + +def delete(resource): + response = run_command("delete", resource) + + if response["status"] != 200: + raise ProxmoxShellError(response) + +def create(resource, **params): + response = run_command("create", resource, **params) + + if response["status"] != 200: + raise ProxmoxShellError(response) + +def set(resource, **params): + response = run_command("set", resource, **params) + + if response["status"] != 200: + raise ProxmoxShellError(response) diff --git a/plugins/modules/collect_kernel_info.py b/plugins/modules/collect_kernel_info.py new file mode 100755 index 0000000..3350294 --- /dev/null +++ b/plugins/modules/collect_kernel_info.py @@ -0,0 +1,76 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python +import glob +import subprocess + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text + + +def main(): + module = AnsibleModule( + argument_spec = dict( + lookup_packages = dict(required=False, default=True, type='bool') + ), + supports_check_mode=True + ) + + params = module.params + + # Collect a list of installed kernels + kernels = glob.glob("/lib/modules/*") + + # Identify path to the latest kernel + latest_kernel = "" + for kernel in kernels: + if not latest_kernel: + latest_kernel = kernel + continue + # These splits remove the path and get the base directory name, which + # should be something like 5.4.78-1-pve, that we can compare + right = latest_kernel.split("/")[-1] + left = kernel.split("/")[-1] + cmp_str = "gt" + if subprocess.call(["dpkg", "--compare-versions", left, cmp_str, right]) == 0: + latest_kernel = kernel + + booted_kernel = "/lib/modules/{}".format(to_text( + subprocess.run(["uname", "-r"], capture_output=True).stdout).strip()) + + booted_kernel_packages = "" + old_kernel_packages = [] + if params['lookup_packages']: + for kernel in kernels: + # Identify the currently booted kernel and unused old kernels by + # querying which packages own directories in /lib/modules + try: + sp = subprocess.run(["dpkg-query", "-S", kernel], + check=True, capture_output=True) + except subprocess.CalledProcessError as e: + # Ignore errors about directories not associated with a package + if e.stderr.startswith(b"dpkg-query: no path found matching"): + continue + raise e + pkgs = to_text(sp.stdout).split(":")[0].split(", ") + if kernel.split("/")[-1] == booted_kernel.split("/")[-1]: + booted_kernel_packages = pkgs + elif kernel != latest_kernel: + old_kernel_packages.extend(pkgs) + + # returns True if we're not booted into the latest kernel + new_kernel_exists = booted_kernel.split("/")[-1] != latest_kernel.split("/")[-1] + module.exit_json( + changed=False, + new_kernel_exists=new_kernel_exists, + old_packages=old_kernel_packages, + booted_packages=booted_kernel_packages + ) + + +if __name__ == '__main__': + main() diff --git a/plugins/modules/proxmox_acl.py b/plugins/modules/proxmox_acl.py new file mode 100755 index 0000000..1fa7d7c --- /dev/null +++ b/plugins/modules/proxmox_acl.py @@ -0,0 +1,190 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python +# -*- coding: utf-8 -*- + +ANSIBLE_METADATA = { + 'metadata_version': '0.1', + 'status': ['preview'], + 'supported_by': 'lae' +} + +DOCUMENTATION = ''' +--- +module: proxmox_acl + +short_description: Manages the Access Control List in Proxmox + +options: + path: + required: true + aliases: [ "resource" ] + description: + - Location of the resource to apply access control to. + roles: + required: true + type: list + description: + - Specifies a list of PVE roles, which contains sets of privileges, + to allow for this access control. + state: + required: false + default: "present" + choices: [ "present", "absent" ] + description: + - Specifies whether this access control should exist or not. + groups: + required: false + type: list + description: + - Specifies a list of PVE groups to apply this access control for. + users: + required: false + type: list + description: + - Specifies a list of PVE users to apply this access control for. + +author: + - Musee Ullah (@lae) +''' + +EXAMPLES = ''' +- name: Allow Admins group Administrator access to / + proxmox_acl: + path: / + roles: [ "Administrator" ] + groups: [ "Admins" ] +- name: Allow pveapi@pve and test_users group PVEAdmin access to /pools/testpool + proxmox_acl: + path: /pools/testpool + roles: [ "PVEAdmin" ] + users: + - pveapi@pve + groups: + - test_users +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.pvesh import ProxmoxShellError +import ansible.module_utils.pvesh as pvesh + +class ProxmoxACL(object): + def __init__(self, module): + self.module = module + self.path = module.params['path'] + self.state = module.params['state'] + self.roles = module.params['roles'] + self.groups = module.params['groups'] + self.users = module.params['users'] + + try: + self.existing_acl = pvesh.get("access/acl") + except ProxmoxShellError as e: + self.module.fail_json(msg=e.message, status_code=e.status_code) + + # PVE 5.x (unnecessarily) uses a string for this value. This ensures + # that it's an integer for when we compare values later. + for acl in self.existing_acl: + acl['propagate'] = int(acl['propagate']) + + self.parse_acls() + + def parse_acls(self): + constituents = [] + + if self.users is not None: + [constituents.append(["user", user]) for user in self.users] + + if self.groups is not None: + [constituents.append(["group", group]) for group in self.groups] + + self.acls = [] + for role in self.roles: + for constituent in constituents: + self.acls.append({ + "path": self.path, + "propagate": 1, # possibly make this configurable in the module later + "roleid": role, + "type": constituent[0], + "ugid": constituent[1] + }) + + def exists(self): + for acl in self.acls: + if acl not in self.existing_acl: + return False + + return True + + def prepare_acl_args(self): + args = {} + args['path'] = self.path + args['roles'] = ','.join(self.roles) + + if self.groups is not None: + args['groups'] = ','.join(self.groups) + + if self.users is not None: + args['users'] = ','.join(self.users) + + return args + + def set_acl(self, delete=0): + acls = self.prepare_acl_args() + + try: + pvesh.set("access/acl", delete=delete, **acls) + return None + except ProxmoxShellError as e: + return e.message + +def main(): + # Refer to https://pve.proxmox.com/pve-docs/api-viewer/index.html + module = AnsibleModule( + argument_spec = dict( + path=dict(type='str', required=True, aliases=['resource']), + roles=dict(type='list', required=True), + state=dict(default='present', choices=['present', 'absent'], type='str'), + groups=dict(default=None, type='list'), + users=dict(default=None, type='list'), + ), + required_one_of=[["groups", "users"]], + supports_check_mode=True + ) + + acl = ProxmoxACL(module) + + error = None + result = {} + result['state'] = acl.state + result['changed'] = False + + if acl.state == 'absent': + if acl.exists(): + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + error = acl.set_acl(delete=1) + elif acl.state == 'present': + if not acl.exists(): + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + error = acl.set_acl() + + if error is not None: + module.fail_json(path=acl.path, msg=error) + + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/proxmox_group.py b/plugins/modules/proxmox_group.py new file mode 100755 index 0000000..a0d3bfc --- /dev/null +++ b/plugins/modules/proxmox_group.py @@ -0,0 +1,181 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python +# -*- coding: utf-8 -*- + +ANSIBLE_METADATA = { + 'metadata_version': '1.0', + 'status': ['stableinterface'], + 'supported_by': 'lae' +} + +DOCUMENTATION = ''' +--- +module: proxmox_group + +short_description: Manages groups in Proxmox + +options: + name: + required: true + aliases: [ "group", "groupid" ] + description: + - Name of the PVE group to manage. + state: + required: false + default: "present" + choices: [ "present", "absent" ] + description: + - Specifies whether the group should exist or not. + comment: + required: false + description: + - Optionally sets the group's comment in PVE. + +author: + - Musee Ullah (@lae) +''' + +EXAMPLES = ''' +- name: Create Administrators group + proxmox_group: + name: Administrators +- name: Create API Users group + proxmox_group: + name: api_users + comment: Users allowed to access the API. +''' + +RETURN = ''' +updated_fields: + description: Fields that were modified for an existing group + type: list +group: + description: Information about the group fetched from PVE after this task completed. + type: json +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.pvesh import ProxmoxShellError +import ansible.module_utils.pvesh as pvesh + +class ProxmoxGroup(object): + def __init__(self, module): + self.module = module + self.name = module.params['name'] + self.state = module.params['state'] + self.comment = module.params['comment'] + + def lookup(self): + try: + return pvesh.get("access/groups/{}".format(self.name)) + except ProxmoxShellError as e: + self.module.fail_json(msg=e.message, status_code=e.status_code, **result) + + def remove_group(self): + try: + pvesh.delete("access/groups/{}".format(self.name)) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def create_group(self): + new_group = {} + if self.comment is not None: + new_group['comment'] = self.comment + + try: + pvesh.create("access/groups", groupid=self.name, **new_group) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def modify_group(self): + lookup = self.lookup() + staged_group = {} + + if self.comment is not None: + staged_group['comment'] = self.comment + + updated_fields = [] + error = None + + for key in staged_group: + staged_value = to_text(staged_group[key]) if isinstance(staged_group[key], str) else staged_group[key] + if key not in lookup or staged_value != lookup[key]: + updated_fields.append(key) + + if self.module.check_mode: + self.module.exit_json(changed=bool(updated_fields), expected_changes=updated_fields) + + if not updated_fields: + # No changes necessary + return (updated_fields, error) + + try: + pvesh.set("access/groups/{}".format(self.name), **staged_group) + except ProxmoxShellError as e: + error = e.message + + return (updated_fields, error) + +def main(): + # Refer to https://pve.proxmox.com/pve-docs/api-viewer/index.html + module = AnsibleModule( + argument_spec = dict( + name=dict(type='str', required=True, aliases=['group', 'groupid']), + state=dict(default='present', choices=['present', 'absent'], type='str'), + comment=dict(default=None, type='str'), + ), + supports_check_mode=True + ) + + group = ProxmoxGroup(module) + + changed = False + error = None + result = {} + result['name'] = group.name + result['state'] = group.state + + if group.state == 'absent': + if group.lookup() is not None: + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = group.remove_group() + + if error is not None: + module.fail_json(name=group.name, msg=error) + elif group.state == 'present': + if not group.lookup(): + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = group.create_group() + else: + # modify group (note: this function is check mode aware) + (updated_fields, error) = group.modify_group() + + if updated_fields: + changed = True + result['updated_fields'] = updated_fields + + if error is not None: + module.fail_json(name=group.name, msg=error) + + lookup = group.lookup() + if lookup is not None: + result['group'] = lookup + + result['changed'] = changed + + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/proxmox_pool.py b/plugins/modules/proxmox_pool.py new file mode 100644 index 0000000..8e9a5b4 --- /dev/null +++ b/plugins/modules/proxmox_pool.py @@ -0,0 +1,181 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python +# -*- coding: utf-8 -*- + +ANSIBLE_METADATA = { + 'metadata_version': '1.0', + 'status': ['stableinterface'], + 'supported_by': 'futuriste' +} + +DOCUMENTATION = ''' +--- +module: proxmox_pool + +short_description: Manages pools in Proxmox + +options: + name: + required: true + aliases: [ "pool", "poolid" ] + description: + - Name of the PVE pool to manage. + state: + required: false + default: "present" + choices: [ "present", "absent" ] + description: + - Specifies whether the pool should exist or not. + comment: + required: false + description: + - Optionally sets the pool's comment in PVE. + +author: + - Guiffo Joel (@futuriste) +''' + +EXAMPLES = ''' +- name: Create Administrators pool + proxmox_pool: + name: Administrators +- name: Create Dev Users pool's + proxmox_pool: + name: pool_dev + comment: Dev Users allowed to access on this pool. +''' + +RETURN = ''' +updated_fields: + description: Fields that were modified for an existing pool + type: list +pool: + description: Information about the pool fetched from PVE after this task completed. + type: json +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.pvesh import ProxmoxShellError +import ansible.module_utils.pvesh as pvesh + +class ProxmoxPool(object): + def __init__(self, module): + self.module = module + self.name = module.params['name'] + self.state = module.params['state'] + self.comment = module.params['comment'] + + def lookup(self): + try: + return pvesh.get("pools/{}".format(self.name)) + except ProxmoxShellError as e: + self.module.fail_json(msg=e.message, status_code=e.status_code, **result) + + def remove_pool(self): + try: + pvesh.delete("pools/{}".format(self.name)) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def create_pool(self): + new_pool = {} + if self.comment is not None: + new_pool['comment'] = self.comment + + try: + pvesh.create("pools", poolid=self.name, **new_pool) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def modify_pool(self): + lookup = self.lookup() + staged_pool = {} + + if self.comment is not None: + staged_pool['comment'] = self.comment + + updated_fields = [] + error = None + + for key in staged_pool: + staged_value = to_text(staged_pool[key]) if isinstance(staged_pool[key], str) else staged_pool[key] + if key not in lookup or staged_value != lookup[key]: + updated_fields.append(key) + + if self.module.check_mode: + self.module.exit_json(changed=bool(updated_fields), expected_changes=updated_fields) + + if not updated_fields: + # No changes necessary + return (updated_fields, error) + + try: + pvesh.set("pools/{}".format(self.name), **staged_pool) + except ProxmoxShellError as e: + error = e.message + + return (updated_fields, error) + +def main(): + # Refer to https://pve.proxmox.com/pve-docs/api-viewer/index.html + module = AnsibleModule( + argument_spec = dict( + name=dict(type='str', required=True, aliases=['pool', 'poolid']), + state=dict(default='present', choices=['present', 'absent'], type='str'), + comment=dict(default=None, type='str'), + ), + supports_check_mode=True + ) + + pool = ProxmoxPool(module) + + changed = False + error = None + result = {} + result['name'] = pool.name + result['state'] = pool.state + + if pool.state == 'absent': + if pool.lookup() is not None: + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = pool.remove_pool() + + if error is not None: + module.fail_json(name=pool.name, msg=error) + elif pool.state == 'present': + if not pool.lookup(): + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = pool.create_pool() + else: + # modify pool (note: this function is check mode aware) + (updated_fields, error) = pool.modify_pool() + + if updated_fields: + changed = True + result['updated_fields'] = updated_fields + + if error is not None: + module.fail_json(name=pool.name, msg=error) + + lookup = pool.lookup() + if lookup is not None: + result['pool'] = lookup + + result['changed'] = changed + + module.exit_json(**result) + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/plugins/modules/proxmox_query.py b/plugins/modules/proxmox_query.py new file mode 100755 index 0000000..a547980 --- /dev/null +++ b/plugins/modules/proxmox_query.py @@ -0,0 +1,76 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python + +ANSIBLE_METADATA = { + 'metadata_version': '1.0', + 'status': ['stableinterface'], + 'supported_by': 'lae' +} + +DOCUMENTATION = ''' +--- +module: proxmox_query + +short_description: Uses pvesh to query Proxmox API + +options: + query: + required: true + aliases: [ "name" ] + description: + - Specifies what resource to query + +author: + - Musee Ullah (@lae) +''' + +EXAMPLES = ''' +- name: Query cluster status + proxmox_query: + query: cluster/status +- name: Collect a list of running LXC containers for some hosts + proxmox_query: + query: "nodes/{{ item }}/lxc" + with_items: + - node01 + - node02 + - node03 +''' + +RETURN = ''' +response: + description: JSON response from pvesh provided by a query + type: json +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.aybarsm.linux.plugins.module_utils.pvesh import ProxmoxShellError +import ansible_collections.aybarsm.linux.plugins.module_utils.pvesh as pvesh + +def main(): + module = AnsibleModule( + argument_spec = dict( + query=dict(type='str', required=True, aliases=['name']), + ), + supports_check_mode=True + ) + + result = {"changed": False} + + try: + result['response'] = pvesh.get(module.params['query']) + except ProxmoxShellError as e: + if e.data: + result["response"] = e.data + + module.fail_json(msg=e.message, status_code=e.status_code, **result) + + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/proxmox_role.py b/plugins/modules/proxmox_role.py new file mode 100644 index 0000000..f2b3ed9 --- /dev/null +++ b/plugins/modules/proxmox_role.py @@ -0,0 +1,195 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python +# -*- coding: utf-8 -*- + +ANSIBLE_METADATA = { + 'metadata_version': '0.1', + 'status': ['preview'], + 'supported_by': 'lae' +} + +DOCUMENTATION = ''' +--- +module: proxmox_role +short_description: Manages the Access Control List in Proxmox +options: + name: + required: true + description: + - name of the role. + privileges: + required: true + type: list + description: + - Specifies a list of PVE privileges for the given role. + state: + required: false + default: "present" + choices: [ "present", "absent" ] + description: + - Specifies whether this role should exist or not. +author: + - Thoralf Rickert-Wendt (@trickert76) +''' + +EXAMPLES = ''' +- name: Create a role for monitoring with given privileges + proxmox_role: + name: "monitoring" + privileges: [ "Sys.Modify", "Sys.Audit", "Datastore.Audit", "VM.Monitor", "VM.Audit" ] +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.pvesh import ProxmoxShellError +import ansible.module_utils.pvesh as pvesh + +class ProxmoxRole(object): + def __init__(self, module): + self.module = module + self.name = module.params['name'] + self.privileges = module.params['privileges'] + self.state = module.params['state'] + + try: + self.existing_roles = pvesh.get("access/roles") + except ProxmoxShellError as e: + self.module.fail_json(msg=e.message, status_code=e.status_code) + + self.parse_roles() + + def parse_roles(self): + self.roles = [] + for existing_role in self.existing_roles: + self.roles.append(existing_role.get('roleid')) + + def lookup(self): + self.roles = [] + for existing_role in self.existing_roles: + if existing_role.get('roleid') == self.name: + args = {} + args['roleid'] = existing_role.get('roleid') + args['privs'] = ','.join(sorted(existing_role.get('privs').split(','))) + return args + + return None + + def exists(self): + if self.name not in self.roles: + return False + + return True + + def prepare_role_args(self, appendKey=True): + args = {} + if appendKey: + args['roleid'] = self.name + args['privs'] = ','.join(sorted(self.privileges)) + + return args + + def remove_role(self): + try: + pvesh.delete("access/roles/{}".format(self.name)) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def create_role(self): + new_role = self.prepare_role_args() + + try: + pvesh.create("access/roles", **new_role) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def modify_role(self): + existing_role = self.lookup() + modified_role = self.prepare_role_args(appendKey=False) + updated_fields = [] + error = None + + for key in modified_role: + if key not in existing_role: + updated_fields.append(key) + else: + new_value = modified_role.get(key) + old_value = existing_role.get(key) + if isinstance(old_value, list): + old_value = ','.join(sorted(old_value)) + if isinstance(new_value, list): + new_value = ','.join(sorted(new_value)) + + if new_value != old_value: + updated_fields.append(key) + + if self.module.check_mode: + self.module.exit_json(changed=bool(updated_fields), expected_changes=updated_fields) + + if not updated_fields: + # No changes necessary + return (updated_fields, error) + + try: + pvesh.set("access/roles/{}".format(self.name), **modified_role) + except ProxmoxShellError as e: + error = e.message + + return (updated_fields, error) + +def main(): + # Refer to https://pve.proxmox.com/pve-docs/api-viewer/index.html + module = AnsibleModule( + argument_spec = dict( + name=dict(type='str', required=True), + privileges=dict(type='list', required=True), + state=dict(default='present', choices=['present', 'absent'], type='str') + ), + supports_check_mode=True + ) + + role = ProxmoxRole(module) + + changed = False + error = None + result = {} + result['name'] = role.name + result['state'] = role.state + result['changed'] = False + + if role.state == 'absent': + if role.exists(): + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = role.remove_role() + elif role.state == 'present': + if not role.exists(): + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = role.create_role() + else: + (updated_fields, error) = role.modify_role() + + if updated_fields: + changed = True + result['updated_fields'] = updated_fields + + if error is not None: + module.fail_json(name=role.name, msg=error) + + result['changed'] = changed + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/proxmox_storage.py b/plugins/modules/proxmox_storage.py new file mode 100755 index 0000000..18a3047 --- /dev/null +++ b/plugins/modules/proxmox_storage.py @@ -0,0 +1,470 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python +# -*- coding: utf-8 -*- + +ANSIBLE_METADATA = { + 'metadata_version': '0.1', + 'status': ['preview'], + 'supported_by': 'fbrachere' +} + +DOCUMENTATION = ''' +--- +module: proxmox_storage + +short_description: Manages the storage in Proxmox + +options: + name: + required: true + aliases: [ "storage", "storageid" ] + description: + - Name of the storage. + type: + required: true + aliases: [ "storagetype" ] + choices: [ "dir", "nfs", "rbd", "lvm", "lvmthin", "cephfs", "zfspool", "btrfs" ] + description: + - Type of storage, must be supported by Proxmox. + disable: + required: false + description: Disable the storage. + state: + required: false + default: "present" + choices: [ "present", "absent" ] + description: + - Specifies whether this storage should exist or not. + content: + required: true + aliases: [ "storagecontent" ] + type: list + choices: [ "images", "rootdir", "vztmpl", "backup", "iso", "snippets" ] + description: + - Contents supported by the storage, not all storage + types support all content types. + nodes: + required: false + type: list + description: + - List of cluster node names where this storage is usable. + path: + required: false + description: + - File system path. + pool: + required: false + description: + - Ceph/ZFS pool name. + monhost: + required: false + type: list + description: + - Monitor addresses of the ceph cluster. + username: + required: false + description: + - User name (RBD) who access to ceph cluster. + krbd: + required: false + default: 0 + description: + - Always access rbd through krbd kernel module. + maxfiles: + required: false + default: 0 + description: + - Maximal number of backup files per VM. 0 for unlimited. + export: + required: false + description: + - NFS export path + server: + required: false + description: + - Server IP or DNS name. + options: + required: false + description: + - NFS mount options. + vgname: + required: false + description: + - LVM volume group name. This must point to an existing volume group. + thinpool: + required: false + description: + - The name of the LVM thin pool. + sparse: + required: false + description: + - Use ZFS thin-provisioning. + is_mountpoint: + required: false + description: + - Specifies whether or not the given path is an externally managed + mountpoint. + +author: + - Fabien Brachere (@fbrachere) +''' + +EXAMPLES = ''' +- name: Create a directory storage type + proxmox_storage: + name: dir1 + type: dir + path: /mydir + content: [ "images", "iso", "backup" ] + maxfiles: 3 +- name: Create an RBD storage type + proxmox_storage: + name: ceph1 + type: rbd + content: [ "images", "rootdir" ] + nodes: [ "proxmox1", "proxmox2" ] + username: admin + pool: rbd + krbd: yes + monhost: + - 10.0.0.1 + - 10.0.0.2 + - 10.0.0.3 +- name: Create an NFS storage type + proxmox_storage: + name: nfs1 + type: nfs + content: [ "images", "iso" ] + server: 192.168.122.2 + export: /data +- name: Create an LVM storage type + proxmox_storage: + name: lvm1 + type: lvm + content: [ "images", "rootdir" ] + vgname: vg1 +- name: Create an LVM-thin storage type + proxmox_storage: + name: lvmthin1 + type: lvmthin + content: [ "images", "rootdir" ] + vgname: vg2 + thinpool: data +- name: Create an CephFS storage type + proxmox_storage: + name: cephfs1 + type: cephfs + content: [ "snippets", "vztmpl", "iso" ] + nodes: [ "proxmox1", "proxmox2"] + monhost: + - 10.0.0.1 + - 10.0.0.2 + - 10.0.0.3 +- name: Create a Proxmox Backup Server storage type + proxmox_storage: + name: pbs1 + type: pbs + content: [ "backup" ] + server: 192.168.122.2 + username: user@pbs + password: PBSPassword1 + datastore: main + fingerprint: f2:fb:85:76:d2:2a:c4:96:5c:6e:d8:71:37:36:06:17:09:55:f7:04:e3:74:bb:aa:9e:26:85:92:63:c8:b9:23 + encryption_key: autogen +- name: Create a ZFS storage type + proxmox_storage: + name: zfs1 + type: zfspool + content: [ "images", "rootdir" ] + pool: rpool/data + sparse: true +''' + +RETURN = ''' +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.pvesh import ProxmoxShellError +import ansible.module_utils.pvesh as pvesh +import re +import json +from json import JSONDecodeError, loads as parse_json + + +class ProxmoxStorage(object): + def __init__(self, module): + self.module = module + self.name = module.params['name'] + self.state = module.params['state'] + # Globally applicable PVE API arguments + self.disable = module.params['disable'] + self.content = module.params['content'] + self.nodes = module.params['nodes'] + self.type = module.params['type'] + # Remaining PVE API arguments (depending on type) past this point + self.datastore = module.params['datastore'] + self.encryption_key = module.params['encryption_key'] + self.master_pubkey = module.params['master_pubkey'] + self.fingerprint = module.params['fingerprint'] + self.password = module.params['password'] + self.path = module.params['path'] + self.pool = module.params['pool'] + self.monhost = module.params['monhost'] + self.username = module.params['username'] + self.krbd = module.params['krbd'] + self.maxfiles = module.params['maxfiles'] + self.server = module.params['server'] + self.export = module.params['export'] + self.options = module.params['options'] + self.vgname = module.params['vgname'] + self.thinpool = module.params['thinpool'] + self.sparse = module.params['sparse'] + self.is_mountpoint = module.params['is_mountpoint'] + + # Validate the parameters given to us + fingerprint_re = re.compile('^([A-Fa-f0-9]{2}:){31}[A-Fa-f0-9]{2}$') + if self.fingerprint is not None and not fingerprint_re.match(self.fingerprint): + self.module.fail_json(msg=(f"fingerprint must be of the format, " + f"{fingerprint_re.pattern}.")) + + if self.type == 'pbs': + if self.content != ['backup']: + self.module.fail_json(msg="PBS storage type only supports the " + "'backup' content type.") + try: + if self.encryption_key not in ["autogen", None]: + parse_json(self.encryption_key) + except JSONDecodeError: + self.module.fail_json(msg=("encryption_key needs to be valid " + "JSON or set to 'autogen'.")) + + # Attempt to retrieve current/live storage definitions + try: + self.existing_storages = pvesh.get("storage") + except ProxmoxShellError as e: + self.module.fail_json(msg=e.message, status_code=e.status_code) + + def lookup(self): + for item in self.existing_storages: + if item['storage'] == self.name: + # pvesh doesn't return the disable param value if it's false, + # so we set it to 0, which is what PVE would normally use. + if item.get('disable') is None: + item['disable'] = 0 + return item + return None + + def exists(self): + for item in self.existing_storages: + if item["storage"] == self.name: + return True + return False + + def prepare_storage_args(self): + args = {} + + args['type'] = self.type + if self.content is not None and len(self.content) > 0: + args['content'] = ','.join(self.content) + else: + # PVE uses "none" to represent when no content types are selected + args['content'] = 'none' + if self.nodes is not None: + args['nodes'] = ','.join(self.nodes) + if self.disable is not None: + args['disable'] = 1 if self.disable else 0 + if self.datastore is not None: + args['datastore'] = self.datastore + if self.encryption_key is not None: + args['encryption-key'] = self.encryption_key + if self.fingerprint is not None: + args['fingerprint'] = self.fingerprint + if self.master_pubkey is not None: + args['master-pubkey'] = self.master_pubkey + if self.password is not None: + args['password'] = self.password + if self.path is not None: + args['path'] = self.path + if self.pool is not None: + args['pool'] = self.pool + if self.monhost is not None: + args['monhost'] = ','.join(self.monhost) + if self.username is not None: + args['username'] = self.username + if self.krbd is not None: + args['krbd'] = 1 if self.krbd else 0 + if self.maxfiles is not None: + args['maxfiles'] = self.maxfiles + if self.server is not None: + args['server'] = self.server + if self.export is not None: + args['export'] = self.export + if self.options is not None: + args['options'] = self.options + if self.vgname is not None: + args['vgname'] = self.vgname + if self.thinpool is not None: + args['thinpool'] = self.thinpool + if self.sparse is not None: + args['sparse'] = 1 if self.sparse else 0 + if self.is_mountpoint is not None: + args['is_mountpoint'] = 1 if self.is_mountpoint else 0 + + if self.maxfiles is not None and 'backup' not in self.content: + self.module.fail_json(msg="maxfiles is not allowed when there is no 'backup' in content") + if self.krbd is not None and self.type != 'rbd': + self.module.fail_json(msg="krbd is only allowed with 'rbd' storage type") + + return args + + def create_storage(self): + new_storage = self.prepare_storage_args() + try: + pvesh.create("storage", storage=self.name, **new_storage) + return None + except ProxmoxShellError as e: + return e.message + + def modify_storage(self): + lookup = self.lookup() + new_storage = self.prepare_storage_args() + + staged_storage = {} + updated_fields = [] + error = None + + for key in new_storage: + if key == 'content': + if set(new_storage['content'].split(',')) \ + != set(lookup.get('content', '').split(',')): + updated_fields.append(key) + staged_storage[key] = new_storage[key] + elif key == 'monhost': + if set(self.monhost) != set(lookup.get('monhost', '').split(',')): + updated_fields.append(key) + staged_storage[key] = new_storage[key] + elif key == 'nodes': + if set(self.nodes) != set(lookup.get('nodes', '').split(',')): + updated_fields.append(key) + staged_storage[key] = new_storage[key] + else: + new_value = to_text(new_storage[key]) if isinstance(new_storage[key], str) else new_storage[key] + if key not in lookup or new_value != lookup[key]: + updated_fields.append(key) + staged_storage[key] = new_storage[key] + + if self.module.check_mode: + self.module.exit_json(changed=bool(updated_fields), expected_changes=updated_fields) + + if not updated_fields: + # No changes necessary + return (updated_fields, error) + + try: + pvesh.set("storage/{}".format(self.name), **staged_storage) + except ProxmoxShellError as e: + error = e.message + + return (updated_fields, error) + + + def remove_storage(self): + try: + pvesh.delete("storage/{}".format(self.name)) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + +def main(): + # Refer to https://pve.proxmox.com/pve-docs/api-viewer/index.html + module_args = dict( + name=dict(type='str', required=True, aliases=['storage', 'storageid']), + state=dict(default='present', choices=['present', 'absent'], type='str'), + # Globally applicable PVE API arguments + content=dict(type='list', required=True, aliases=['storagetype']), + disable=dict(required=False, type='bool', default=False), + nodes=dict(type='list', required=False, default=None), + type=dict(default=None, type='str', required=True, + choices=["dir", "nfs", "rbd", "lvm", "lvmthin", "cephfs", + "zfspool", "btrfs", "pbs"]), + # Remaining PVE API arguments (depending on type) past this point + datastore=dict(default=None, type='str', required=False), + encryption_key=dict(default=None, type='str', required=False), + fingerprint=dict(default=None, type='str', required=False), + master_pubkey=dict(default=None, type='str', required=False), + password=dict(default=None, type='str', required=False), + path=dict(default=None, required=False, type='str'), + pool=dict(default=None, type='str', required=False), + monhost=dict(default=None, type='list', required=False), + username=dict(default=None, type='str', required=False), + krbd=dict(default=None, type='bool', required=False), + maxfiles=dict(default=None, type='int', required=False), + export=dict(default=None, type='str', required=False), + server=dict(default=None, type='str', required=False), + options=dict(default=None, type='str', required=False), + vgname=dict(default=None, type='str', required=False), + thinpool=dict(default=None, type='str', required=False), + sparse=dict(default=None, type='bool', required=False), + is_mountpoint=dict(default=None, type='bool', required=False), + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + required_if=[ + ["type", "cephfs", ["content"]], + ["type", "dir", ["path", "content"]], + ["type", "rbd", ["pool", "content"]], + ["type", "nfs", ["server", "content", "export"]], + ["type", "lvm", ["vgname", "content"]], + ["type", "lvmthin", ["vgname", "thinpool", "content"]], + ["type", "zfspool", ["pool", "content"]], + ["type", "btrfs", ["path", "content"]], + ["type", "pbs", ["server", "username", "password", "datastore"]] + ], + required_by={ + "master_pubkey": "encryption_key" + } + ) + storage = ProxmoxStorage(module) + + changed = False + error = None + result = {} + result['state'] = storage.state + result['changed'] = False + + if storage.state == 'absent': + if storage.exists(): + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + (changed, error) = storage.remove_storage() + elif storage.state == 'present': + if not storage.exists(): + result['changed'] = True + if module.check_mode: + module.exit_json(**result) + + error = storage.create_storage() + else: + # modify storage (check mode is ok) + (updated_fields,error) = storage.modify_storage() + + if updated_fields: + result['changed'] = True + result['updated_fields'] = updated_fields + + if error is not None: + module.fail_json(name=storage.name, msg=error) + + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/proxmox_user.py b/plugins/modules/proxmox_user.py new file mode 100755 index 0000000..5c345dc --- /dev/null +++ b/plugins/modules/proxmox_user.py @@ -0,0 +1,302 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python +# -*- coding: utf-8 -*- + +ANSIBLE_METADATA = { + 'metadata_version': '0.2', + 'status': ['preview'], + 'supported_by': 'lae' +} + +DOCUMENTATION = ''' +--- +module: proxmox_user + +short_description: Manages user accounts in Proxmox + +options: + name: + required: true + aliases: [ "user", "userid" ] + description: + - Name and realm of the user to create, e.g. C(operator@pam) and + C(pveapi@pve). + state: + required: false + default: "present" + choices: [ "present", "absent" ] + description: + - Specifies whether the user should exist or not. + enable: + required: false + default: yes + type: bool + description: + - Whether or not the user should be enabled in PVE. + groups: + required: false + type: list + description: + - Specifies a list of PVE groups that this user should belong to. + comment: + required: false + description: + - Optionally sets the user's comment in PVE. + email: + required: false + description: + - Optionally sets the user's email in PVE. + firstname: + required: false + description: + - Optionally sets the user's first name in PVE. + lastname: + required: false + description: + - Optionally sets the user's last name in PVE. + password: + required: false + description: + - Optionally sets the user's password in PVE. Note that this is only + used during the creation of a user to specify their initial + password, thus cannot be used to change a password of a user that + already exists (due to a limitation of the API, I believe). This + also only applies to the C(pve) realm as well, probably. + expire: + required: false + default: 0 + type: int + description: + - Account expiration date (seconds since epoch). C(0) means no + expiration date. + +author: + - Musee Ullah (@lae) +''' + +EXAMPLES = ''' +- name: Create PVE user with an initial password that expires at the beginning of 2018 + proxmox_user: + name: helloworld@pve + expire: 1514793600 + password: helloworld + firstname: Hello + lastname: World + comment: A hello world user. + groups:[ "test_users" ] +- name: Another way of defining groups + proxmox_user: + name: admin@pve + password: "{{ vaulted_password }}" + groups: + - Administrators + - APIUsers +- name: Add email for root user + proxmox_user: + name: root@pam + email: root@mail.example +- name: Disable a user + proxmox_user: + name: baduser@pam + enable: no +- name: Ensure a user does not exist + proxmox_user: + name: ghost@pve + state: absent +''' + +RETURN = ''' +updated_fields: + description: Fields that were modified for an existing user + type: list +user: + description: Information about the user fetched from PVE after this task completed. + type: json +''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils._text import to_text +from ansible.module_utils.pvesh import ProxmoxShellError +import ansible.module_utils.pvesh as pvesh + +class ProxmoxUser(object): + def __init__(self, module): + self.module = module + self.name = module.params['name'] + self.state = module.params['state'] + self.enable = module.params['enable'] + self.groups = module.params['groups'] + self.comment = module.params['comment'] + self.email = module.params['email'] + self.expire = module.params['expire'] + self.firstname = module.params['firstname'] + self.lastname = module.params['lastname'] + self.password = module.params['password'] + + def lookup(self): + try: + return pvesh.get("access/users/{}".format(self.name)) + except ProxmoxShellError as e: + self.module.fail_json(msg=e.message, status_code=e.status_code) + + def check_groups_exist(self): + # Checks to see if groups specified already exist or not + if self.groups is not None: + try: + groups = [group['groupid'] for group in pvesh.get("access/groups")] + return set(self.groups).issubset(set(groups)) + except ProxmoxShellError as e: + self.module.fail_json(msg=e.message, status_code=e.status_code) + + return True + + def prepare_user_args(self): + args = {} + + args['enable'] = 1 if self.enable else 0 + args['expire'] = self.expire + + if self.comment is not None: + args['comment'] = self.comment + + if self.firstname is not None: + args['firstname'] = self.firstname + + if self.lastname is not None: + args['lastname'] = self.lastname + + if self.email is not None: + args['email'] = self.email + + if self.groups is not None: + args['groups'] = ','.join(self.groups) + + return args + + def remove_user(self): + try: + pvesh.delete("access/users/{}".format(self.name)) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def create_user(self): + new_user = self.prepare_user_args() + + if self.password is not None: + new_user['password'] = self.password + + if not self.check_groups_exist(): + return (False, "One or more specified groups do not exist.") + + try: + pvesh.create("access/users", userid=self.name, **new_user) + return (True, None) + except ProxmoxShellError as e: + return (False, e.message) + + def modify_user(self): + lookup = self.lookup() + staged_user = self.prepare_user_args() + + updated_fields = [] + error = None + + for key in staged_user: + if key == 'groups': + # Since staged_user['groups'] is already converted to a string, + # we check our object instead + if set(self.groups) != set(lookup['groups']): + updated_fields.append(key) + else: + staged_value = to_text(staged_user[key]) if isinstance(staged_user[key], str) else staged_user[key] + if key not in lookup or staged_value != lookup[key]: + updated_fields.append(key) + + if self.module.check_mode: + self.module.exit_json(changed=bool(updated_fields), expected_changes=updated_fields) + + if not updated_fields: + # No changes necessary + return (updated_fields, error) + + if not self.check_groups_exist(): + error = "One or more specified groups do not exist." + else: + try: + pvesh.set("access/users/{}".format(self.name), **staged_user) + except ProxmoxShellError as e: + error = e.message + + return (updated_fields, error) + +def main(): + # Refer to https://pve.proxmox.com/pve-docs/api-viewer/index.html + module = AnsibleModule( + argument_spec = dict( + name=dict(type='str', required=True, aliases=['user', 'userid']), + state=dict(default='present', choices=['present', 'absent'], type='str'), + enable=dict(default='yes', type='bool'), + groups=dict(default=None, type='list'), + comment=dict(default=None, type='str'), + email=dict(default=None, type='str'), + firstname=dict(default=None, type='str'), + lastname=dict(default=None, type='str'), + password=dict(default=None, type='str', no_log=True), + expire=dict(default=0, type='int') + ), + supports_check_mode=True + ) + + user = ProxmoxUser(module) + + changed = False + error = None + result = {} + result['name'] = user.name + result['state'] = user.state + + if user.password is not None: + result['password'] = 'NOT_LOGGING_PASSWORD' + + if user.state == 'absent': + if user.lookup() is not None: + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = user.remove_user() + + if error is not None: + module.fail_json(name=user.name, msg=error) + elif user.state == 'present': + if not user.lookup(): + if module.check_mode: + module.exit_json(changed=True) + + (changed, error) = user.create_user() + else: + # modify user (note: this function is check mode aware) + (updated_fields, error) = user.modify_user() + + if updated_fields: + changed = True + result['updated_fields'] = updated_fields + + if error is not None: + module.fail_json(name=user.name, msg=error) + + lookup = user.lookup() + if lookup: + result['user'] = lookup + + result['changed'] = changed + + module.exit_json(**result) + +if __name__ == '__main__': + main() diff --git a/plugins/modules/pve_ceph_volume.py b/plugins/modules/pve_ceph_volume.py new file mode 100755 index 0000000..b059b7d --- /dev/null +++ b/plugins/modules/pve_ceph_volume.py @@ -0,0 +1,153 @@ +# +# Copyright (c) 2016 Musee Ullah +# Author: Musee Ullah (@lae) +# Forked from https://github.com/lae/ansible-role-proxmox +# + +#!/usr/bin/python + +from ansible.module_utils.basic import AnsibleModule +import datetime + +ANSIBLE_METADATA = { + 'metadata_version': '1.0', + 'status': ['preview'], + 'supported_by': 'community' +} + +DOCUMENTATION = ''' +--- +module: ceph_volume + +short_description: Query ceph OSDs with ceph-volume + +description: + - Using the ceph-volume utility available in Ceph this module + can be used to query ceph OSDs that are backed by logical volumes. + - Only available in ceph versions luminous or greater. + +options: + cluster: + description: + - The ceph cluster name. + required: false + default: ceph + data: + description: + - The logical volume name or device to use for the OSD data. + required: true + data_vg: + description: + - If data is a lv, this must be the name of the volume group it belongs to. + required: false + +author: + - Andrew Schoen (@andrewschoen) + - Sebastien Han +''' + +EXAMPLES = ''' +- name: query all osds + ceph_volume: + +- name: query single osd on test cluster + ceph_volume: + cluster: test + data: /dev/sdc +''' + +def exec_command(module, cmd, stdin=None): + ''' + Execute command(s) + ''' + binary_data = False + if stdin: + binary_data = True + rc, out, err = module.run_command(cmd, data=stdin, binary_data=binary_data) + return rc, cmd, out, err + +def get_data(data, data_vg): + if data_vg: + data = '{0}/{1}'.format(data_vg, data) + return data + + +def list_osd(module): + ''' + List will detect whether or not a device has Ceph LVM Metadata + ''' + + # get module variables + cluster = module.params['cluster'] + data = module.params.get('data', None) + data_vg = module.params.get('data_vg', None) + data = get_data(data, data_vg) + + # Build the CLI + action = ['lvm', 'list'] + cmd = ['ceph-volume', '--cluster', cluster] + cmd.extend(action) + if data: + cmd.append(data) + cmd.append('--format=json') + + return cmd + + +def run_module(): + module_args = dict( + cluster=dict(type='str', required=False, default='ceph'), + data=dict(type='str', required=False), + data_vg=dict(type='str', required=False), + ) + + module = AnsibleModule( + argument_spec=module_args, + supports_check_mode=True, + ) + + result = dict( + changed=False, + stdout='', + stderr='', + rc=0, + start='', + end='', + delta='', + ) + + if module.check_mode: + module.exit_json(**result) + + # start execution + startd = datetime.datetime.now() + + # List Ceph LVM Metadata on a device + rc, cmd, out, err = exec_command(module, list_osd(module)) + + endd = datetime.datetime.now() + delta = endd - startd + + result = dict( + cmd=cmd, + start=str(startd), + end=str(endd), + delta=str(delta), + rc=rc, + stdout=out.rstrip('\r\n'), + stderr=err.rstrip('\r\n'), + changed=False, + ) + + if rc != 0: + module.fail_json(msg='non-zero return code', **result) + + module.exit_json(**result) + + +def main(): + run_module() + + +if __name__ == '__main__': + main() diff --git a/roles/proxmox/defaults/main.yml b/roles/proxmox/defaults/main.yml index 0124b2b..2bd35b3 100644 --- a/roles/proxmox/defaults/main.yml +++ b/roles/proxmox/defaults/main.yml @@ -12,7 +12,6 @@ proxmox__purpose_packages: # If enabled, the role will manage the purpose package setup for repos and package versioning. proxmox__manage_purpose_package_setup: true -proxmox__purpose_package_setup_template: purpose_package_setup.j2 proxmox__default: {} proxmox__group: {} diff --git a/roles/proxmox/meta/main.yml b/roles/proxmox/meta/main.yml deleted file mode 100644 index ddf19d4..0000000 --- a/roles/proxmox/meta/main.yml +++ /dev/null @@ -1,3 +0,0 @@ -# dependencies: -# - role: aybarsm.linux.package_manager -# - role: lae.proxmox \ No newline at end of file diff --git a/roles/proxmox/templates/purpose_package_setup.j2 b/roles/proxmox/templates/purpose_package_setup.j2 deleted file mode 100644 index 8ef9ae2..0000000 --- a/roles/proxmox/templates/purpose_package_setup.j2 +++ /dev/null @@ -1,42 +0,0 @@ -{%- if ansible_distribution_release is undefined -%} -{%- set ansible_distribution_release = 'bookworm' -%} -{%- endif -%} -{%- set proxmox_repos = [] -%} -{%- set proxmox_packages = [] -%} -{%- if proxmox__manage_purpose_package_setup is defined and proxmox__manage_purpose_package_setup and proxmox__all.purposes is defined -%} -{%- for purpose in proxmox__purpose_names -%} -{%- for purpose_type in proxmox__purpose_types -%} -{%- set host_config = proxmox__all.purposes[purpose] if purpose in proxmox__all.purposes else none -%} -{%- set is_host_purpose = true if host_config else false -%} -{%- set is_host_purpose_type = true if host_config and host_config.type is defined and host_config.type == purpose_type else false -%} -{%- set host_purpose_version = host_config.version if host_config and host_config.version is defined else none -%} -{%- set purpose_repo_state = 'present' if is_host_purpose and is_host_purpose_type else 'absent' -%} -{%- set purpose_repo = proxmox__repo_url_enterprise if purpose_type == 'enterprise' else proxmox__repo_url_no_subscription -%} -{%- if purpose in ['pve', 'pbs', 'pmg'] -%} -{%- set proxmox_repos = proxmox_repos.append({ - 'type': 'repo', - 'repo': 'deb ' + purpose_repo + '/' + purpose + ' ' + ansible_distribution_release + ' ' + purpose + '-' + purpose_type, - 'filename': purpose + '-' + purpose_type, - 'state': purpose_repo_state -}) -%} -{%- if host_purpose_version and proxmox__purpose_packages[purpose] is defined -%} -{%- set package_name = proxmox__purpose_packages[purpose] if host_purpose_version == 'latest' else proxmox__purpose_packages[purpose] + '=' + host_purpose_version -%} -{%- set package_state = 'latest' if host_purpose_version == 'latest' else 'present' -%} -{%- set proxmox_packages = proxmox_packages.append({ - 'type': 'package', - 'name': package_name, - 'state': package_state -}) -%} -{%- endif -%} -{%- elif purpose.startswith('ceph-') -%} -{%- set proxmox_repos = proxmox_repos.append({ - 'type': 'repo', - 'repo': 'deb ' + purpose_repo + '/' + purpose + ' ' + ansible_distribution_release + ' ' + purpose_type, - 'filename': 'ceph', - 'state': purpose_repo_state -}) -%} -{%- endif -%} -{%- endfor -%} -{%- endfor -%} -{%- endif -%} -{{ proxmox_repos + proxmox_packages }}