# Copyright (c) 2026, Dexter Le # 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 # Note that this module util is **PRIVATE** to the collection. It can have breaking changes at any time. # Do not use this from other collections or standalone plugins/modules! from __future__ import annotations import typing as t from ansible_collections.community.general.plugins.module_utils._cmd_runner import CmdRunner, cmd_runner_fmt if t.TYPE_CHECKING: from ansible.module_utils.basic import AnsibleModule # Maps kopia_repository module state values to kopia CLI subcommands. # Used with cmd_runner_fmt.as_map() for the 'state' arg format. REPOSITORY_STATE_MAP = { "created": "create", "connected": "connect", "disconnected": "disconnect", "synced": "sync-to", "throttled": "throttle", } # Maps backend provider names to their CLI flag names. # Each provider emits its name as a positional sub-subcommand, followed by # --flag value pairs for each non-None param. # The "server" provider is intentionally absent: `kopia repository connect server` # uses top-level flags (--url, --server-cert-fingerprint) rather than backend flags, # so fmt_backend() returns [] for it and those flags are passed separately. _PROVIDER_BACKEND_MAP = { "azure": { "container": "--container", "storage_account": "--storage-account", "storage_key": "--storage-key", "sas_token": "--sas-token", "storage_domain": "--storage-domain", "prefix": "--prefix", }, "b2": { "bucket": "--bucket", "access_key": "--key-id", "secret_access_key": "--key", "prefix": "--prefix", }, "filesystem": { "path": "--path", }, "gcs": { "bucket": "--bucket", "credentials_file": "--credentials-file", "prefix": "--prefix", }, "gdrive": { "folder_id": "--folder-id", "credentials_file": "--credentials-file", }, "rclone": { "path": "--remote-path", }, "s3": { "bucket": "--bucket", "access_key": "--access-key", "secret_access_key": "--secret-access-key", "endpoint": "--endpoint", "region": "--region", "prefix": "--prefix", "session_token": "--session-token", }, "sftp": { "path": "--path", "host": "--host", "username": "--username", "port": "--port", "keyfile": "--keyfile", "known_hosts": "--known-hosts", }, "webdav": { "url": "--url", "webdav_username": "--webdav-username", "webdav_password": "--webdav-password", }, } # Argument spec entries shared by all kopia modules. # Include this in each module's argument_spec via dict unpacking. KOPIA_COMMON_ARGUMENT_SPEC = dict( password=dict(type="str", no_log=True), config=dict(type="path"), ) def fmt_backend(value): """Format the backend dict into positional + flag arguments for kopia CLI. For most providers the output is: [provider_name, --flag1, value1, --flag2, value2, ...] For the "server" provider, returns [] because server connect uses top-level flags (--url, --server-cert-fingerprint) passed separately. """ provider = value["provider"] if provider == "server": return [] result = [provider] for param_name, flag in _PROVIDER_BACKEND_MAP[provider].items(): param_value = value.get(param_name) if param_value is not None: result.append(f"{flag}={param_value}") return result def _fmt_throttle(value): """Format the throttle dict into --flag value arguments for kopia repository throttle set.""" if not value: return [] flag_map = { "download_bytes_per_second": "--download-bytes-per-second", "upload_bytes_per_second": "--upload-bytes-per-second", "read_requests_per_second": "--read-requests-per-second", "write_requests_per_second": "--write-requests-per-second", "list_requests_per_second": "--list-requests-per-second", "concurrent_reads": "--concurrent-reads", "concurrent_writes": "--concurrent-writes", } result = [] for param, flag in flag_map.items(): v = value.get(param) if v is not None: result.extend([flag, str(v)]) return result def kopia_runner(module: AnsibleModule, extra_formats: dict | None = None, **kwargs) -> CmdRunner: """Create a CmdRunner for the kopia CLI. Provides arg formats for all params shared across kopia modules. Pass extra_formats to add module-specific arg formats on top of the shared ones. """ formats = dict( cli_action=cmd_runner_fmt.as_list(), status=cmd_runner_fmt.as_fixed("repository", "status"), get_throttle=cmd_runner_fmt.as_fixed("repository", "throttle", "get"), state=cmd_runner_fmt.as_map(REPOSITORY_STATE_MAP), backend=cmd_runner_fmt.as_func(fmt_backend), password=cmd_runner_fmt.as_opt_eq_val("--password"), fingerprint_tls=cmd_runner_fmt.as_opt_eq_val("--server-cert-fingerprint"), url=cmd_runner_fmt.as_opt_eq_val("--url"), config=cmd_runner_fmt.as_opt_eq_val("--config-file"), throttle_operation=cmd_runner_fmt.as_list(), throttle=cmd_runner_fmt.as_func(_fmt_throttle), ) if extra_formats: formats.update(extra_formats) return CmdRunner( module, command=["kopia"], arg_formats=formats, **kwargs, )