Skip to content

Commit

Permalink
Fix #181 move docker config to its own folder
Browse files Browse the repository at this point in the history
  • Loading branch information
khizunov committed Jan 22, 2025
1 parent 239346b commit 12cf39a
Show file tree
Hide file tree
Showing 6 changed files with 63 additions and 21 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ venv
MANIFEST
reports
**/RCS/**
.venv
3 changes: 3 additions & 0 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,9 @@ ignore-imports=no
# Maximum number of arguments for function / method
max-args=8

# Maximum number of positional arguments (see R0917).
max-positional-arguments = 12

# Argument names that match this expression will be ignored. Default to name
# with leading underscore
ignored-argument-names=_.*
Expand Down
26 changes: 19 additions & 7 deletions skipper/runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import sys
from retry import retry
from skipper import utils
from skipper.utils import DOCKER_CONFIG


def get_default_net():
Expand All @@ -17,6 +18,7 @@ def get_default_net():


# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments
def run(command, fqdn_image=None, environment=None, interactive=False, name=None, net=None, publish=(), volumes=None,
workdir=None, use_cache=False, workspace=None, env_file=(), stdout_to_stderr=False):

Expand Down Expand Up @@ -44,7 +46,9 @@ def _run(cmd_args, stdout_to_stderr=False):


# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-arguments
# pylint: disable=too-many-positional-arguments
def _run_nested(fqdn_image, environment, command, interactive, name, net, publish, volumes, workdir, use_cache, workspace, env_file):
cwd = os.getcwd()
if workspace is None:
Expand Down Expand Up @@ -86,6 +90,12 @@ def _run_nested(fqdn_image, environment, command, interactive, name, net, publis
cmd += ['-e', f'CONTAINER_RUNTIME_COMMAND={utils.get_runtime_command()}']

if utils.get_runtime_command() == "docker":
if not utils.is_environment_variable_defined('DOCKER_CONFIG', environment):
cmd += ['-e', f'DOCKER_CONFIG={DOCKER_CONFIG}']

if not utils.is_environment_variable_defined('DOCKER_CONTEXT', environment):
cmd += ['-e', 'DOCKER_CONTEXT=default']

try:
docker_gid = grp.getgrnam('docker').gr_gid
cmd += ['-e', f'SKIPPER_DOCKER_GID={docker_gid}']
Expand Down Expand Up @@ -136,12 +146,12 @@ def handle_volumes_bind_mount(docker_cmd, homedir, volumes, workspace):
volumes.extend([f'{homedir}/.netrc:{homedir}/.netrc:ro',
f'{homedir}/.gitconfig:{homedir}/.gitconfig:ro'])

# required for docker buildkit and credentials
docker_folder = f'{homedir}/.docker'
if not any(f'{docker_folder}:{docker_folder}' in volume for volume in volumes):
_add_path_if_exists(docker_folder, docker_folder, 'rw', volumes)
# required for docker credentials
docker_config_folder = f'{homedir}/.docker/config.json'
if not any(f'{docker_config_folder}:' in volume for volume in volumes):
_add_path_if_exists(docker_config_folder, f'{DOCKER_CONFIG}/config.json', 'rw', volumes)

# required for docker login (certificates)
# required for docker certificates
_add_path_if_exists('/etc/docker', '/etc/docker', 'ro', volumes)

if utils.get_runtime_command() == utils.PODMAN:
Expand Down Expand Up @@ -222,8 +232,10 @@ def _network(net):
yield
else:
_create_network(net)
yield
_destroy_network(net)
try:
yield
finally:
_destroy_network(net)


def _create_network(net):
Expand Down
5 changes: 5 additions & 0 deletions skipper/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
logger = None # pylint: disable=invalid-name

CONTAINER_RUNTIME_COMMAND = os.getenv("CONTAINER_RUNTIME_COMMAND")
DOCKER_CONFIG = "/opt/.docker"

SKIPPER_ULIMIT = [['--ulimit', limit] for limit in os.environ.get('SKIPPER_ULIMITS', 'nofile=65536:65536').split(',')]

Expand Down Expand Up @@ -211,3 +212,7 @@ def set_remote_registry_login_info(registry, ctx_object):
except Exception: # pylint: disable=broad-except
# Ignore failure and just continue
pass


def is_environment_variable_defined(name, environment):
return any(name in env_var for env_var in environment)
38 changes: 29 additions & 9 deletions tests/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from skipper import utils
from skipper import runner
from skipper.runner import get_default_net
from skipper.utils import DOCKER_CONFIG

USER_ID = 1000
GROUP_ID = 2000
Expand Down Expand Up @@ -32,6 +33,7 @@ def get_volume_mapping(volume_mapping):
return volume_mapping


@mock.patch('skipper.runner._network_exists', mock.MagicMock(autospec=True, return_value=True))
class TestRunner(unittest.TestCase):

NET_LS = 'NETWORK ID NAME DRIVER SCOPE\n' \
Expand Down Expand Up @@ -89,10 +91,12 @@ def test_run_simple_command_nested_network_exist(self, resource_filename_mock, c
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('/var/run/docker.sock:/var/run/docker.sock:rw'),
Expand Down Expand Up @@ -135,10 +139,12 @@ def test_run_simple_command_nested_network_not_exist(self, resource_filename_moc
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('/var/run/docker.sock:/var/run/docker.sock:rw'),
Expand Down Expand Up @@ -182,10 +188,12 @@ def test_run_simple_command_nested_with_env(self, resource_filename_mock, check_
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('/var/run/docker.sock:/var/run/docker.sock:rw'),
Expand Down Expand Up @@ -236,12 +244,14 @@ def test_run_simple_command_nested_with_env_file(
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(
homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(
homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('/var/run/docker.sock:/var/run/docker.sock:rw'),
Expand Down Expand Up @@ -293,13 +303,15 @@ def test_run_simple_command_nested_with_multiple_env_files(
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', '%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(
homedir=HOME_DIR),
'-v', '%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(
homedir=HOME_DIR),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(
homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(
homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', '/etc/docker:/etc/docker:ro',
'-v', '%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR),
'-v', '/var/run/docker.sock:/var/run/docker.sock:rw',
Expand Down Expand Up @@ -345,10 +357,12 @@ def test_run_simple_command_nested_interactive(self, resource_filename_mock,
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('/var/run/docker.sock:/var/run/docker.sock:rw'),
Expand Down Expand Up @@ -390,10 +404,12 @@ def test_run_complex_command_nested(self, resource_filename_mock, check_output_m
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('/var/run/docker.sock:/var/run/docker.sock:rw'),
Expand Down Expand Up @@ -439,10 +455,12 @@ def test_run_complex_command_nested_with_env(self, resource_filename_mock, check
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('/var/run/docker.sock:/var/run/docker.sock:rw'),
Expand Down Expand Up @@ -494,6 +512,8 @@ def test_run_complex_command_nested_with_special_case_verification(self, resourc
'-e', 'SKIPPER_UID=%(user_uid)s' % dict(user_uid=USER_ID),
'-e', 'HOME=%(homedir)s' % dict(homedir=HOME_DIR),
'-e', 'CONTAINER_RUNTIME_COMMAND=%(runtime_command)s' % dict(runtime_command=utils.get_runtime_command()),
'-e', 'DOCKER_CONFIG=/opt/.docker',
'-e', 'DOCKER_CONTEXT=default',
'-e', 'SKIPPER_DOCKER_GID=978',
'-v', '%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR),
'-v', '%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR),
Expand Down
11 changes: 6 additions & 5 deletions tests/test_runner_podman.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from skipper import utils
from skipper import runner
from skipper.runner import get_default_net
from skipper.utils import PODMAN, DOCKER_CONFIG
from tests.test_runner import get_volume_mapping

USER_ID = 1000
Expand Down Expand Up @@ -85,7 +86,7 @@ def test_run_simple_command_nested_network_exist(self, resource_filename_mock, c
'--group-add', 'keep-groups',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('entrypoint.sh:/opt/skipper/skipper-entrypoint.sh:rw'),
Expand Down Expand Up @@ -127,7 +128,7 @@ def test_run_simple_command_nested_network_not_exist(self, resource_filename_moc
'--group-add', 'keep-groups',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('entrypoint.sh:/opt/skipper/skipper-entrypoint.sh:rw'),
Expand Down Expand Up @@ -169,7 +170,7 @@ def test_run_complex_command_nested(self, resource_filename_mock, check_output_m
'--group-add', 'keep-groups',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('entrypoint.sh:/opt/skipper/skipper-entrypoint.sh:rw'),
Expand Down Expand Up @@ -215,7 +216,7 @@ def test_run_non_existent_unauthorized_volume(self, resource_filename_mock,
'--group-add', 'keep-groups',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('entrypoint.sh:/opt/skipper/skipper-entrypoint.sh:rw'),
Expand Down Expand Up @@ -259,7 +260,7 @@ def test_run_complex_command_nested_with_env(self, resource_filename_mock, check
'--group-add', 'keep-groups',
'-v', get_volume_mapping('%(homedir)s/.netrc:%(homedir)s/.netrc:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.gitconfig:%(homedir)s/.gitconfig:ro' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker:%(homedir)s/.docker:rw' % dict(homedir=HOME_DIR)),
'-v', get_volume_mapping('%(homedir)s/.docker/config.json:%(docker_config)s/config.json:rw' % dict(homedir=HOME_DIR, docker_config=DOCKER_CONFIG)),
'-v', get_volume_mapping('/etc/docker:/etc/docker:ro'),
'-v', get_volume_mapping('%(workdir)s:%(workdir)s:rw' % dict(workdir=WORKDIR)),
'-v', get_volume_mapping('entrypoint.sh:/opt/skipper/skipper-entrypoint.sh:rw'),
Expand Down

0 comments on commit 12cf39a

Please sign in to comment.