Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelogs/fragments/10432-dconf-load-subpath.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- dconf - adds support of ``load`` to populate a subpath from file (https://github.com/ansible-collections/community.general/pull/10432).
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- dconf - adds support of ``load`` to populate a subpath from file (https://github.com/ansible-collections/community.general/pull/10432).
- dconf - allow to populate a subpath from a file (https://github.com/ansible-collections/community.general/pull/10432).

108 changes: 100 additions & 8 deletions plugins/modules/dconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@
description:
- Value to set for the specified dconf key. Value should be specified in GVariant format. Due to complexity of this
format, it is best to have a look at existing values in the dconf database.
- Required for O(state=present).
- One of O(value) and O(path) are required for O(state=present).
- Although the type is specified as "raw", it should typically be specified as a string. However, boolean values in
particular are handled properly even when specified as booleans rather than strings (in fact, handling booleans properly
is why the type of this parameter is "raw").
path:
type: path
description:
- Remote path to the configuration to apply.
- One of O(value) and O(path) are required for O(state=present).

state:
type: str
required: false
Expand Down Expand Up @@ -122,12 +128,20 @@
key: "/org/cinnamon/desktop-effects"
value: "false"
state: present

- name: Load terminal profile in Gnome
community.general.dconf:
key: "/org/gnome/terminal/legacy/profiles/:"
path: "/tmp/solarized_dark.dump"
state: present
"""


import os
import sys

from ansible.module_utils.six.moves.configparser import ConfigParser

from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.respawn import (
has_respawned,
Expand Down Expand Up @@ -224,7 +238,7 @@ def _get_existing_dbus_session(self):

return None

def run_command(self, command):
def run_command(self, command, data=None):
"""
Runs the specified command within a functional D-Bus session. Command is
effectively passed-on to AnsibleModule.run_command() method, with
Expand All @@ -233,19 +247,21 @@ def run_command(self, command):
:param command: Command to run, including parameters. Each element of the list should be a string.
:type module: list

:kw data: If given, information to write to the stdin of the command

:returns: tuple(result_code, standard_output, standard_error) -- Result code, standard output, and standard error from running the command.
"""

if self.dbus_session_bus_address is None:
self.module.debug("Using dbus-run-session wrapper for running commands.")
command = [self.dbus_run_session_cmd] + command
rc, out, err = self.module.run_command(command)
rc, out, err = self.module.run_command(command, data=data)

if self.dbus_session_bus_address is None and rc == 127:
self.module.fail_json(msg="Failed to run passed-in command, dbus-run-session faced an internal error: %s" % err)
else:
extra_environment = {'DBUS_SESSION_BUS_ADDRESS': self.dbus_session_bus_address}
rc, out, err = self.module.run_command(command, environ_update=extra_environment)
rc, out, err = self.module.run_command(command, data=data, environ_update=extra_environment)

return rc, out, err

Expand Down Expand Up @@ -390,6 +406,71 @@ def reset(self, key):
# Value was changed.
return True

def load(self, key, path):
"""
Load the config file in specified path.

if an error occurs, a call will be made to AnsibleModule.fail_json.

:param key: dconf directory for which the config should be set.
:type key: str

:param path: Remote configuration path to set for the specified dconf path.
:type value: str

:returns: bool -- True if a change was made, False if no change was required.
"""
def _create_dconf_key(root, sub_dir, key):
# Root should end with '/'
if sub_dir == "/":
# No sub directory
return "%s%s" % (root, key)
return "%s%s/%s" % (root, sub_dir, key)

# Ensure key refers to a directory, as required by dconf
root_dir = key
if not root_dir.endswith('/'):
root_dir += '/'

# Read config to check if change is needed and passing to command line
try:
with open(path, 'r') as fd:
raw_config = fd.read()
except IOError as ex:
self.module.fail_json(msg='Failed while reading configuration file %s with error: %s' % (path, ex))

# Parse configuration file
config = ConfigParser()
try:
config.read_string(raw_config)
except Exception as e:
self.module.fail_json(msg='Failed while parsing config with error: %s' % e)

# For each sub-directory, check if at least one change is needed
changed = any(
not self.variants_are_equal(self.read(_create_dconf_key(root_dir, sub_dir, k)), v)
for sub_dir in config.sections()
for k, v in config[sub_dir].items()
)

if self.check_mode or not changed:
return changed

# Set-up command to run. Since DBus is needed for write operation, wrap
# dconf command dbus-launch.
command = [self.dconf_bin, 'load', root_dir]

# Run the command and fetch standard return code, stdout, and stderr.
dbus_wrapper = DBusWrapper(self.module)
rc, out, err = dbus_wrapper.run_command(command, data=raw_config)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens to keys that already exist, but are not part of raw_config? Are these kept, or removed?

If the latter, the changed check above is not correct.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If keys exist in the database but, are not part of raw_config then they're kept. changed check should be correct!


if rc != 0:
self.module.fail_json(msg='dconf failed while load config %s, root dir %s with error: %s' % (path, root_dir, err),
out=out,
err=err)
# Value was changed.
return changed


def main():
# Setup the Ansible module
Expand All @@ -398,11 +479,15 @@ def main():
state=dict(default='present', choices=['present', 'absent', 'read']),
key=dict(required=True, type='str', no_log=False),
# Converted to str below after special handling of bool.
value=dict(required=False, default=None, type='raw'),
value=dict(type='raw'),
path=dict(type='path'),
),
supports_check_mode=True,
required_if=[
('state', 'present', ['value']),
('state', 'present', ['value', 'path'], True),
],
mutually_exclusive=[
['value', 'path'],
],
)

Expand Down Expand Up @@ -459,11 +544,18 @@ def main():

# Process based on different states.
if module.params['state'] == 'read':
# TODO: Handle this case when 'state=present' and 'key' is the only one
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what this comment means. Can you please explain it?

value = dconf.read(module.params['key'])
module.exit_json(changed=False, value=value)
elif module.params['state'] == 'present':
changed = dconf.write(module.params['key'], module.params['value'])
module.exit_json(changed=changed)
if module.params['path']:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if module.params['path']:
if module.params['path'] is not None:

# Use 'dconf load' to propagate multiple entries from the root given by 'key'
changed = dconf.load(module.params['key'], module.params['path'])
module.exit_json(changed=changed)
elif module.params['value']:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
elif module.params['value']:
else:

# Use 'dconf write' to modify the key
changed = dconf.write(module.params['key'], module.params['value'])
module.exit_json(changed=changed)
elif module.params['state'] == 'absent':
changed = dconf.reset(module.params['key'])
module.exit_json(changed=changed)
Expand Down