community.docker/plugins/modules/docker_context_info.py
2025-02-08 20:01:10 +01:00

303 lines
10 KiB
Python

#!/usr/bin/python
#
# Copyright 2025 Felix Fontein <felix@fontein.de>
# 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 = r"""
module: docker_context_info
short_description: Retrieve information on Docker contexts for the current user
description:
- 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
- community.docker.attributes.idempotent_not_modify_state
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) (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:
- 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:
- "Felix Fontein (@felixfontein)"
"""
EXAMPLES = r"""
- name: Get infos on contexts
community.docker.docker_context_info:
register: result
- name: Show all contexts
ansible.builtin.debug:
msg: "{{ result.contexts }}"
- name: Get current context
community.docker.docker_context_info:
only_current: true
register: docker_current_context
- name: Run community.docker modules with current context
module_defaults:
group/community.docker.docker: "{{ docker_current_context.contexts[0].config }}"
block:
- name: Task using the current context
community.docker.docker_container:
image: ubuntu:latest
name: ubuntu
state: started
"""
RETURN = r"""
contexts:
description:
- 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
contains:
current:
description:
- Whether this context is the current one.
type: bool
returned: success
sample: true
name:
description:
- The context's name.
type: bool
returned: success
sample: default
description:
description:
- The context's description, if available.
type: bool
returned: success
sample: My context
meta_path:
description:
- The path to the context's meta directory.
- Not present for RV(contexts[].name=default).
type: str
returned: success
sample: /home/felix/.docker/contexts/meta/0123456789abcdef01234567890abcdef0123456789abcdef0123456789abcde
tls_path:
description:
- The path to the context's TLS config directory.
- Not present for RV(contexts[].name=default).
type: str
returned: success
sample: /home/user/.docker/contexts/tls/0123456789abcdef01234567890abcdef0123456789abcdef0123456789abcde/
config:
description:
- In case the context is for Docker, contains option values to configure the community.docker modules to use this context.
type: dict
returned: success
sample: {}
contains:
docker_host:
description:
- The Docker daemon to connect to.
type: str
returned: success and context is for Docker
sample: unix:///var/run/docker.sock
tls:
description:
- Whether the Docker context should use an unvalidated TLS connection.
type: bool
returned: success and context is for Docker
sample: false
ca_path:
description:
- The CA certificate used to validate the Docker daemon's certificate.
type: bool
returned: success, context is for Docker, TLS config is present, and CA cert is present
sample: /path/to/ca-cert.pem
client_cert:
description:
- The client certificate to authenticate with to the Docker daemon.
type: bool
returned: success, context is for Docker, TLS config is present, and client cert info is present
sample: /path/to/client-cert.pem
client_key:
description:
- The client certificate's key to authenticate with to the Docker daemon.
type: bool
returned: success, context is for Docker, TLS config is present, and client cert info is present
sample: /path/to/client-key.pem
validate_certs:
description:
- Whether the Docker context should use a validated TLS connection.
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
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.common.text.converters import to_native, to_text
from ansible.module_utils.six import string_types
from ansible_collections.community.docker.plugins.module_utils._api.context.api import (
ContextAPI,
)
from ansible_collections.community.docker.plugins.module_utils._api.context.config import (
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 (
ContextException,
DockerException,
)
def tls_context_to_json(context):
if context is None:
return None
return {
'client_cert': context.cert[0] if context.cert else None,
'client_key': context.cert[1] if context.cert else None,
'ca_cert': context.ca_cert,
'verify': context.verify,
# 'ssl_version': context.ssl_version, -- this isn't used anymore
}
def to_bool(value):
return True if value else False
def context_to_json(context, current):
module_config = {}
if 'docker' in context.endpoints:
endpoint = context.endpoints['docker']
if isinstance(endpoint.get('Host'), string_types):
host_str = to_text(endpoint['Host'])
# Adjust protocol name so that it works with the Docker CLI tool as well
proto = None
idx = host_str.find('://')
if idx >= 0:
proto = host_str[:idx]
host_str = host_str[idx + 3:]
if proto in ('http', 'https'):
proto = 'tcp'
if proto == 'http+unix':
proto = 'unix'
if proto:
host_str = "{0}://{1}".format(proto, host_str)
# Create config for the modules
module_config['docker_host'] = host_str
module_config['tls'] = not to_bool(endpoint.get('SkipTLSVerify'))
if context.tls_cfg.get('docker'):
tls_cfg = context.tls_cfg['docker']
if tls_cfg.ca_cert:
module_config['ca_path'] = tls_cfg.ca_cert
if tls_cfg.cert:
module_config['client_cert'] = tls_cfg.cert[0]
module_config['client_key'] = tls_cfg.cert[1]
module_config['validate_certs'] = tls_cfg.verify
module_config['tls'] = to_bool(tls_cfg.verify)
return {
'current': current,
'name': context.name,
'description': context.description,
'meta_path': None if context.meta_path is IN_MEMORY else context.meta_path,
'tls_path': None if context.tls_path is IN_MEMORY else context.tls_path,
'config': module_config,
}
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:
if module.params['cli_context']:
current_context_name, current_context_source = module.params['cli_context'], "cli_context module option"
else:
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=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())
if __name__ == '__main__':
main()