-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
1,930 additions
and
47 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
__pycache__ | ||
__pycache__/ | ||
|
||
*.bak* | ||
*.bak*/ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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() |
Oops, something went wrong.