proxmox_template: Add optional checksum validation (#9601)

* Adds support for checksums in Proxmox_template.

* Implemented checksum verification

* Removed unintended captilization changes

* further fixing of unintended changes

* removed misspelling

* Final adjustementsto proxmox_template.py

* fixed typo

* fixed a typo in sha512

* add changelog fragment

* fixed type in choices for checksum_algortihm

* fixed file naming error and add relevant links to changelog

* Fix all unintentional refactorings

* refactoring changes removed

* renamed the function verify_checksum to fetch_and_verify for clarity

* Adjusted additions based on feedback

* Apply suggestions from code review

Co-authored-by: Felix Fontein <felix@fontein.de>

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
pull/9625/head
Andrew Bowen 2025-01-25 04:21:20 -05:00 committed by GitHub
parent 6294f0b747
commit b9299e633c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 64 additions and 3 deletions

View File

@ -0,0 +1,2 @@
minor_changes:
- proxmox_template - add support for checksum validation with new options ``checksum_algorithm`` and ``checksum`` (https://github.com/ansible-collections/community.general/issues/9553, https://github.com/ansible-collections/community.general/pull/9601).

View File

@ -170,6 +170,15 @@ class ProxmoxAnsible(object):
except Exception as e:
self.module.fail_json(msg='Unable to retrieve API task ID from node %s: %s' % (node, e))
def api_task_failed(self, node, taskid):
""" Explicitly check if the task stops but exits with a failed status
"""
try:
status = self.proxmox_api.nodes(node).tasks(taskid).status.get()
return status['status'] == 'stopped' and status['exitstatus'] != 'OK'
except Exception as e:
self.module.fail_json(msg='Unable to retrieve API task ID from node %s: %s' % (node, e))
def api_task_complete(self, node_name, task_id, timeout):
"""Wait until the task stops or times out.

View File

@ -71,6 +71,21 @@ options:
type: str
choices: ['present', 'absent']
default: present
checksum_algorithm:
description:
- Algorithm used to verify the checksum.
- If specified, O(checksum) must also be specified.
type: str
choices: ['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']
version_added: 10.3.0
checksum:
description:
- The checksum to validate against.
- Checksums are often provided by software distributors to verify that a download is not corrupted.
- Checksums can usually be found on the distributors download page in the form of a file or string.
- If specified, O(checksum_algorithm) must also be specified.
type: str
version_added: 10.3.0
notes:
- Requires C(proxmoxer) and C(requests) modules on host. Those modules can be installed with M(ansible.builtin.pip).
- C(proxmoxer) >= 1.2.0 requires C(requests_toolbelt) to upload files larger than 256 MB.
@ -82,6 +97,7 @@ extends_documentation_fragment:
"""
EXAMPLES = r"""
---
- name: Upload new openvz template with minimal options
community.general.proxmox_template:
node: uk-mc02
@ -147,6 +163,16 @@ EXAMPLES = r"""
storage: local
content_type: vztmpl
template: ubuntu-20.04-standard_20.04-1_amd64.tar.gz
- name: Download and verify a template's checksum
community.general.proxmox_template:
node: uk-mc02
api_user: root@pam
api_password: 1q2w3e
api_host: node1
url: ubuntu-20.04-standard_20.04-1_amd64.tar.gz
checksum_algorithm: sha256
checksum: 65d860160bdc9b98abf72407e14ca40b609417de7939897d3b58d55787aaef69
"""
import os
@ -156,7 +182,7 @@ import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.community.general.plugins.module_utils.proxmox import (proxmox_auth_argument_spec, ProxmoxAnsible)
from ansible_collections.community.general.plugins.module_utils.version import LooseVersion
from ansible.module_utils.six.moves.urllib.parse import urlparse
from ansible.module_utils.six.moves.urllib.parse import urlparse, urlencode
REQUESTS_TOOLBELT_ERR = None
try:
@ -183,6 +209,8 @@ class ProxmoxTemplateAnsible(ProxmoxAnsible):
while timeout:
if self.api_task_ok(node, taskid):
return True
elif self.api_task_failed(node, taskid):
self.module.fail_json(msg="Task error: %s" % self.proxmox_api.nodes(node).tasks(taskid).status.get()['exitstatus'])
timeout = timeout - 1
if timeout == 0:
self.module.fail_json(msg='Reached timeout while waiting for uploading/downloading template. Last line in task before timeout: %s' %
@ -235,6 +263,21 @@ class ProxmoxTemplateAnsible(ProxmoxAnsible):
time.sleep(1)
return False
def fetch_and_verify(self, node, storage, url, content_type, timeout, checksum, checksum_algorithm):
""" Fetch a template from a web url, then verify it using a checksum.
"""
data = {
'url': url,
'content': content_type,
'filename': os.path.basename(url),
'checksum': checksum,
'checksum-algorithm': checksum_algorithm}
try:
taskid = self.proxmox_api.nodes(node).storage(storage).post("download-url?{}".format(urlencode(data)))
return self.task_status(node, taskid, timeout)
except Exception as e:
self.module.fail_json(msg="Checksum mismatch: %s" % (e))
def main():
module_args = proxmox_auth_argument_spec()
@ -248,12 +291,14 @@ def main():
timeout=dict(type='int', default=30),
force=dict(type='bool', default=False),
state=dict(default='present', choices=['present', 'absent']),
checksum_algorithm=dict(choices=['md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512']),
checksum=dict(type='str'),
)
module_args.update(template_args)
module = AnsibleModule(
argument_spec=module_args,
required_together=[('api_token_id', 'api_token_secret')],
required_together=[('api_token_id', 'api_token_secret'), ('checksum', 'checksum_algorithm')],
required_one_of=[('api_password', 'api_token_id')],
required_if=[('state', 'absent', ['template'])],
mutually_exclusive=[("src", "url")],
@ -265,6 +310,8 @@ def main():
node = module.params['node']
storage = module.params['storage']
timeout = module.params['timeout']
checksum = module.params['checksum']
checksum_algorithm = module.params['checksum_algorithm']
if state == 'present':
content_type = module.params['content_type']
@ -272,7 +319,7 @@ def main():
url = module.params['url']
# download appliance template
if content_type == 'vztmpl' and not (src or url) :
if content_type == 'vztmpl' and not (src or url):
template = module.params['template']
if not template:
@ -303,6 +350,9 @@ def main():
elif not proxmox.delete_template(node, storage, content_type, template, timeout):
module.fail_json(changed=False, msg='failed to delete template with volid=%s:%s/%s' % (storage, content_type, template))
if checksum:
if proxmox.fetch_and_verify(node, storage, url, content_type, timeout, checksum, checksum_algorithm):
module.exit_json(changed=True, msg="Checksum verified, template with volid=%s:%s/%s uploaded" % (storage, content_type, template))
if proxmox.fetch_template(node, storage, content_type, url, timeout):
module.exit_json(changed=True, msg='template with volid=%s:%s/%s uploaded' % (storage, content_type, template))