diff --git a/plugins/module_utils/_api/context/api.py b/plugins/module_utils/_api/context/api.py index eb54e433..f7166837 100644 --- a/plugins/module_utils/_api/context/api.py +++ b/plugins/module_utils/_api/context/api.py @@ -133,7 +133,7 @@ class ContextAPI(object): (Context): List of context objects. Raises: :py:class:`docker.errors.APIError` - If the server returns an error. + If something goes wrong. """ names = [] for dirname, dummy, fnames in os.walk(get_meta_dir()): @@ -154,7 +154,10 @@ class ContextAPI(object): contexts = [cls.get_default_context()] for name in names: - contexts.append(Context.load_context(name)) + context = Context.load_context(name) + if not context: + raise errors.ContextException("Context {context} cannot be found".format(context=name)) + contexts.append(context) return contexts @classmethod diff --git a/plugins/module_utils/_api/context/config.py b/plugins/module_utils/_api/context/config.py index f339d2b1..77b2e01f 100644 --- a/plugins/module_utils/_api/context/config.py +++ b/plugins/module_utils/_api/context/config.py @@ -21,20 +21,23 @@ from ..utils.utils import parse_host METAFILE = "meta.json" -def get_current_context_name(): - name = "default" +def get_current_context_name_with_source(): if os.environ.get('DOCKER_HOST'): - return name + return "default", "DOCKER_HOST environment variable set" if os.environ.get('DOCKER_CONTEXT'): - return os.environ['DOCKER_CONTEXT'] + return os.environ['DOCKER_CONTEXT'], "DOCKER_CONTEXT environment variable set" docker_cfg_path = find_config_file() if docker_cfg_path: try: with open(docker_cfg_path) as f: - name = json.load(f).get("currentContext", "default") + return json.load(f).get("currentContext", "default"), "configuration file {file}".format(file=docker_cfg_path) except Exception: - return "default" - return name + pass + return "default", "fallback value" + + +def get_current_context_name(): + return get_current_context_name_with_source()[0] def write_context_name_to_docker_config(name=None): diff --git a/plugins/modules/docker_context_info.py b/plugins/modules/docker_context_info.py index 4166c0a5..c7a3fcb0 100644 --- a/plugins/modules/docker_context_info.py +++ b/plugins/modules/docker_context_info.py @@ -14,7 +14,9 @@ module: docker_context_info short_description: Retrieve information on Docker contexts for the current user description: - - Essentially returns the output of C(docker context ls --format json). + - Return information on Docker contexts. + - This includes some generic information, as well as a RV(contexts[].config) dictionary that can be used for module defaults for all community.docker modules + that use the C(community.docker.docker) module defaults group. extends_documentation_fragment: - community.docker.attributes - community.docker.attributes.info_module @@ -24,17 +26,22 @@ options: only_current: description: - If set to V(true), RV(contexts) will just contain the current context and none else. - - If set to V(false), RV(contexts) will list all contexts. + - If set to V(false) (default), RV(contexts) will list all contexts, unless O(name) is specified. + - Mutually exclusive to O(name). type: bool default: false + name: + description: + - A specific Docker CLI context to query. + - The module will fail if this context does not exist. If you simply want to query whether a context exists, + do not specify this parameter and use Jinja2 to search the resulting list for a context of the given name instead. + - Mutually exclusive with O(only_current). + type: str cli_context: description: - - The Docker CLI context to use. - - If set, will ignore the E(DOCKER_HOST) and E(DOCKER_CONTEXT) environment variables and the user's Docker config. - - If not set, the module will follow Docker CLI's precedence and uses E(DOCKER_HOST) if set; - if not, uses E(DOCKER_CONTEXT) if set; - if not, uses the current context from the Docker config; - if not set, uses C(default). + - Override for the default context's name. + - This is preferably used for context selection when O(only_current=true), + and it is used to compute the return values RV(contexts[].current) and RV(current_context_name). type: str author: @@ -63,13 +70,15 @@ EXAMPLES = r""" community.docker.docker_container: image: ubuntu:latest name: ubuntu - state: running + state: started """ RETURN = r""" contexts: description: - - A list of all contexts (O(only_current=false)) or only the current context (O(only_current=true)). + - A list of all contexts (O(only_current=false), O(name) not specified), + only the current context (O(only_current=true)), + or the requested context (O(name) specified). type: list elements: dict returned: success @@ -149,6 +158,13 @@ contexts: type: bool returned: success, context is for Docker, and TLS config is present sample: true + +current_context_name: + description: + - The name of the current Docker context. + type: str + returned: success + sample: default """ import traceback @@ -161,12 +177,15 @@ from ansible_collections.community.docker.plugins.module_utils._api.context.api ContextAPI, ) from ansible_collections.community.docker.plugins.module_utils._api.context.config import ( - get_current_context_name, + get_current_context_name_with_source, ) from ansible_collections.community.docker.plugins.module_utils._api.context.context import ( IN_MEMORY, ) -from ansible_collections.community.docker.plugins.module_utils._api.errors import DockerException +from ansible_collections.community.docker.plugins.module_utils._api.errors import ( + ContextException, + DockerException, +) def tls_context_to_json(context): @@ -230,28 +249,51 @@ def context_to_json(context, current): def main(): argument_spec = dict( only_current=dict(type='bool', default=False), + name=dict(type='str'), cli_context=dict(type='str'), ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, + mutually_exclusive=[ + ("only_current", "name"), + ], ) try: - current_context_name = get_current_context_name() - if module.params['only_current']: - contexts = [context_to_json(ContextAPI.get_context(current_context_name), True)] + if module.params['cli_context']: + current_context_name, current_context_source = module.params['cli_context'], "cli_context module option" else: - contexts = [ - context_to_json(context, context.name == current_context_name) - for context in ContextAPI.contexts() - ] + current_context_name, current_context_source = get_current_context_name_with_source() + if module.params['name']: + contexts = [ContextAPI.get_context(module.params['name'])] + if not contexts[0]: + module.fail_json(msg="There is no context of name {name!r}".format(name=module.params['name'])) + elif module.params['only_current']: + contexts = [ContextAPI.get_context(current_context_name)] + if not contexts[0]: + module.fail_json( + msg="There is no context of name {name!r}, which is configured as the default context ({source})".format( + name=current_context_name, + source=current_context_source, + ), + ) + else: + contexts = ContextAPI.contexts() + + json_contexts = [ + context_to_json(context, context.name == current_context_name) + for context in contexts + ] module.exit_json( changed=False, - contexts=contexts, + contexts=json_contexts, + current_context_name=current_context_name, ) + except ContextException as e: + module.fail_json(msg='Error when handling Docker contexts: {0}'.format(to_native(e)), exception=traceback.format_exc()) except DockerException as e: module.fail_json(msg='An unexpected Docker error occurred: {0}'.format(to_native(e)), exception=traceback.format_exc())