Skip to content
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

Docker config file defaults from environment #126

Merged
merged 14 commits into from
Jul 20, 2016
Merged
8 changes: 0 additions & 8 deletions doc/source/api/remoteappmanager.docker.rst
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,6 @@ remoteappmanager.docker.container_manager module
:undoc-members:
:show-inheritance:

remoteappmanager.docker.docker_client_config module
---------------------------------------------------

.. automodule:: remoteappmanager.docker.docker_client_config
:members:
:undoc-members:
:show-inheritance:

remoteappmanager.docker.docker_labels module
--------------------------------------------

Expand Down
99 changes: 55 additions & 44 deletions jupyterhub/remoteappmanager_config.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,61 @@
import os
import platform

# Clarifies to be not intended to be passed to tornado config conflict.
# Tornado extracts vars from global scope of the config file.
_platform = platform.system()

if _platform == 'Darwin':
# For platforms that have a separate docker machine, we need to connect
# using tls, but if we use self-signed certificates, using tls as True
# will produce an error of incorrect CA validation.
# As a consequence, we set tls to false, and tls_verify to true, honoring
# docker documentation (although not very clear on this point)
# See https://docs.docker.com/engine/security/https/
if "DOCKER_CERT_PATH" not in os.environ:
raise ValueError("Docker environment has not been defined.")

tls = False
tls_verify = bool(int(os.path.expandvars("${DOCKER_TLS_VERIFY}")))
tls_ca = os.path.expandvars('${DOCKER_CERT_PATH}/ca.pem')
tls_cert = os.path.expandvars('${DOCKER_CERT_PATH}/cert.pem')
tls_key = os.path.expandvars('${DOCKER_CERT_PATH}/key.pem')
docker_host = os.path.expandvars("${DOCKER_HOST}")
elif _platform == 'Linux':
# Linux works through unix socket, so we don't need tls.
tls = False
else:
raise RuntimeError("Unknown platform {}".format(_platform))


# -----------------------------
# Define the accounting class
# -----------------------------
# Notes on os.path:
# 1. When running with system-user mode, both the current directory and '~'
# are the system user's home directory.
# 2. When running in virtual-user mode, the current directory is the
# directory where jupyterhub is started, '~' would be evaluated according to
# the spawned process's owner's home directory (not the virtual user's
# home directory)

# CSV database support
# # --------------------
# # Docker configuration
# # --------------------
# #
# # Configuration options for connecting to the docker machine.
# # These options override the default provided by the local environment
# # variables.
# #
# # The endpoint of the docker machine, specified as a URL.
# # By default, it is obtained by DOCKER_HOST envvar. On Linux in a vanilla
# # install, the connection uses a unix socket by default.
#
# docker_host = "tcp://192.168.99.100:2376"
#
# # TLS configuration
# # -----------------
# #
# # Set this to True only if your docker machine has a certificate signed by
# # a recognised CA.
# # If using self-signed certificates, using tls as True will produce an error
# # of incorrect CA validation. As a consequence, the default tls setting is
# # False, and tls_verify is according to the current environment (likely True
# # with default setup on OSX), honoring docker documentation.
# # See https://docs.docker.com/engine/security/https/ for additional details
#
# tls = True
#
# # Enables verification of the certificates. By default, this is the
# # result of the DOCKER_TLS_VERIFY envvar
#
# tls_verify = True
#
# # Full paths of the CA certificate, certificate and key of the docker
# # machine. Normally these are computed from the DOCKER_CERT_PATH
#
# tls_ca = "/path/to/ca.pem"
# tls_cert = "/path/to/cert.pem"
# tls_key = "/path/to/key.pem"
#
# # ----------
# # Accounting
# # ----------
# # Notes on os.path:
# # 1. When running with system-user mode, both the current directory and '~'
# # are the system user's home directory.
# # 2. When running in virtual-user mode, the current directory is the
# # directory where jupyterhub is started, '~' would be evaluated according to
# # the spawned process's owner's home directory (not the virtual user's
# # home directory)
#
# # CSV database support
#
# accounting_class = "remoteappmanager.db.csv_db.CSVAccounting"
# accounting_kwargs = {
# "csv_file_path": os.path.abspath("./remoteappmanager.csv")}

# sqlite database support
#
# # Sqlite database support
#
# accounting_class = "remoteappmanager.db.orm.AppAccounting"
# accounting_kwargs = {
# "url": "sqlite:///"+os.path.abspath('./remoteappmanager.db')}
10 changes: 1 addition & 9 deletions remoteappmanager/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
from remoteappmanager.handlers.api import HomeHandler
from remoteappmanager.logging.logging_mixin import LoggingMixin
from remoteappmanager.docker.container_manager import ContainerManager
from remoteappmanager.docker.docker_client_config import DockerClientConfig
from remoteappmanager.jinja2_adapters import Jinja2LoaderAdapter
from remoteappmanager.user import User
from remoteappmanager.traitlets import as_dict
Expand Down Expand Up @@ -79,14 +78,7 @@ def _container_manager_default(self):
"""Initializes the docker container manager."""

return ContainerManager(
docker_config=DockerClientConfig(
tls=self.file_config.tls,
tls_verify=self.file_config.tls_verify,
tls_ca=self.file_config.tls_ca,
tls_key=self.file_config.tls_key,
tls_cert=self.file_config.tls_cert,
docker_host=self.file_config.docker_host,
)
docker_config=self.file_config.docker_config()
)

