From 9489a1ea1cc5a19184109243deb76ff646df4876 Mon Sep 17 00:00:00 2001 From: Alexei Znamensky <103110+russoz@users.noreply.github.com> Date: Thu, 21 Sep 2023 05:29:05 +1200 Subject: [PATCH] New Module: gio_mime (#7271) * gio_mime: new module * multiple changes - fixed gio_mime module utils handler detection - add message to test helper assertion - add unit test * gio mime output uses unicode smart quotes * test helper: do not use skip/xfail reason messages - break in old pytest * Update plugins/modules/gio_mime.py Co-authored-by: Felix Fontein * change exist variables to meet standards --------- Co-authored-by: Felix Fontein --- .github/BOTMETA.yml | 4 + plugins/module_utils/gio_mime.py | 32 ++++++ plugins/modules/gio_mime.py | 108 ++++++++++++++++++ tests/sanity/ignore-2.11.txt | 1 + tests/sanity/ignore-2.12.txt | 1 + tests/sanity/ignore-2.13.txt | 1 + tests/sanity/ignore-2.14.txt | 1 + tests/sanity/ignore-2.15.txt | 1 + tests/sanity/ignore-2.16.txt | 1 + tests/unit/plugins/modules/helper.py | 6 +- tests/unit/plugins/modules/test_gio_mime.py | 16 +++ tests/unit/plugins/modules/test_gio_mime.yaml | 70 ++++++++++++ 12 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 plugins/module_utils/gio_mime.py create mode 100644 plugins/modules/gio_mime.py create mode 100644 tests/unit/plugins/modules/test_gio_mime.py create mode 100644 tests/unit/plugins/modules/test_gio_mime.yaml diff --git a/.github/BOTMETA.yml b/.github/BOTMETA.yml index 4088a7cc3b..5c21e0b556 100644 --- a/.github/BOTMETA.yml +++ b/.github/BOTMETA.yml @@ -278,6 +278,8 @@ files: $module_utils/gconftool2.py: labels: gconftool2 maintainers: russoz + $module_utils/gio_mime.py: + maintainers: russoz $module_utils/gitlab.py: keywords: gitlab source_control labels: gitlab @@ -517,6 +519,8 @@ files: $modules/gem.py: labels: gem maintainers: $team_ansible_core johanwiren + $modules/gio_mime.py: + maintainers: russoz $modules/git_config.py: maintainers: djmattyg007 mgedmin $modules/github_: diff --git a/plugins/module_utils/gio_mime.py b/plugins/module_utils/gio_mime.py new file mode 100644 index 0000000000..e01709487d --- /dev/null +++ b/plugins/module_utils/gio_mime.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2022, Alexei Znamensky +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +from ansible_collections.community.general.plugins.module_utils.cmd_runner import CmdRunner, cmd_runner_fmt + + +def gio_mime_runner(module, **kwargs): + return CmdRunner( + module, + command=['gio', 'mime'], + arg_formats=dict( + mime_type=cmd_runner_fmt.as_list(), + handler=cmd_runner_fmt.as_list(), + ), + **kwargs + ) + + +def gio_mime_get(runner, mime_type): + def process(rc, out, err): + if err.startswith("No default applications for"): + return None + out = out.splitlines()[0] + return out.split()[-1] + + with runner("mime_type", output_process=process) as ctx: + return ctx.run(mime_type=mime_type) diff --git a/plugins/modules/gio_mime.py b/plugins/modules/gio_mime.py new file mode 100644 index 0000000000..27f90581ef --- /dev/null +++ b/plugins/modules/gio_mime.py @@ -0,0 +1,108 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright (c) 2022, Alexei Znamensky +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +module: gio_mime +author: + - "Alexei Znamensky (@russoz)" +short_description: Set default handler for MIME type, for applications using Gnome GIO +version_added: 7.5.0 +description: + - This module allows configuring the default handler for a specific MIME type, to be used by applications built with th Gnome GIO API. +extends_documentation_fragment: + - community.general.attributes +attributes: + check_mode: + support: full + diff_mode: + support: full +options: + mime_type: + description: + - MIME type for which a default handler will be set. + type: str + required: true + handler: + description: + - Default handler will be set for the MIME type. + type: str + required: true +notes: + - This module is a thin wrapper around the C(gio mime) command (and subcommand). + - See man gio(1) for more details. +seealso: + - name: GIO Documentation + description: Reference documentation for the GIO API.. + link: https://docs.gtk.org/gio/ +''' + +EXAMPLES = """ +- name: Set chrome as the default handler for https + community.general.gio_mime: + mime_type: x-scheme-handler/https + handler: google-chrome.desktop + register: result +""" + +RETURN = ''' + handler: + description: + - The handler set as default. + returned: success + type: str + sample: google-chrome.desktop + stdout: + description: + - The output of the C(gio) command. + returned: success + type: str + sample: Set google-chrome.desktop as the default for x-scheme-handler/https + stderr: + description: + - The error output of the C(gio) command. + returned: failure + type: str + sample: 'gio: Failed to load info for handler "never-existed.desktop"' +''' + +from ansible_collections.community.general.plugins.module_utils.module_helper import ModuleHelper +from ansible_collections.community.general.plugins.module_utils.gio_mime import gio_mime_runner, gio_mime_get + + +class GioMime(ModuleHelper): + output_params = ['handler'] + module = dict( + argument_spec=dict( + mime_type=dict(type='str', required=True), + handler=dict(type='str', required=True), + ), + supports_check_mode=True, + ) + + def __init_module__(self): + self.runner = gio_mime_runner(self.module, check_rc=True) + self.vars.set_meta("handler", initial_value=gio_mime_get(self.runner, self.vars.mime_type), diff=True, change=True) + + def __run__(self): + check_mode_return = (0, 'Module executed in check mode', '') + if self.vars.has_changed("handler"): + with self.runner.context(args_order=["mime_type", "handler"], check_mode_skip=True, check_mode_return=check_mode_return) as ctx: + rc, out, err = ctx.run() + self.vars.stdout = out + self.vars.stderr = err + if self.verbosity >= 4: + self.vars.run_info = ctx.run_info + + +def main(): + GioMime.execute() + + +if __name__ == '__main__': + main() diff --git a/tests/sanity/ignore-2.11.txt b/tests/sanity/ignore-2.11.txt index 730c978bff..964831eeed 100644 --- a/tests/sanity/ignore-2.11.txt +++ b/tests/sanity/ignore-2.11.txt @@ -26,3 +26,4 @@ plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/xfconf.py validate-modules:return-syntax-error tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.6 # django generated code tests/integration/targets/django_manage/files/base_test/simple_project/p1/manage.py compile-2.7 # django generated code +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/tests/sanity/ignore-2.12.txt b/tests/sanity/ignore-2.12.txt index 801a80d592..d082211c90 100644 --- a/tests/sanity/ignore-2.12.txt +++ b/tests/sanity/ignore-2.12.txt @@ -19,3 +19,4 @@ plugins/modules/rax.py use-argspec-type-path plugins/modules/read_csv.py validate-modules:invalid-documentation plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/tests/sanity/ignore-2.13.txt b/tests/sanity/ignore-2.13.txt index e5799d312b..8dae95713c 100644 --- a/tests/sanity/ignore-2.13.txt +++ b/tests/sanity/ignore-2.13.txt @@ -21,3 +21,4 @@ plugins/modules/rax.py use-argspec-type-path plugins/modules/read_csv.py validate-modules:invalid-documentation plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/tests/sanity/ignore-2.14.txt b/tests/sanity/ignore-2.14.txt index 390270caa1..135d5260e5 100644 --- a/tests/sanity/ignore-2.14.txt +++ b/tests/sanity/ignore-2.14.txt @@ -23,3 +23,4 @@ plugins/modules/read_csv.py validate-modules:invalid-documentation plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/tests/sanity/ignore-2.15.txt b/tests/sanity/ignore-2.15.txt index f94c184eed..ebb9af9aeb 100644 --- a/tests/sanity/ignore-2.15.txt +++ b/tests/sanity/ignore-2.15.txt @@ -20,3 +20,4 @@ plugins/modules/rax.py use-argspec-type-path plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/tests/sanity/ignore-2.16.txt b/tests/sanity/ignore-2.16.txt index a364d8dc0f..fb940fbb51 100644 --- a/tests/sanity/ignore-2.16.txt +++ b/tests/sanity/ignore-2.16.txt @@ -21,3 +21,4 @@ plugins/modules/rhevm.py validate-modules:parameter-state-invalid-choice plugins/modules/udm_user.py import-3.11 # Uses deprecated stdlib library 'crypt' plugins/modules/udm_user.py import-3.12 # Uses deprecated stdlib library 'crypt' plugins/modules/xfconf.py validate-modules:return-syntax-error +tests/unit/plugins/modules/test_gio_mime.yaml no-smart-quotes diff --git a/tests/unit/plugins/modules/helper.py b/tests/unit/plugins/modules/helper.py index 94ccfb4f38..db9ebe3981 100644 --- a/tests/unit/plugins/modules/helper.py +++ b/tests/unit/plugins/modules/helper.py @@ -51,9 +51,9 @@ class _BaseContext(object): test_flags = self.test_flags() if test_flags.get("skip"): - pytest.skip(reason=test_flags["skip"]) + pytest.skip() if test_flags.get("xfail"): - pytest.xfail(reason=test_flags["xfail"]) + pytest.xfail() func() @@ -90,7 +90,7 @@ class _RunCmdContext(_BaseContext): print("call args list =\n%s" % call_args_list) print("expected args list =\n%s" % expected_call_args_list) - assert self.mock_run_cmd.call_count == len(self.run_cmd_calls) + assert self.mock_run_cmd.call_count == len(self.run_cmd_calls), "{0} != {1}".format(self.mock_run_cmd.call_count, len(self.run_cmd_calls)) if self.mock_run_cmd.call_count: assert call_args_list == expected_call_args_list diff --git a/tests/unit/plugins/modules/test_gio_mime.py b/tests/unit/plugins/modules/test_gio_mime.py new file mode 100644 index 0000000000..664a4290c8 --- /dev/null +++ b/tests/unit/plugins/modules/test_gio_mime.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Alexei Znamensky (russoz@gmail.com) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible_collections.community.general.plugins.modules import gio_mime +from .helper import Helper + + +helper = Helper.from_file(gio_mime.main, "tests/unit/plugins/modules/test_gio_mime.yaml") +patch_bin = helper.cmd_fixture +test_module = helper.test_module diff --git a/tests/unit/plugins/modules/test_gio_mime.yaml b/tests/unit/plugins/modules/test_gio_mime.yaml new file mode 100644 index 0000000000..d9e47a60ea --- /dev/null +++ b/tests/unit/plugins/modules/test_gio_mime.yaml @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# Copyright (c) Alexei Znamensky (russoz@gmail.com) +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +--- +- id: test_set_handler + input: + handler: google-chrome.desktop + mime_type: x-scheme-handler/http + output: + handler: google-chrome.desktop + changed: true + run_command_calls: + - command: [/testbin/gio, mime, x-scheme-handler/http] + environ: &env-def {environ_update: {LANGUAGE: C, LC_ALL: C}, check_rc: true} + rc: 0 + out: "" + err: > + No default applications for “x-scheme-handler/http” + - command: [/testbin/gio, mime, x-scheme-handler/http, google-chrome.desktop] + environ: *env-def + rc: 0 + out: "Set google-chrome.desktop as the default for x-scheme-handler/http\n" + err: "" +- id: test_set_handler_check + input: + handler: google-chrome.desktop + mime_type: x-scheme-handler/http + output: + handler: google-chrome.desktop + changed: true + flags: + skip: test helper does not support check mode yet + run_command_calls: + - command: [/testbin/gio, mime, x-scheme-handler/http] + environ: *env-def + rc: 0 + out: "" + err: > + No default applications for “x-scheme-handler/http” + - command: [/testbin/gio, mime, x-scheme-handler/http, google-chrome.desktop] + environ: *env-def + rc: 0 + out: "Set google-chrome.desktop as the default for x-scheme-handler/http\n" + err: "" +- id: test_set_handler_idempot + input: + handler: google-chrome.desktop + mime_type: x-scheme-handler/http + output: + handler: google-chrome.desktop + changed: false + run_command_calls: + - command: [/testbin/gio, mime, x-scheme-handler/http] + environ: *env-def + rc: 0 + out: | + Default application for “x-scheme-handler/https”: google-chrome.desktop + Registered applications: + brave-browser.desktop + firefox.desktop + google-chrome.desktop + firefox_firefox.desktop + Recommended applications: + brave-browser.desktop + firefox.desktop + google-chrome.desktop + firefox_firefox.desktop + err: ""