-
Notifications
You must be signed in to change notification settings - Fork 144
Add docker_context_info module #1039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f399775
423ad11
230922b
3c86a9e
4e76d15
e1baff0
471681d
65e32d5
7bec26a
f73a240
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,242 @@ | ||
| # -*- coding: utf-8 -*- | ||
| # This code is part of the Ansible collection community.docker, but is an independent component. | ||
| # This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) | ||
| # | ||
| # Copyright (c) 2016-2025 Docker, Inc. | ||
| # | ||
| # It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| from __future__ import (absolute_import, division, print_function) | ||
| __metaclass__ = type | ||
|
|
||
| import json | ||
| import os | ||
|
|
||
| from ansible.module_utils.six import raise_from | ||
|
|
||
| from .. import errors | ||
|
|
||
| from .config import ( | ||
| METAFILE, | ||
| get_current_context_name, | ||
| get_meta_dir, | ||
| write_context_name_to_docker_config, | ||
| ) | ||
| from .context import Context | ||
|
|
||
|
|
||
| def create_default_context(): | ||
| host = None | ||
| if os.environ.get('DOCKER_HOST'): | ||
| host = os.environ.get('DOCKER_HOST') | ||
| return Context("default", "swarm", host, description="Current DOCKER_HOST based configuration") | ||
|
|
||
|
|
||
| class ContextAPI(object): | ||
| """Context API. | ||
| Contains methods for context management: | ||
| create, list, remove, get, inspect. | ||
| """ | ||
| DEFAULT_CONTEXT = None | ||
|
|
||
| @classmethod | ||
| def get_default_context(cls): | ||
| context = cls.DEFAULT_CONTEXT | ||
| if context is None: | ||
| context = create_default_context() | ||
| cls.DEFAULT_CONTEXT = context | ||
| return context | ||
|
|
||
| @classmethod | ||
| def create_context( | ||
| cls, name, orchestrator=None, host=None, tls_cfg=None, | ||
| default_namespace=None, skip_tls_verify=False): | ||
| """Creates a new context. | ||
| Returns: | ||
| (Context): a Context object. | ||
| Raises: | ||
| :py:class:`docker.errors.MissingContextParameter` | ||
| If a context name is not provided. | ||
| :py:class:`docker.errors.ContextAlreadyExists` | ||
| If a context with the name already exists. | ||
| :py:class:`docker.errors.ContextException` | ||
| If name is default. | ||
|
|
||
| Example: | ||
|
|
||
| >>> from docker.context import ContextAPI | ||
| >>> ctx = ContextAPI.create_context(name='test') | ||
| >>> print(ctx.Metadata) | ||
| { | ||
| "Name": "test", | ||
| "Metadata": {}, | ||
| "Endpoints": { | ||
| "docker": { | ||
| "Host": "unix:///var/run/docker.sock", | ||
| "SkipTLSVerify": false | ||
| } | ||
| } | ||
| } | ||
| """ | ||
| if not name: | ||
| raise errors.MissingContextParameter("name") | ||
| if name == "default": | ||
| raise errors.ContextException( | ||
| '"default" is a reserved context name') | ||
| ctx = Context.load_context(name) | ||
| if ctx: | ||
| raise errors.ContextAlreadyExists(name) | ||
| endpoint = "docker" | ||
| if orchestrator and orchestrator != "swarm": | ||
| endpoint = orchestrator | ||
| ctx = Context(name, orchestrator) | ||
| ctx.set_endpoint( | ||
| endpoint, host, tls_cfg, | ||
| skip_tls_verify=skip_tls_verify, | ||
| def_namespace=default_namespace) | ||
| ctx.save() | ||
| return ctx | ||
|
|
||
| @classmethod | ||
| def get_context(cls, name=None): | ||
| """Retrieves a context object. | ||
| Args: | ||
| name (str): The name of the context | ||
|
|
||
| Example: | ||
|
|
||
| >>> from docker.context import ContextAPI | ||
| >>> ctx = ContextAPI.get_context(name='test') | ||
| >>> print(ctx.Metadata) | ||
| { | ||
| "Name": "test", | ||
| "Metadata": {}, | ||
| "Endpoints": { | ||
| "docker": { | ||
| "Host": "unix:///var/run/docker.sock", | ||
| "SkipTLSVerify": false | ||
| } | ||
| } | ||
| } | ||
| """ | ||
| if not name: | ||
| name = get_current_context_name() | ||
| if name == "default": | ||
| return cls.get_default_context() | ||
| return Context.load_context(name) | ||
|
|
||
| @classmethod | ||
| def contexts(cls): | ||
| """Context list. | ||
| Returns: | ||
| (Context): List of context objects. | ||
| Raises: | ||
| :py:class:`docker.errors.APIError` | ||
| If something goes wrong. | ||
| """ | ||
| names = [] | ||
| for dirname, dummy, fnames in os.walk(get_meta_dir()): | ||
| for filename in fnames: | ||
| if filename == METAFILE: | ||
| filepath = os.path.join(dirname, filename) | ||
| try: | ||
| with open(filepath, "r") as f: | ||
| data = json.load(f) | ||
| name = data["Name"] | ||
| if name == "default": | ||
| raise ValueError('"default" is a reserved context name') | ||
| names.append(name) | ||
| except Exception as e: | ||
| raise_from(errors.ContextException( | ||
| "Failed to load metafile {filepath}: {e}".format(filepath=filepath, e=e), | ||
| ), e) | ||
|
|
||
| contexts = [cls.get_default_context()] | ||
| for name in names: | ||
| 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 | ||
| def get_current_context(cls): | ||
| """Get current context. | ||
| Returns: | ||
| (Context): current context object. | ||
| """ | ||
| return cls.get_context() | ||
|
|
||
| @classmethod | ||
| def set_current_context(cls, name="default"): | ||
| ctx = cls.get_context(name) | ||
| if not ctx: | ||
| raise errors.ContextNotFound(name) | ||
|
|
||
| err = write_context_name_to_docker_config(name) | ||
| if err: | ||
| raise errors.ContextException( | ||
| 'Failed to set current context: {err}'.format(err=err)) | ||
|
|
||
| @classmethod | ||
| def remove_context(cls, name): | ||
| """Remove a context. Similar to the ``docker context rm`` command. | ||
|
|
||
| Args: | ||
| name (str): The name of the context | ||
|
|
||
| Raises: | ||
| :py:class:`docker.errors.MissingContextParameter` | ||
| If a context name is not provided. | ||
| :py:class:`docker.errors.ContextNotFound` | ||
| If a context with the name does not exist. | ||
| :py:class:`docker.errors.ContextException` | ||
| If name is default. | ||
|
|
||
| Example: | ||
|
|
||
| >>> from docker.context import ContextAPI | ||
| >>> ContextAPI.remove_context(name='test') | ||
| >>> | ||
| """ | ||
| if not name: | ||
| raise errors.MissingContextParameter("name") | ||
| if name == "default": | ||
| raise errors.ContextException( | ||
| 'context "default" cannot be removed') | ||
| ctx = Context.load_context(name) | ||
| if not ctx: | ||
| raise errors.ContextNotFound(name) | ||
| if name == get_current_context_name(): | ||
| write_context_name_to_docker_config(None) | ||
| ctx.remove() | ||
|
|
||
| @classmethod | ||
| def inspect_context(cls, name="default"): | ||
| """Inspect a context. Similar to the ``docker context inspect`` command. | ||
|
|
||
| Args: | ||
| name (str): The name of the context | ||
|
|
||
| Raises: | ||
| :py:class:`docker.errors.MissingContextParameter` | ||
| If a context name is not provided. | ||
| :py:class:`docker.errors.ContextNotFound` | ||
| If a context with the name does not exist. | ||
|
|
||
| Example: | ||
|
|
||
| >>> from docker.context import ContextAPI | ||
| >>> ContextAPI.remove_context(name='test') | ||
| >>> | ||
| """ | ||
| if not name: | ||
| raise errors.MissingContextParameter("name") | ||
| if name == "default": | ||
| return cls.get_default_context()() | ||
| ctx = Context.load_context(name) | ||
| if not ctx: | ||
| raise errors.ContextNotFound(name) | ||
|
|
||
| return ctx() | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,103 @@ | ||||||||||||||||||||||
| # -*- coding: utf-8 -*- | ||||||||||||||||||||||
| # This code is part of the Ansible collection community.docker, but is an independent component. | ||||||||||||||||||||||
| # This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/) | ||||||||||||||||||||||
| # | ||||||||||||||||||||||
| # Copyright (c) 2016-2025 Docker, Inc. | ||||||||||||||||||||||
| # | ||||||||||||||||||||||
| # It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection) | ||||||||||||||||||||||
| # SPDX-License-Identifier: Apache-2.0 | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from __future__ import (absolute_import, division, print_function) | ||||||||||||||||||||||
| __metaclass__ = type | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| import hashlib | ||||||||||||||||||||||
| import json | ||||||||||||||||||||||
| import os | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| from ..constants import DEFAULT_UNIX_SOCKET, IS_WINDOWS_PLATFORM | ||||||||||||||||||||||
| from ..utils.config import find_config_file, get_default_config_file | ||||||||||||||||||||||
| from ..utils.utils import parse_host | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| METAFILE = "meta.json" | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_current_context_name_with_source(): | ||||||||||||||||||||||
| if os.environ.get('DOCKER_HOST'): | ||||||||||||||||||||||
| return "default", "DOCKER_HOST environment variable set" | ||||||||||||||||||||||
| if os.environ.get('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: | ||||||||||||||||||||||
| return json.load(f).get("currentContext", "default"), "configuration file {file}".format(file=docker_cfg_path) | ||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||
| 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): | ||||||||||||||||||||||
| if name == 'default': | ||||||||||||||||||||||
| name = None | ||||||||||||||||||||||
| docker_cfg_path = find_config_file() | ||||||||||||||||||||||
| config = {} | ||||||||||||||||||||||
| if docker_cfg_path: | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| with open(docker_cfg_path) as f: | ||||||||||||||||||||||
| config = json.load(f) | ||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||
| return e | ||||||||||||||||||||||
| current_context = config.get("currentContext", None) | ||||||||||||||||||||||
| if current_context and not name: | ||||||||||||||||||||||
| del config["currentContext"] | ||||||||||||||||||||||
| elif name: | ||||||||||||||||||||||
| config["currentContext"] = name | ||||||||||||||||||||||
| else: | ||||||||||||||||||||||
| return | ||||||||||||||||||||||
| if not docker_cfg_path: | ||||||||||||||||||||||
| docker_cfg_path = get_default_config_file() | ||||||||||||||||||||||
| try: | ||||||||||||||||||||||
| with open(docker_cfg_path, "w") as f: | ||||||||||||||||||||||
| json.dump(config, f, indent=4) | ||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||
| return e | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_context_id(name): | ||||||||||||||||||||||
| return hashlib.sha256(name.encode('utf-8')).hexdigest() | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_context_dir(): | ||||||||||||||||||||||
| docker_cfg_path = find_config_file() or get_default_config_file() | ||||||||||||||||||||||
| return os.path.join(os.path.dirname(docker_cfg_path), "contexts") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_meta_dir(name=None): | ||||||||||||||||||||||
| meta_dir = os.path.join(get_context_dir(), "meta") | ||||||||||||||||||||||
| if name: | ||||||||||||||||||||||
| return os.path.join(meta_dir, get_context_id(name)) | ||||||||||||||||||||||
| return meta_dir | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_meta_file(name): | ||||||||||||||||||||||
| return os.path.join(get_meta_dir(name), METAFILE) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_tls_dir(name=None, endpoint=""): | ||||||||||||||||||||||
| context_dir = get_context_dir() | ||||||||||||||||||||||
| if name: | ||||||||||||||||||||||
| return os.path.join(context_dir, "tls", get_context_id(name), endpoint) | ||||||||||||||||||||||
| return os.path.join(context_dir, "tls") | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
|
|
||||||||||||||||||||||
| def get_context_host(path=None, tls=False): | ||||||||||||||||||||||
| host = parse_host(path, IS_WINDOWS_PLATFORM, tls) | ||||||||||||||||||||||
| if host == DEFAULT_UNIX_SOCKET: | ||||||||||||||||||||||
| # remove http+ from default docker socket url | ||||||||||||||||||||||
| if host.startswith("http+"): | ||||||||||||||||||||||
| host = host[5:] | ||||||||||||||||||||||
| return host | ||||||||||||||||||||||
|
Comment on lines
+98
to
+103
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
But I don't know how the host looks like when it's not a
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer not to change this, since it will be some divergence between the original Docker SDK for Python and this vendored version of the code that might break at some point in funny/unexpected ways. Docker SDK for Python uses |
||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this copyright be docker? is it copied from some upstream sdk?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep, it's copied from https://github.com/docker/docker-py/