@default("reverse_proxy")
Expand Down
2 changes: 1 addition & 1 deletion remoteappmanager/command_line_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def parse_config(self):

tornado.options.parse_command_line()

set_traits_from_dict(self, options)
set_traits_from_dict(self, options.as_dict())

# Normalize the base_urlpath to end with a slash
self.base_urlpath = with_end_slash(self.base_urlpath)
46 changes: 4 additions & 42 deletions remoteappmanager/docker/async_docker_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import docker
import functools
from docker.utils import kwargs_from_env

# Common threaded executor for asynchronous jobs.
# Required for the AsyncDockerClient to operate.
Expand All @@ -18,7 +17,7 @@ class AsyncDockerClient:
executor.
"""

def __init__(self, config=None, *args, **kwargs):
def __init__(self, *args, **kwargs):
"""Initialises the docker async client.

The client uses a single, module level executor to submit
Expand All @@ -30,50 +29,13 @@ def __init__(self, config=None, *args, **kwargs):

Note that the executor is a ThreadPoolExecutor with a single thread.
"""
self.config = config
self.client = None

super().__init__(*args, **kwargs)

def _init_client(self):
"""Returns the docker-py synchronous client instance."""
config = self.config

# If there is no configuration, we try to obtain it
# from the envvars
if config is None:
kwargs = kwargs_from_env()
client = docker.Client(version='auto', **kwargs)
else:
if config.tls:
tls_config = True
elif config.tls_verify or config.tls_ca or config.tls_client:
tls_config = docker.tls.TLSConfig(
client_cert=config.tls_client,
ca_cert=config.tls_ca,
verify=config.tls_verify,
assert_hostname=config.tls_assert_hostname)
else:
tls_config = None

if len(config.docker_host) == 0:
docker_host = 'unix://var/run/docker.sock'
else:
docker_host = config.docker_host

client = docker.Client(base_url=docker_host,
tls=tls_config,
version='auto')
self.client = client
self._sync_client = docker.Client(*args, **kwargs)

def __getattr__(self, attr):
"""Returns the docker client method, wrapped in an async execution
environment. The returned method must be used in conjunction with
the yield keyword."""
if self.client is None:
self._init_client()

if hasattr(self.client, attr):
if hasattr(self._sync_client, attr):
return functools.partial(self._submit_to_executor, attr)
else:
raise AttributeError(
Expand Down Expand Up @@ -108,5 +70,5 @@ def _invoke(self, method, *args, **kwargs):
"""wrapper for calling docker methods to be passed to
ThreadPoolExecutor.
"""
m = getattr(self.client, method)
m = getattr(self._sync_client, method)
return m(*args, **kwargs)
29 changes: 20 additions & 9 deletions remoteappmanager/docker/container_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@
from escapism import escape
from remoteappmanager.docker.async_docker_client import AsyncDockerClient
from remoteappmanager.docker.container import Container
from remoteappmanager.docker.docker_client_config import DockerClientConfig
from remoteappmanager.docker.docker_labels import SIMPHONY_NS
from remoteappmanager.docker.image import Image
from remoteappmanager.logging.logging_mixin import LoggingMixin
from tornado import gen
from traitlets import (
Int,
Dict,
Set,
Instance,
default)
Expand All @@ -25,11 +25,6 @@


class ContainerManager(LoggingMixin):
#: The configuration of the docker client.
#: Can be None. If that's the case, it will use the environment variables.
docker_config = Instance(DockerClientConfig,
allow_none=True)

#: The asynchronous docker client.
docker_client = Instance(AsyncDockerClient)

Expand All @@ -44,6 +39,21 @@ class ContainerManager(LoggingMixin):
#: Tracks if a given container is stopping down.
_stop_pending = Set()

#: The docker client configuration
docker_config = Dict()

def __init__(self, docker_config, *args, **kwargs):
"""Initializes the Container manager.

Parameters
----------
docker_config: Dict
A dictionary containing the keywords for the configuration of
the docker client in agreement to docker py documentation.
"""
self.docker_config = docker_config
super().__init__(*args, **kwargs)

@gen.coroutine
def start_container(self, user_name, image_name, mapping_id, volumes):
"""Starts a container using the given image name.
Expand Down Expand Up @@ -376,8 +386,9 @@ def _get_ip_and_port(self, container_id):
# docker url to extract the ip.
ip = '127.0.0.1'

if self.docker_config and self.docker_config.docker_host != '':
url = urlparse(self.docker_config.docker_host)
base_url = self.docker_config.get("base_url")
if base_url:
url = urlparse(base_url)
if url.scheme == 'tcp':
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/simphony/simphony-remote/pull/126/files#r71502734 refers to here. Maybe we should take the hostname regardless of its scheme?

ip = url.hostname

Expand Down Expand Up @@ -459,7 +470,7 @@ def _stop_and_remove_container(self, container_id):

@default("docker_client")
def _docker_client_default(self):
return AsyncDockerClient(config=self.docker_config)
return AsyncDockerClient(**self.docker_config)


def _get_container_env(user_name, url_id):
Expand Down
42 changes: 0 additions & 42 deletions remoteappmanager/docker/docker_client_config.py

This file was deleted.

Loading