From fc27c49e6b466511c7e7d5dac56b13388d175ec6 Mon Sep 17 00:00:00 2001 From: Rafael Chicoli <rafaelchicoli@hotmail.com> Date: Mon, 1 Nov 2021 22:04:58 +0100 Subject: [PATCH] lxd_container: support lxd instance types Update the lxd_container module to enable the new LXD API endpoint, which supports different types of instances, such as containers and virtual machines. The type attributes can be set explicitly to create containers or virtual machines. --- .../3661-lxd_container-add-vm-support.yml | 2 + plugins/modules/cloud/lxd/lxd_container.py | 72 +++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) create mode 100644 changelogs/fragments/3661-lxd_container-add-vm-support.yml diff --git a/changelogs/fragments/3661-lxd_container-add-vm-support.yml b/changelogs/fragments/3661-lxd_container-add-vm-support.yml new file mode 100644 index 00000000000..b2a8a6d0668 --- /dev/null +++ b/changelogs/fragments/3661-lxd_container-add-vm-support.yml @@ -0,0 +1,2 @@ +minor_changes: + - lxd_container - manipulate the API endpoint to create instances of type container or virtual-machine. diff --git a/plugins/modules/cloud/lxd/lxd_container.py b/plugins/modules/cloud/lxd/lxd_container.py index e06b72b2441..f4804e2d873 100644 --- a/plugins/modules/cloud/lxd/lxd_container.py +++ b/plugins/modules/cloud/lxd/lxd_container.py @@ -11,16 +11,23 @@ DOCUMENTATION = ''' --- module: lxd_container -short_description: Manage LXD Containers +short_description: Manage LXD instances description: - - Management of LXD containers + - Management of LXD containers and virtual machines. author: "Hiroaki Nakamura (@hnakamur)" options: name: description: - - Name of a container. + - Name of an instance. type: str required: true + api_endpoint: + description: + - The LXD Rest API endpoint to create containers oder virtual-machines. + It can be set to "/1.0/instances" for creating instances of type container or virtual-machine. + - If the LXD server version is older than 3.19, then this should be set to "/1.0/containers". + type: str + required: false architecture: description: - 'The architecture for the container (for example C(x86_64) or C(i686)). @@ -32,7 +39,7 @@ - 'The config for the container (for example C({"limits.cpu": "2"})). See U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-1).' - If the container already exists and its "config" values in metadata - obtained from GET /1.0/containers/<name> + obtained from GET <api_endpoint>/<name> U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#10containersname) are different, this module tries to apply the configurations. - The keys starting with C(volatile.) are ignored for this comparison when I(ignore_volatile_options=true). @@ -107,6 +114,12 @@ required: false default: 30 type: int + type: + description: + - Instance type can be either "container" or "virtual-machine". + required: false + default: container + type: str wait_for_ipv4_addresses: description: - If this is true, the C(lxd_container) waits until IPv4 addresses @@ -361,7 +374,7 @@ # CONFIG_PARAMS is a list of config attribute names. CONFIG_PARAMS = [ - 'architecture', 'config', 'devices', 'ephemeral', 'profiles', 'source' + 'api_endpoint', 'architecture', 'config', 'devices', 'ephemeral', 'profiles', 'source', 'type' ] @@ -384,6 +397,9 @@ def __init__(self, module): self.addresses = None self.target = self.module.params['target'] + self.api_endpoint = self.module.params['api_endpoint'] + self.type = self.module.params['type'] + self.key_file = self.module.params.get('client_key') if self.key_file is None: self.key_file = '{0}/.config/lxc/client.key'.format(os.environ['HOME']) @@ -421,13 +437,13 @@ def _build_config(self): def _get_container_json(self): return self.client.do( - 'GET', '/1.0/containers/{0}'.format(self.name), + 'GET', '{0}/{1}'.format(self.api_endpoint, self.name), ok_error_codes=[404] ) def _get_container_state_json(self): return self.client.do( - 'GET', '/1.0/containers/{0}/state'.format(self.name), + 'GET', '{0}/{1}/state'.format(self.api_endpoint, self.name), ok_error_codes=[404] ) @@ -441,15 +457,15 @@ def _change_state(self, action, force_stop=False): body_json = {'action': action, 'timeout': self.timeout} if force_stop: body_json['force'] = True - return self.client.do('PUT', '/1.0/containers/{0}/state'.format(self.name), body_json=body_json) + return self.client.do('PUT', '{0}/{1}/state'.format(self.api_endpoint, self.name), body_json=body_json) def _create_container(self): config = self.config.copy() config['name'] = self.name if self.target: - self.client.do('POST', '/1.0/containers?' + urlencode(dict(target=self.target)), config) + self.client.do('POST', '{0}?{1}'.format(self.api_endpoint, urlencode(dict(target=self.target))), config) else: - self.client.do('POST', '/1.0/containers', config) + self.client.do('POST', self.api_endpoint, config) self.actions.append('create') def _start_container(self): @@ -465,7 +481,7 @@ def _restart_container(self): self.actions.append('restart') def _delete_container(self): - self.client.do('DELETE', '/1.0/containers/{0}'.format(self.name)) + self.client.do('DELETE', '{0}/{1}'.format(self.api_endpoint, self.name)) self.actions.append('delete') def _freeze_container(self): @@ -594,7 +610,8 @@ def _needs_to_apply_container_configs(self): self._needs_to_change_container_config('config') or self._needs_to_change_container_config('ephemeral') or self._needs_to_change_container_config('devices') or - self._needs_to_change_container_config('profiles') + self._needs_to_change_container_config('profiles') or + self._needs_to_change_container_config('type') ) def _apply_container_configs(self): @@ -603,8 +620,10 @@ def _apply_container_configs(self): 'architecture': old_metadata['architecture'], 'config': old_metadata['config'], 'devices': old_metadata['devices'], - 'profiles': old_metadata['profiles'] + 'profiles': old_metadata['profiles'], + 'type': old_metadata['type'], } + if self._needs_to_change_container_config('architecture'): body_json['architecture'] = self.config['architecture'] if self._needs_to_change_container_config('config'): @@ -616,7 +635,13 @@ def _apply_container_configs(self): body_json['devices'] = self.config['devices'] if self._needs_to_change_container_config('profiles'): body_json['profiles'] = self.config['profiles'] - self.client.do('PUT', '/1.0/containers/{0}'.format(self.name), body_json=body_json) + if self._needs_to_change_container_config('type') and self.type != self.old_container_json['metadata']['type']: + self.module.fail_json(rc=1, + msg="Failed to update the instance name '{0}'. The parameter 'type' is immutable and cannot be changed." + " The last provided value '{1}' mismatch the current one '{2}'". + format(self.name, self.type, self.old_container_json['metadata']['type'])) + + self.client.do('PUT', '{0}/{1}'.format(self.api_endpoint, self.name), body_json=body_json) self.actions.append('apply_container_configs') def run(self): @@ -698,6 +723,13 @@ def main(): type='int', default=30 ), + api_endpoint=dict( + type='str', + ), + type=dict( + type='str', + default='container' + ), wait_for_ipv4_addresses=dict( type='bool', default=False @@ -736,6 +768,18 @@ def main(): 'This will change in the future. Please test your scripts' 'by "ignore_volatile_options: false". To keep the old behavior, set that option explicitly to "true"', version='6.0.0', collection_name='community.general') + + if module.params['api_endpoint'] is None: + if module.params['type'] == 'virtual-machine': + module.params['api_endpoint'] = '/1.0/virtual-machines' + else: + module.params['api_endpoint'] = '/1.0/containers' + module.deprecate( + 'The LXD Rest API has implemented a new endpoint, which supports containers and virtual-machines.' + ' If the LXD server version is greater than or equal to 3.19, make sure to change the API endpoint by setting "api_endpoint: /1.0/instances".' + ' To keep the old behavior, set that option explicitly to "/1.0/containers"', + version='6.0.0', collection_name='community.general') + lxd_manage = LXDContainerManagement(module=module) lxd_manage.run()