def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()

        self.state = self.module.params['state']

        self.timeout = self.module.params['timeout']
        self.wait_for_ipv4_addresses = self.module.params[
            'wait_for_ipv4_addresses']
        self.wait_for_ipv4_interfaces = self.module.params[
            'wait_for_ipv4_interfaces']
        self.force_stop = self.module.params['force_stop']
        self.addresses = None
        self.target = self.module.params['target']

        self.type = self.module.params['type']

        # LXD Rest API provides additional endpoints for creating containers and virtual-machines.
        self.api_endpoint = None
        if self.type == 'container':
            self.api_endpoint = '/1.0/containers'
        elif self.type == 'virtual-machine':
            self.api_endpoint = '/1.0/virtual-machines'

        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'])
        self.cert_file = self.module.params.get('client_cert')
        if self.cert_file is None:
            self.cert_file = '{0}/.config/lxc/client.crt'.format(
                os.environ['HOME'])
        self.debug = self.module._verbosity >= 4

        try:
            if self.module.params['url'] != ANSIBLE_LXD_DEFAULT_URL:
                self.url = self.module.params['url']
            elif os.path.exists(self.module.params['snap_url'].replace(
                    'unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(self.url,
                                    key_file=self.key_file,
                                    cert_file=self.cert_file,
                                    debug=self.debug)
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []
Ejemplo n.º 2
0
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()
        self.state = self.module.params['state']
        self.new_name = self.module.params.get('new_name', None)

        self.key_file = self.module.params.get('client_key', None)
        self.cert_file = self.module.params.get('client_cert', None)
        self.debug = self.module._verbosity >= 4

        try:
            if self.module.params['url'] != ANSIBLE_LXD_DEFAULT_URL:
                self.url = self.module.params['url']
            elif os.path.exists(self.module.params['snap_url'].replace('unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(
                self.url, key_file=self.key_file, cert_file=self.cert_file,
                debug=self.debug
            )
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []
Ejemplo n.º 3
0
    def _connect_to_socket(self):
        """connect to lxd socket

        Connect to lxd socket by provided url or defaults

        Args:
            None
        Kwargs:
            None
        Raises:
            AnsibleError
        Returns:
            None"""
        error_storage = {}
        url_list = [
            self.get_option('url'), self.SNAP_SOCKET_URL, self.SOCKET_URL
        ]
        urls = (url for url in url_list if self.validate_url(url))
        for url in urls:
            try:
                socket_connection = LXDClient(url, self.client_key,
                                              self.client_cert, self.debug)
                return socket_connection
            except LXDClientException as err:
                error_storage[url] = err
        raise AnsibleError('No connection to the socket: {0}'.format(
            to_native(error_storage)))
Ejemplo n.º 4
0
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()

        self.state = self.module.params['state']

        self.timeout = self.module.params['timeout']
        self.wait_for_ipv4_addresses = self.module.params[
            'wait_for_ipv4_addresses']
        self.force_stop = self.module.params['force_stop']
        self.addresses = None

        self.key_file = self.module.params.get('client_key', None)
        self.cert_file = self.module.params.get('client_cert', None)
        self.debug = self.module._verbosity >= 4

        try:
            if os.path.exists(self.module.params['snap_url'].replace(
                    'unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(self.url,
                                    key_file=self.key_file,
                                    cert_file=self.cert_file,
                                    debug=self.debug)
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []
Ejemplo n.º 5
0
class LXDContainerManagement(object):
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()

        self.state = self.module.params['state']

        self.timeout = self.module.params['timeout']
        self.wait_for_ipv4_addresses = self.module.params[
            'wait_for_ipv4_addresses']
        self.force_stop = self.module.params['force_stop']
        self.addresses = None

        self.key_file = self.module.params.get('client_key', None)
        self.cert_file = self.module.params.get('client_cert', None)
        self.debug = self.module._verbosity >= 4

        try:
            if os.path.exists(self.module.params['snap_url'].replace(
                    'unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(self.url,
                                    key_file=self.key_file,
                                    cert_file=self.cert_file,
                                    debug=self.debug)
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_container_json(self):
        return self.client.do('GET',
                              '/1.0/containers/{0}'.format(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),
                              ok_error_codes=[404])

    @staticmethod
    def _container_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return ANSIBLE_LXD_STATES[resp_json['metadata']['status']]

    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)

    def _create_container(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/containers', config)
        self.actions.append('create')

    def _start_container(self):
        self._change_state('start')
        self.actions.append('start')

    def _stop_container(self):
        self._change_state('stop', self.force_stop)
        self.actions.append('stop')

    def _restart_container(self):
        self._change_state('restart', self.force_stop)
        self.actions.append('restart')

    def _delete_container(self):
        self.client.do('DELETE', '/1.0/containers/{0}'.format(self.name))
        self.actions.append('delete')

    def _freeze_container(self):
        self._change_state('freeze')
        self.actions.append('freeze')

    def _unfreeze_container(self):
        self._change_state('unfreeze')
        self.actions.append('unfreez')

    def _container_ipv4_addresses(self, ignore_devices=None):
        ignore_devices = ['lo'] if ignore_devices is None else ignore_devices

        resp_json = self._get_container_state_json()
        network = resp_json['metadata']['network'] or {}
        network = dict(
            (k, v)
            for k, v in network.items() if k not in ignore_devices) or {}
        addresses = dict(
            (k,
             [a['address'] for a in v['addresses'] if a['family'] == 'inet'])
            for k, v in network.items()) or {}
        return addresses

    @staticmethod
    def _has_all_ipv4_addresses(addresses):
        return len(addresses) > 0 and all(
            len(v) > 0 for v in addresses.values())

    def _get_addresses(self):
        try:
            due = datetime.datetime.now() + datetime.timedelta(
                seconds=self.timeout)
            while datetime.datetime.now() < due:
                time.sleep(1)
                addresses = self._container_ipv4_addresses()
                if self._has_all_ipv4_addresses(addresses):
                    self.addresses = addresses
                    return
        except LXDClientException as e:
            e.msg = 'timeout for getting IPv4 addresses'
            raise

    def _started(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
        else:
            if self.old_state == 'frozen':
                self._unfreeze_container()
            elif self.old_state == 'stopped':
                self._start_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
        if self.wait_for_ipv4_addresses:
            self._get_addresses()

    def _stopped(self):
        if self.old_state == 'absent':
            self._create_container()
        else:
            if self.old_state == 'stopped':
                if self._needs_to_apply_container_configs():
                    self._start_container()
                    self._apply_container_configs()
                    self._stop_container()
            else:
                if self.old_state == 'frozen':
                    self._unfreeze_container()
                if self._needs_to_apply_container_configs():
                    self._apply_container_configs()
                self._stop_container()

    def _restarted(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
        else:
            if self.old_state == 'frozen':
                self._unfreeze_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
            self._restart_container()
        if self.wait_for_ipv4_addresses:
            self._get_addresses()

    def _destroyed(self):
        if self.old_state != 'absent':
            if self.old_state == 'frozen':
                self._unfreeze_container()
            if self.old_state != 'stopped':
                self._stop_container()
            self._delete_container()

    def _frozen(self):
        if self.old_state == 'absent':
            self._create_container()
            self._start_container()
            self._freeze_container()
        else:
            if self.old_state == 'stopped':
                self._start_container()
            if self._needs_to_apply_container_configs():
                self._apply_container_configs()
            self._freeze_container()

    def _needs_to_change_container_config(self, key):
        if key not in self.config:
            return False
        if key == 'config':
            old_configs = dict(
                (k, v)
                for k, v in self.old_container_json['metadata'][key].items()
                if not k.startswith('volatile.'))
            for k, v in self.config['config'].items():
                if k not in old_configs:
                    return True
                if old_configs[k] != v:
                    return True
            return False
        else:
            old_configs = self.old_container_json['metadata'][key]
            return self.config[key] != old_configs

    def _needs_to_apply_container_configs(self):
        return (self._needs_to_change_container_config('architecture')
                or 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'))

    def _apply_container_configs(self):
        old_metadata = self.old_container_json['metadata']
        body_json = {
            'architecture': old_metadata['architecture'],
            'config': old_metadata['config'],
            'devices': old_metadata['devices'],
            'profiles': old_metadata['profiles']
        }
        if self._needs_to_change_container_config('architecture'):
            body_json['architecture'] = self.config['architecture']
        if self._needs_to_change_container_config('config'):
            for k, v in self.config['config'].items():
                body_json['config'][k] = v
        if self._needs_to_change_container_config('ephemeral'):
            body_json['ephemeral'] = self.config['ephemeral']
        if self._needs_to_change_container_config('devices'):
            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)
        self.actions.append('apply_container_configs')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_container_json = self._get_container_json()
            self.old_state = self._container_json_to_module_state(
                self.old_container_json)
            action = getattr(self, LXD_ANSIBLE_STATES[self.state])
            action()

            state_changed = len(self.actions) > 0
            result_json = {
                'log_verbosity': self.module._verbosity,
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            if self.addresses is not None:
                result_json['addresses'] = self.addresses
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)
Ejemplo n.º 6
0
class LXDProfileManagement(object):
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()
        self.state = self.module.params['state']
        self.new_name = self.module.params.get('new_name', None)

        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'])
        self.cert_file = self.module.params.get('client_cert')
        if self.cert_file is None:
            self.cert_file = '{0}/.config/lxc/client.crt'.format(os.environ['HOME'])
        self.debug = self.module._verbosity >= 4

        try:
            if self.module.params['url'] != ANSIBLE_LXD_DEFAULT_URL:
                self.url = self.module.params['url']
            elif os.path.exists(self.module.params['snap_url'].replace('unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(
                self.url, key_file=self.key_file, cert_file=self.cert_file,
                debug=self.debug
            )
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_profile_json(self):
        return self.client.do(
            'GET', '/1.0/profiles/{0}'.format(self.name),
            ok_error_codes=[404]
        )

    @staticmethod
    def _profile_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return 'present'

    def _update_profile(self):
        if self.state == 'present':
            if self.old_state == 'absent':
                if self.new_name is None:
                    self._create_profile()
                else:
                    self.module.fail_json(
                        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:
                    self._rename_profile()
                if self._needs_to_apply_profile_configs():
                    self._apply_profile_configs()
        elif self.state == 'absent':
            if self.old_state == 'present':
                if self.new_name is None:
                    self._delete_profile()
                else:
                    self.module.fail_json(
                        msg='new_name must not be set when the profile exists and the specified state is absent',
                        changed=False)

    def _create_profile(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/profiles', config)
        self.actions.append('create')

    def _rename_profile(self):
        config = {'name': self.new_name}
        self.client.do('POST', '/1.0/profiles/{0}'.format(self.name), config)
        self.actions.append('rename')
        self.name = self.new_name

    def _needs_to_change_profile_config(self, key):
        if key not in self.config:
            return False
        old_configs = self.old_profile_json['metadata'].get(key, None)
        return self.config[key] != old_configs

    def _needs_to_apply_profile_configs(self):
        return (
            self._needs_to_change_profile_config('config') or
            self._needs_to_change_profile_config('description') or
            self._needs_to_change_profile_config('devices')
        )

    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')

    def _delete_profile(self):
        self.client.do('DELETE', '/1.0/profiles/{0}'.format(self.name))
        self.actions.append('delete')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_profile_json = self._get_profile_json()
            self.old_state = self._profile_json_to_module_state(self.old_profile_json)
            self._update_profile()

            state_changed = len(self.actions) > 0
            result_json = {
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)
Ejemplo n.º 7
0
class LXDProfileManagement(object):
    def __init__(self, module):
        """Management of LXC containers via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()
        self.state = self.module.params['state']
        self.new_name = self.module.params.get('new_name', None)

        self.key_file = self.module.params.get('client_key', None)
        self.cert_file = self.module.params.get('client_cert', None)
        self.debug = self.module._verbosity >= 4

        try:
            if self.module.params['url'] != ANSIBLE_LXD_DEFAULT_URL:
                self.url = self.module.params['url']
            elif os.path.exists(self.module.params['snap_url'].replace(
                    'unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(self.url,
                                    key_file=self.key_file,
                                    cert_file=self.cert_file,
                                    debug=self.debug)
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_profile_json(self):
        return self.client.do('GET',
                              '/1.0/profiles/{0}'.format(self.name),
                              ok_error_codes=[404])

    @staticmethod
    def _profile_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return 'present'

    def _update_profile(self):
        if self.state == 'present':
            if self.old_state == 'absent':
                if self.new_name is None:
                    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',
                        changed=False)
            else:
                if self.new_name is not None and self.new_name != self.name:
                    self._rename_profile()
                if self._needs_to_apply_profile_configs():
                    self._apply_profile_configs()
        elif self.state == 'absent':
            if self.old_state == 'present':
                if self.new_name is None:
                    self._delete_profile()
                else:
                    self.module.fail_json(
                        msg=
                        'new_name must not be set when the profile exists and the specified state is absent',
                        changed=False)

    def _create_profile(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/profiles', config)
        self.actions.append('create')

    def _rename_profile(self):
        config = {'name': self.new_name}
        self.client.do('POST', '/1.0/profiles/{0}'.format(self.name), config)
        self.actions.append('rename')
        self.name = self.new_name

    def _needs_to_change_profile_config(self, key):
        if key not in self.config:
            return False
        old_configs = self.old_profile_json['metadata'].get(key, None)
        return self.config[key] != old_configs

    def _needs_to_apply_profile_configs(self):
        return (self._needs_to_change_profile_config('config')
                or self._needs_to_change_profile_config('description')
                or self._needs_to_change_profile_config('devices'))

    def _apply_profile_configs(self):
        config = self.old_profile_json.copy()
        for k, v in self.config.items():
            config[k] = v
        self.client.do('PUT', '/1.0/profiles/{0}'.format(self.name), config)
        self.actions.append('apply_profile_configs')

    def _delete_profile(self):
        self.client.do('DELETE', '/1.0/profiles/{0}'.format(self.name))
        self.actions.append('delete')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_profile_json = self._get_profile_json()
            self.old_state = self._profile_json_to_module_state(
                self.old_profile_json)
            self._update_profile()

            state_changed = len(self.actions) > 0
            result_json = {
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)
Ejemplo n.º 8
0
class LXDProjectManagement(object):
    def __init__(self, module):
        """Management of LXC projects via Ansible.

        :param module: Processed Ansible Module.
        :type module: ``object``
        """
        self.module = module
        self.name = self.module.params['name']
        self._build_config()
        self.state = self.module.params['state']
        self.new_name = self.module.params.get('new_name', None)

        self.key_file = self.module.params.get('client_key')
        if self.key_file is None:
            self.key_file = os.path.expanduser('~/.config/lxc/client.key')
        self.cert_file = self.module.params.get('client_cert')
        if self.cert_file is None:
            self.cert_file = os.path.expanduser('~/.config/lxc/client.crt')
        self.debug = self.module._verbosity >= 4

        try:
            if self.module.params['url'] != ANSIBLE_LXD_DEFAULT_URL:
                self.url = self.module.params['url']
            elif os.path.exists(self.module.params['snap_url'].replace('unix:', '')):
                self.url = self.module.params['snap_url']
            else:
                self.url = self.module.params['url']
        except Exception as e:
            self.module.fail_json(msg=e.msg)

        try:
            self.client = LXDClient(
                self.url, key_file=self.key_file, cert_file=self.cert_file,
                debug=self.debug
            )
        except LXDClientException as e:
            self.module.fail_json(msg=e.msg)
        self.trust_password = self.module.params.get('trust_password', None)
        self.actions = []

    def _build_config(self):
        self.config = {}
        for attr in CONFIG_PARAMS:
            param_val = self.module.params.get(attr, None)
            if param_val is not None:
                self.config[attr] = param_val

    def _get_project_json(self):
        return self.client.do(
            'GET', '/1.0/projects/{0}'.format(self.name),
            ok_error_codes=[404]
        )

    @staticmethod
    def _project_json_to_module_state(resp_json):
        if resp_json['type'] == 'error':
            return 'absent'
        return 'present'

    def _update_project(self):
        if self.state == 'present':
            if self.old_state == 'absent':
                if self.new_name is None:
                    self._create_project()
                else:
                    self.module.fail_json(
                        msg='new_name must not be set when the project does not exist and the state is present',
                        changed=False)
            else:
                if self.new_name is not None and self.new_name != self.name:
                    self._rename_project()
                if self._needs_to_apply_project_configs():
                    self._apply_project_configs()
        elif self.state == 'absent':
            if self.old_state == 'present':
                if self.new_name is None:
                    self._delete_project()
                else:
                    self.module.fail_json(
                        msg='new_name must not be set when the project exists and the specified state is absent',
                        changed=False)

    def _create_project(self):
        config = self.config.copy()
        config['name'] = self.name
        self.client.do('POST', '/1.0/projects', config)
        self.actions.append('create')

    def _rename_project(self):
        config = {'name': self.new_name}
        self.client.do('POST', '/1.0/projects/{0}'.format(self.name), config)
        self.actions.append('rename')
        self.name = self.new_name

    def _needs_to_change_project_config(self, key):
        if key not in self.config:
            return False
        old_configs = self.old_project_json['metadata'].get(key, None)
        return self.config[key] != old_configs

    def _needs_to_apply_project_configs(self):
        return (
            self._needs_to_change_project_config('config') or
            self._needs_to_change_project_config('description')
        )

    def _merge_dicts(self, source, destination):
        """ Return a new dict taht merge two dict,
        with values in source dict overwrite destination dict

        Args:
            dict(source): source dict
            dict(destination): destination dict
        Kwargs:
            None
        Raises:
            None
        Returns:
            dict(destination): merged dict"""
        result = destination.copy()
        for key, value in source.items():
            if isinstance(value, dict):
                # get node or create one
                node = result.setdefault(key, {})
                self._merge_dicts(value, node)
            else:
                result[key] = value
        return result

    def _apply_project_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_project" is provides in the play and "True", then existing
        configurations from the project and new ones defined are merged.

        Args:
            None
        Kwargs:
            None
        Raises:
            None
        Returns:
            None"""
        old_config = dict()
        old_metadata = self.old_project_json['metadata'].copy()
        for attr in CONFIG_PARAMS:
            old_config[attr] = old_metadata[attr]

        if self.module.params['merge_project']:
            config = self._merge_dicts(self.config, old_config)
            if config == old_config:
                # no need to call api if merged config is the same
                # as old config
                return
        else:
            config = self.config.copy()
        # upload config to lxd
        self.client.do('PUT', '/1.0/projects/{0}'.format(self.name), config)
        self.actions.append('apply_projects_configs')

    def _delete_project(self):
        self.client.do('DELETE', '/1.0/projects/{0}'.format(self.name))
        self.actions.append('delete')

    def run(self):
        """Run the main method."""

        try:
            if self.trust_password is not None:
                self.client.authenticate(self.trust_password)

            self.old_project_json = self._get_project_json()
            self.old_state = self._project_json_to_module_state(
                self.old_project_json)
            self._update_project()

            state_changed = len(self.actions) > 0
            result_json = {
                'changed': state_changed,
                'old_state': self.old_state,
                'actions': self.actions
            }
            if self.client.debug:
                result_json['logs'] = self.client.logs
            self.module.exit_json(**result_json)
        except LXDClientException as e:
            state_changed = len(self.actions) > 0
            fail_params = {
                'msg': e.msg,
                'changed': state_changed,
                'actions': self.actions
            }
            if self.client.debug:
                fail_params['logs'] = e.kwargs['logs']
            self.module.fail_json(**fail_params)