From 6dd4cd0eb782be4caf9d708143121a4272b9600e Mon Sep 17 00:00:00 2001 From: Frank Dornheim <524257+conloos@users.noreply.github.com> Date: Tue, 16 Feb 2021 07:15:50 +0100 Subject: [PATCH] Previously LXD profiles were overwritten, now these are merged. (#1813) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * added ``merge_profile`` parameter to merge configurations from the play to an existing profile * add fragment * cosmetic changes Co-authored-by: Frank Dornheim <“dornheim@posteo.de@users.noreply.github.com”> --- .../1813-lxd_profile-merge-profiles.yml | 2 + plugins/modules/cloud/lxd/lxd_profile.py | 124 +++++++++++++++++- 2 files changed, 121 insertions(+), 5 deletions(-) create mode 100644 changelogs/fragments/1813-lxd_profile-merge-profiles.yml diff --git a/changelogs/fragments/1813-lxd_profile-merge-profiles.yml b/changelogs/fragments/1813-lxd_profile-merge-profiles.yml new file mode 100644 index 0000000000..d374347a5e --- /dev/null +++ b/changelogs/fragments/1813-lxd_profile-merge-profiles.yml @@ -0,0 +1,2 @@ +minor_changes: +- lxd_profile - added ``merge_profile`` parameter to merge configurations from the play to an existing profile (https://github.com/ansible-collections/community.general/pull/1813). diff --git a/plugins/modules/cloud/lxd/lxd_profile.py b/plugins/modules/cloud/lxd/lxd_profile.py index 9a119d26a2..3094898f2c 100644 --- a/plugins/modules/cloud/lxd/lxd_profile.py +++ b/plugins/modules/cloud/lxd/lxd_profile.py @@ -2,12 +2,12 @@ # -*- coding: utf-8 -*- # Copyright: (c) 2016, Hiroaki Nakamura +# Copyright: (c) 2020, Frank Dornheim # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) from __future__ import absolute_import, division, print_function __metaclass__ = type - DOCUMENTATION = ''' --- module: lxd_profile @@ -52,6 +52,14 @@ options: See U(https://github.com/lxc/lxd/blob/master/doc/rest-api.md#post-11) required: false type: str + merge_profile: + description: + - Merge the configuration of the present profile with the new desired configuration, + instead of replacing it. + required: false + default: false + type: bool + version_added: 2.1.0 state: choices: - present @@ -142,6 +150,23 @@ EXAMPLES = ''' parent: br0 type: nic +# An example for modify/merge a profile +- hosts: localhost + connection: local + tasks: + - name: Merge a profile + community.general.lxd_profile: + merge_profile: true + name: macvlan + state: present + config: {} + description: my macvlan profile + devices: + eth0: + nictype: macvlan + parent: br0 + type: nic + # An example for deleting a profile - hosts: localhost connection: local @@ -181,7 +206,6 @@ actions: ''' import os - from ansible.module_utils.basic import AnsibleModule from ansible_collections.community.general.plugins.module_utils.lxd import LXDClient, LXDClientException @@ -266,7 +290,7 @@ class LXDProfileManagement(object): self._create_profile() else: self.module.fail_json( - msg='new_name must not be set when the profile does not exist and the specified state is present', + msg='new_name must not be set when the profile does not exist and the state is present', changed=False) else: if self.new_name is not None and self.new_name != self.name: @@ -307,10 +331,96 @@ class LXDProfileManagement(object): self._needs_to_change_profile_config('devices') ) - def _apply_profile_configs(self): - config = self.old_profile_json.copy() + def _merge_dicts(self, source, destination): + """Merge Dictionarys + + Get a list of filehandle numbers from logger to be handed to + DaemonContext.files_preserve + + Args: + dict(source): source dict + dict(destination): destination dict + Kwargs: + None + Raises: + None + Returns: + dict(destination): merged dict""" + for key, value in source.items(): + if isinstance(value, dict): + # get node or create one + node = destination.setdefault(key, {}) + self._merge_dicts(value, node) + else: + destination[key] = value + return destination + + def _merge_config(self, config): + """ merge profile + + Merge Configuration of the present profile and the new desired configitems + + Args: + dict(config): Dict with the old config in 'metadata' and new config in 'config' + Kwargs: + None + Raises: + None + Returns: + dict(config): new config""" + # merge or copy the sections from the existing profile to 'config' + for item in ['config', 'description', 'devices', 'name', 'used_by']: + if item in config: + config[item] = self._merge_dicts(config['metadata'][item], config[item]) + else: + config[item] = config['metadata'][item] + # merge or copy the sections from the ansible-task to 'config' + return self._merge_dicts(self.config, config) + + def _generate_new_config(self, config): + """ rebuild profile + + Rebuild the Profile by the configuration provided in the play. + Existing configurations are discarded. + + This ist the default behavior. + + Args: + dict(config): Dict with the old config in 'metadata' and new config in 'config' + Kwargs: + None + Raises: + None + Returns: + dict(config): new config""" for k, v in self.config.items(): config[k] = v + return config + + def _apply_profile_configs(self): + """ Selection of the procedure: rebuild or merge + + The standard behavior is that all information not contained + in the play is discarded. + + If "merge_profile" is provides in the play and "True", then existing + configurations from the profile and new ones defined are merged. + + Args: + None + Kwargs: + None + Raises: + None + Returns: + None""" + config = self.old_profile_json.copy() + if self.module.params['merge_profile']: + config = self._merge_config(config) + else: + config = self._generate_new_config(config) + + # upload config to lxd self.client.do('PUT', '/1.0/profiles/{0}'.format(self.name), config) self.actions.append('apply_profile_configs') @@ -371,6 +481,10 @@ def main(): devices=dict( type='dict', ), + merge_profile=dict( + type='bool', + default=False + ), state=dict( choices=PROFILES_STATES, default='present'