1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3# 4# Copyright: Ansible Project 5# 6# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 7 8from __future__ import absolute_import, division, print_function 9__metaclass__ = type 10 11 12DOCUMENTATION = ''' 13--- 14module: proxmox_template 15short_description: management of OS templates in Proxmox VE cluster 16description: 17 - allows you to upload/delete templates in Proxmox VE cluster 18options: 19 node: 20 description: 21 - Proxmox VE node on which to operate. 22 type: str 23 src: 24 description: 25 - path to uploaded file 26 - required only for C(state=present) 27 type: path 28 template: 29 description: 30 - the template name 31 - Required for state C(absent) to delete a template. 32 - Required for state C(present) to download an appliance container template (pveam). 33 type: str 34 content_type: 35 description: 36 - content type 37 - required only for C(state=present) 38 type: str 39 default: 'vztmpl' 40 choices: ['vztmpl', 'iso'] 41 storage: 42 description: 43 - target storage 44 type: str 45 default: 'local' 46 timeout: 47 description: 48 - timeout for operations 49 type: int 50 default: 30 51 force: 52 description: 53 - can be used only with C(state=present), exists template will be overwritten 54 type: bool 55 default: 'no' 56 state: 57 description: 58 - Indicate desired state of the template 59 type: str 60 choices: ['present', 'absent'] 61 default: present 62notes: 63 - Requires proxmoxer and requests modules on host. This modules can be installed with pip. 64author: Sergei Antipov (@UnderGreen) 65extends_documentation_fragment: community.general.proxmox.documentation 66''' 67 68EXAMPLES = ''' 69- name: Upload new openvz template with minimal options 70 community.general.proxmox_template: 71 node: uk-mc02 72 api_user: root@pam 73 api_password: 1q2w3e 74 api_host: node1 75 src: ~/ubuntu-14.04-x86_64.tar.gz 76 77- name: > 78 Upload new openvz template with minimal options use environment 79 PROXMOX_PASSWORD variable(you should export it before) 80 community.general.proxmox_template: 81 node: uk-mc02 82 api_user: root@pam 83 api_host: node1 84 src: ~/ubuntu-14.04-x86_64.tar.gz 85 86- name: Upload new openvz template with all options and force overwrite 87 community.general.proxmox_template: 88 node: uk-mc02 89 api_user: root@pam 90 api_password: 1q2w3e 91 api_host: node1 92 storage: local 93 content_type: vztmpl 94 src: ~/ubuntu-14.04-x86_64.tar.gz 95 force: yes 96 97- name: Delete template with minimal options 98 community.general.proxmox_template: 99 node: uk-mc02 100 api_user: root@pam 101 api_password: 1q2w3e 102 api_host: node1 103 template: ubuntu-14.04-x86_64.tar.gz 104 state: absent 105 106- name: Download proxmox appliance container template 107 community.general.proxmox_template: 108 node: uk-mc02 109 api_user: root@pam 110 api_password: 1q2w3e 111 api_host: node1 112 storage: local 113 content_type: vztmpl 114 template: ubuntu-20.04-standard_20.04-1_amd64.tar.gz 115''' 116 117import os 118import time 119 120try: 121 from proxmoxer import ProxmoxAPI 122 HAS_PROXMOXER = True 123except ImportError: 124 HAS_PROXMOXER = False 125 126from ansible.module_utils.basic import AnsibleModule, env_fallback 127 128 129def get_template(proxmox, node, storage, content_type, template): 130 return [True for tmpl in proxmox.nodes(node).storage(storage).content.get() 131 if tmpl['volid'] == '%s:%s/%s' % (storage, content_type, template)] 132 133 134def task_status(module, proxmox, node, taskid, timeout): 135 """ 136 Check the task status and wait until the task is completed or the timeout is reached. 137 """ 138 while timeout: 139 task_status = proxmox.nodes(node).tasks(taskid).status.get() 140 if task_status['status'] == 'stopped' and task_status['exitstatus'] == 'OK': 141 return True 142 timeout = timeout - 1 143 if timeout == 0: 144 module.fail_json(msg='Reached timeout while waiting for uploading/downloading template. Last line in task before timeout: %s' 145 % proxmox.node(node).tasks(taskid).log.get()[:1]) 146 147 time.sleep(1) 148 return False 149 150 151def upload_template(module, proxmox, node, storage, content_type, realpath, timeout): 152 taskid = proxmox.nodes(node).storage(storage).upload.post(content=content_type, filename=open(realpath, 'rb')) 153 return task_status(module, proxmox, node, taskid, timeout) 154 155 156def download_template(module, proxmox, node, storage, template, timeout): 157 taskid = proxmox.nodes(node).aplinfo.post(storage=storage, template=template) 158 return task_status(module, proxmox, node, taskid, timeout) 159 160 161def delete_template(module, proxmox, node, storage, content_type, template, timeout): 162 volid = '%s:%s/%s' % (storage, content_type, template) 163 proxmox.nodes(node).storage(storage).content.delete(volid) 164 while timeout: 165 if not get_template(proxmox, node, storage, content_type, template): 166 return True 167 timeout = timeout - 1 168 if timeout == 0: 169 module.fail_json(msg='Reached timeout while waiting for deleting template.') 170 171 time.sleep(1) 172 return False 173 174 175def main(): 176 module = AnsibleModule( 177 argument_spec=dict( 178 api_host=dict(required=True), 179 api_password=dict(no_log=True, fallback=(env_fallback, ['PROXMOX_PASSWORD'])), 180 api_token_id=dict(no_log=True), 181 api_token_secret=dict(no_log=True), 182 api_user=dict(required=True), 183 validate_certs=dict(type='bool', default=False), 184 node=dict(), 185 src=dict(type='path'), 186 template=dict(), 187 content_type=dict(default='vztmpl', choices=['vztmpl', 'iso']), 188 storage=dict(default='local'), 189 timeout=dict(type='int', default=30), 190 force=dict(type='bool', default=False), 191 state=dict(default='present', choices=['present', 'absent']), 192 ), 193 required_together=[('api_token_id', 'api_token_secret')], 194 required_one_of=[('api_password', 'api_token_id')], 195 required_if=[('state', 'absent', ['template'])] 196 ) 197 198 if not HAS_PROXMOXER: 199 module.fail_json(msg='proxmoxer required for this module') 200 201 state = module.params['state'] 202 api_host = module.params['api_host'] 203 api_password = module.params['api_password'] 204 api_token_id = module.params['api_token_id'] 205 api_token_secret = module.params['api_token_secret'] 206 api_user = module.params['api_user'] 207 validate_certs = module.params['validate_certs'] 208 node = module.params['node'] 209 storage = module.params['storage'] 210 timeout = module.params['timeout'] 211 212 auth_args = {'user': api_user} 213 if not (api_token_id and api_token_secret): 214 auth_args['password'] = api_password 215 else: 216 auth_args['token_name'] = api_token_id 217 auth_args['token_value'] = api_token_secret 218 219 try: 220 proxmox = ProxmoxAPI(api_host, verify_ssl=validate_certs, **auth_args) 221 # Used to test the validity of the token if given 222 proxmox.version.get() 223 except Exception as e: 224 module.fail_json(msg='authorization on proxmox cluster failed with exception: %s' % e) 225 226 if state == 'present': 227 try: 228 content_type = module.params['content_type'] 229 src = module.params['src'] 230 231 # download appliance template 232 if content_type == 'vztmpl' and not src: 233 template = module.params['template'] 234 235 if not template: 236 module.fail_json(msg='template param for downloading appliance template is mandatory') 237 238 if get_template(proxmox, node, storage, content_type, template) and not module.params['force']: 239 module.exit_json(changed=False, msg='template with volid=%s:%s/%s already exists' % (storage, content_type, template)) 240 241 if download_template(module, proxmox, node, storage, template, timeout): 242 module.exit_json(changed=True, msg='template with volid=%s:%s/%s downloaded' % (storage, content_type, template)) 243 244 template = os.path.basename(src) 245 if get_template(proxmox, node, storage, content_type, template) and not module.params['force']: 246 module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already exists' % (storage, content_type, template)) 247 elif not src: 248 module.fail_json(msg='src param to uploading template file is mandatory') 249 elif not (os.path.exists(src) and os.path.isfile(src)): 250 module.fail_json(msg='template file on path %s not exists' % src) 251 252 if upload_template(module, proxmox, node, storage, content_type, src, timeout): 253 module.exit_json(changed=True, msg='template with volid=%s:%s/%s uploaded' % (storage, content_type, template)) 254 except Exception as e: 255 module.fail_json(msg="uploading/downloading of template %s failed with exception: %s" % (template, e)) 256 257 elif state == 'absent': 258 try: 259 content_type = module.params['content_type'] 260 template = module.params['template'] 261 262 if not get_template(proxmox, node, storage, content_type, template): 263 module.exit_json(changed=False, msg='template with volid=%s:%s/%s is already deleted' % (storage, content_type, template)) 264 265 if delete_template(module, proxmox, node, storage, content_type, template, timeout): 266 module.exit_json(changed=True, msg='template with volid=%s:%s/%s deleted' % (storage, content_type, template)) 267 except Exception as e: 268 module.fail_json(msg="deleting of template %s failed with exception: %s" % (template, e)) 269 270 271if __name__ == '__main__': 272 main() 273