Skip to content

Commit

Permalink
lae.proxmox role modules ported
Browse files Browse the repository at this point in the history
  • Loading branch information
aybarsm committed Jul 14, 2024
1 parent d71b2fd commit edf247a
Show file tree
Hide file tree
Showing 14 changed files with 1,930 additions and 47 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__pycache__
__pycache__/

*.bak*
*.bak*/
Expand Down
105 changes: 105 additions & 0 deletions plugins/module_utils/pvesh.py
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)
76 changes: 76 additions & 0 deletions plugins/modules/collect_kernel_info.py
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()
190 changes: 190 additions & 0 deletions plugins/modules/proxmox_acl.py
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()
Loading

0 comments on commit edf247a

Please sign in to comment.