From b3c066b99f346812f3bb33fd16774d154bb97d0c Mon Sep 17 00:00:00 2001 From: Greg Harvey Date: Sun, 28 Dec 2025 21:03:55 +0100 Subject: [PATCH] Adding scw_profile parameter to Scaleway module utilities. (#11314) * Adding scw_profile parameter to Scaleway module utilities. * Setting param name to profile for consistency and adding scw_profile as an alias. * Adding changelog fragment. * Forgot to import 'os' library. * Type in variable type for Scaleway profile. * Also forgot to include the yaml library, code taking from plugins/inventory/scaleway.py. * Adding default 'profile' value of empty string and changing check to a length check. * Treated wrong variable, checking XDG_CONFIG_HOME is a string. * Explicitly setting default of environment path vars to empty strings instead of None. * Letting ruff reformat the dict for 'profile'. * Changes from code review. * Fixing ruff formatting issue with error message. * Properly catching PyYAML import issues. * Adding PyYAML requirement when 'profile' is used. * Ruff wants an extra line after the PyYAML import code. * Fixing PyYAML dependency code as per review. * Removing extraneous var declaration. * Moving SCW_CONFIG loading to a function. * Fixing type errors with os.getenv calls. * Cannot send None to os.path.exists() or open(). * Oops, inversed logic! * Setting os.getenv() default to empty string so it is never None. Co-authored-by: Felix Fontein * None check no longer needed as scw_config_path is never None. Co-authored-by: Felix Fontein --------- Co-authored-by: Felix Fontein --- ...11314-scaleway-scw-profile-var-modules.yml | 2 + plugins/doc_fragments/scaleway.py | 11 ++++- plugins/module_utils/scaleway.py | 47 ++++++++++++++++++- 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 changelogs/fragments/11314-scaleway-scw-profile-var-modules.yml diff --git a/changelogs/fragments/11314-scaleway-scw-profile-var-modules.yml b/changelogs/fragments/11314-scaleway-scw-profile-var-modules.yml new file mode 100644 index 0000000000..61377381cc --- /dev/null +++ b/changelogs/fragments/11314-scaleway-scw-profile-var-modules.yml @@ -0,0 +1,2 @@ +minor_changes: + - scaleway module utils - added ``scw_profile`` parameter with ``SCW_PROFILE`` environment variable support (https://github.com/ansible-collections/community.general/issues/11313, https://github.com/ansible-collections/community.general/pull/11314). diff --git a/plugins/doc_fragments/scaleway.py b/plugins/doc_fragments/scaleway.py index 1db61fbf02..a20cbb1f1a 100644 --- a/plugins/doc_fragments/scaleway.py +++ b/plugins/doc_fragments/scaleway.py @@ -12,8 +12,8 @@ options: api_token: description: - Scaleway OAuth token. + - This is required if O(profile) is not specified. type: str - required: true aliases: [oauth_token] api_url: description: @@ -27,6 +27,13 @@ options: type: int default: 30 aliases: [timeout] + profile: + description: + - The config profile in config file to load the Scaleway OAuth token from, use instead of O(api_token). + - It is also possible to set E(SCW_PROFILE) to use a SCW CLI config profile. + type: str + aliases: [scw_profile] + version_added: 12.2.0 query_parameters: description: - List of parameters passed to the query string. @@ -37,6 +44,8 @@ options: - Validate SSL certs of the Scaleway API. type: bool default: true +requirements: + - PyYAML (when O(profile) is used) notes: - Also see the API documentation on U(https://developer.scaleway.com/). - If O(api_token) is not set within the module, the following environment variables can be used in decreasing order of precedence diff --git a/plugins/module_utils/scaleway.py b/plugins/module_utils/scaleway.py index c7663d7b16..43dff7ece5 100644 --- a/plugins/module_utils/scaleway.py +++ b/plugins/module_utils/scaleway.py @@ -4,6 +4,7 @@ from __future__ import annotations +import os import json import re import sys @@ -33,11 +34,18 @@ except Exception: SCALEWAY_SECRET_IMP_ERR = traceback.format_exc() HAS_SCALEWAY_SECRET_PACKAGE = False +YAML_IMPORT_ERROR: str | None +try: + import yaml +except ImportError: + YAML_IMPORT_ERROR = traceback.format_exc() +else: + YAML_IMPORT_ERROR = None + def scaleway_argument_spec() -> dict[str, t.Any]: return dict( api_token=dict( - required=True, fallback=(env_fallback, ["SCW_TOKEN", "SCW_API_KEY", "SCW_OAUTH_TOKEN", "SCW_API_TOKEN"]), no_log=True, aliases=["oauth_token"], @@ -45,6 +53,10 @@ def scaleway_argument_spec() -> dict[str, t.Any]: api_url=dict( fallback=(env_fallback, ["SCW_API_URL"]), default="https://api.scaleway.com", aliases=["base_url"] ), + profile=dict( + fallback=(env_fallback, ["SCW_PROFILE"]), + aliases=["scw_profile"], + ), api_timeout=dict(type="int", default=30, aliases=["timeout"]), query_parameters=dict(type="dict", default={}), validate_certs=dict(default=True, type="bool"), @@ -63,6 +75,22 @@ def payload_from_object(scw_object): return {k: v for k, v in scw_object.items() if k != "id" and v is not None} +def get_scw_config_path(scw_profile: str) -> str | None: + if "SCW_CONFIG_PATH" in os.environ: + scw_config_path = os.getenv("SCW_CONFIG_PATH", "") + elif "XDG_CONFIG_HOME" in os.environ: + scw_config_path = os.path.join(os.getenv("XDG_CONFIG_HOME", ""), "scw", "config.yaml") + else: + scw_config_path = os.path.join(os.path.expanduser("~"), ".config", "scw", "config.yaml") + + if os.path.exists(scw_config_path): + with open(scw_config_path) as fh: + scw_config = yaml.safe_load(fh) + return scw_config["profiles"][scw_profile].get("secret_key") + + return None + + class ScalewayException(Exception): def __init__(self, message: str) -> None: self.message = message @@ -176,8 +204,23 @@ class Response: class Scaleway: def __init__(self, module: AnsibleModule) -> None: self.module = module + oauth_token = self.module.params.get("api_token") + scw_profile = self.module.params.get("profile") + + if scw_profile: + if YAML_IMPORT_ERROR is not None: + self.module.fail_json( + msg=missing_required_lib("PyYAML", reason="for scw_profile"), exception=YAML_IMPORT_ERROR + ) + oauth_token = get_scw_config_path(scw_profile) + + if oauth_token is None: + self.module.fail_json( + msg="Either your config profile could not be loaded or you have not provided an api_token." + ) + self.headers = { - "X-Auth-Token": self.module.params.get("api_token"), + "X-Auth-Token": oauth_token, "User-Agent": self.get_user_agent_string(module), "Content-Type": "application/json", }