-
Notifications
You must be signed in to change notification settings - Fork 1.7k
dconf: add feature to populate a subpath from file #10432
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 18 commits
29b11f0
7443fe4
b840120
63f22d3
6f8ef18
266b62b
84df105
3f37370
840fbf8
a7dd03f
fffdfb4
8bdcdc8
d860986
bc32bd4
236edae
7e3f675
abba302
6e10135
8177c0a
07a6c79
588eb3c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -61,10 +61,17 @@ | |||||
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 | ||||||
required: false | ||||||
Sh3idan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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 | ||||||
|
@@ -122,12 +129,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, | ||||||
|
@@ -224,7 +239,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 | ||||||
|
@@ -233,19 +248,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 | ||||||
|
||||||
|
@@ -390,6 +407,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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What happens to keys that already exist, but are not part of If the latter, the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If keys exist in the database but, are not part of |
||||||
|
||||||
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 | ||||||
|
@@ -399,10 +481,14 @@ def main(): | |||||
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'), | ||||||
path=dict(required=False, default=None, type='path'), | ||||||
Sh3idan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
), | ||||||
supports_check_mode=True, | ||||||
required_if=[ | ||||||
('state', 'present', ['value']), | ||||||
('state', 'present', ['value', 'path'], True), | ||||||
], | ||||||
mutually_exclusive=[ | ||||||
['value', 'path'], | ||||||
], | ||||||
) | ||||||
|
||||||
|
@@ -459,11 +545,24 @@ 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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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']: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
# 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']: | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
# Use 'dconf write' to modify the key | ||||||
changed = dconf.write(module.params['key'], module.params['value']) | ||||||
module.exit_json(changed=changed) | ||||||
else: | ||||||
# NOTE: This case shouldn't happen yet as 'key' and 'path' are | ||||||
# required with 'state=present' | ||||||
# TODO: if 'key' ends with '/' then 'dconf list' should be used | ||||||
# else, 'dconf read' | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't this TODO be part of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't thing so. The architecture defined by the original author suggests that:
If As you said, Are you agree with that? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What do you mean that read is a mistake? The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi, Felix wrote the following sentence:
I understand that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Anyway, this comment has been removed to follow the guidance of russoz! |
||||||
module.fail_json(msg="'key' or 'path' must be defined.") | ||||||
Sh3idan marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
elif module.params['state'] == 'absent': | ||||||
changed = dconf.reset(module.params['key']) | ||||||
module.exit_json(changed=changed) | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.