|
|
|
"""Functions for accessing docker via the docker cli."""
|
|
|
|
|
|
|
|
from __future__ import absolute_import, print_function
|
|
|
|
|
|
|
|
import json
|
|
|
|
import os
|
|
|
|
import time
|
|
|
|
|
|
|
|
from lib.executor import (
|
|
|
|
SubprocessError,
|
|
|
|
)
|
|
|
|
|
|
|
|
from lib.util import (
|
|
|
|
ApplicationError,
|
|
|
|
run_command,
|
|
|
|
common_environment,
|
|
|
|
display,
|
|
|
|
find_executable,
|
|
|
|
)
|
|
|
|
|
|
|
|
from lib.config import (
|
|
|
|
EnvironmentConfig,
|
|
|
|
)
|
|
|
|
|
|
|
|
BUFFER_SIZE = 256 * 256
|
|
|
|
|
|
|
|
|
|
|
|
def docker_available():
|
|
|
|
"""
|
|
|
|
:rtype: bool
|
|
|
|
"""
|
|
|
|
return find_executable('docker', required=False)
|
|
|
|
|
|
|
|
|
|
|
|
def get_docker_container_id():
|
|
|
|
"""
|
|
|
|
:rtype: str | None
|
|
|
|
"""
|
|
|
|
path = '/proc/self/cgroup'
|
|
|
|
|
|
|
|
if not os.path.exists(path):
|
|
|
|
return None
|
|
|
|
|
|
|
|
with open(path) as cgroup_fd:
|
|
|
|
contents = cgroup_fd.read()
|
|
|
|
|
|
|
|
paths = [line.split(':')[2] for line in contents.splitlines()]
|
|
|
|
container_ids = set(path.split('/')[2] for path in paths if path.startswith('/docker/'))
|
|
|
|
|
|
|
|
if not container_ids:
|
|
|
|
return None
|
|
|
|
|
|
|
|
if len(container_ids) == 1:
|
|
|
|
return container_ids.pop()
|
|
|
|
|
|
|
|
raise ApplicationError('Found multiple container_id candidates: %s\n%s' % (sorted(container_ids), contents))
|
|
|
|
|
|
|
|
|
|
|
|
def get_docker_container_ip(args, container_id):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type container_id: str
|
|
|
|
:rtype: str
|
|
|
|
"""
|
|
|
|
results = docker_inspect(args, container_id)
|
|
|
|
ipaddress = results[0]['NetworkSettings']['IPAddress']
|
|
|
|
return ipaddress
|
|
|
|
|
|
|
|
|
|
|
|
def get_docker_networks(args, container_id):
|
|
|
|
"""
|
|
|
|
:param args: EnvironmentConfig
|
|
|
|
:param container_id: str
|
|
|
|
:rtype: list[str]
|
|
|
|
"""
|
|
|
|
results = docker_inspect(args, container_id)
|
|
|
|
networks = sorted(results[0]['NetworkSettings']['Networks'])
|
|
|
|
return networks
|
|
|
|
|
|
|
|
|
|
|
|
def docker_pull(args, image):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type image: str
|
|
|
|
"""
|
|
|
|
if not args.docker_pull:
|
|
|
|
display.warning('Skipping docker pull for "%s". Image may be out-of-date.' % image)
|
|
|
|
return
|
|
|
|
|
|
|
|
for _ in range(1, 10):
|
|
|
|
try:
|
|
|
|
docker_command(args, ['pull', image])
|
|
|
|
return
|
|
|
|
except SubprocessError:
|
|
|
|
display.warning('Failed to pull docker image "%s". Waiting a few seconds before trying again.' % image)
|
|
|
|
time.sleep(3)
|
|
|
|
|
|
|
|
raise ApplicationError('Failed to pull docker image "%s".' % image)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_put(args, container_id, src, dst):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type container_id: str
|
|
|
|
:type src: str
|
|
|
|
:type dst: str
|
|
|
|
"""
|
|
|
|
# avoid 'docker cp' due to a bug which causes 'docker rm' to fail
|
|
|
|
with open(src, 'rb') as src_fd:
|
|
|
|
docker_exec(args, container_id, ['dd', 'of=%s' % dst, 'bs=%s' % BUFFER_SIZE],
|
|
|
|
options=['-i'], stdin=src_fd, capture=True)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_get(args, container_id, src, dst):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type container_id: str
|
|
|
|
:type src: str
|
|
|
|
:type dst: str
|
|
|
|
"""
|
|
|
|
# avoid 'docker cp' due to a bug which causes 'docker rm' to fail
|
|
|
|
with open(dst, 'wb') as dst_fd:
|
|
|
|
docker_exec(args, container_id, ['dd', 'if=%s' % src, 'bs=%s' % BUFFER_SIZE],
|
|
|
|
options=['-i'], stdout=dst_fd, capture=True)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_run(args, image, options, cmd=None):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type image: str
|
|
|
|
:type options: list[str] | None
|
|
|
|
:type cmd: list[str] | None
|
|
|
|
:rtype: str | None, str | None
|
|
|
|
"""
|
|
|
|
if not options:
|
|
|
|
options = []
|
|
|
|
|
|
|
|
if not cmd:
|
|
|
|
cmd = []
|
|
|
|
|
|
|
|
for _ in range(1, 3):
|
|
|
|
try:
|
|
|
|
return docker_command(args, ['run'] + options + [image] + cmd, capture=True)
|
|
|
|
except SubprocessError as ex:
|
|
|
|
display.error(ex)
|
|
|
|
display.warning('Failed to run docker image "%s". Waiting a few seconds before trying again.' % image)
|
|
|
|
time.sleep(3)
|
|
|
|
|
|
|
|
raise ApplicationError('Failed to run docker image "%s".' % image)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_rm(args, container_id):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type container_id: str
|
|
|
|
"""
|
|
|
|
docker_command(args, ['rm', '-f', container_id], capture=True)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_inspect(args, container_id):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type container_id: str
|
|
|
|
:rtype: list[dict]
|
|
|
|
"""
|
|
|
|
if args.explain:
|
|
|
|
return []
|
|
|
|
|
|
|
|
try:
|
|
|
|
stdout, _ = docker_command(args, ['inspect', container_id], capture=True)
|
|
|
|
return json.loads(stdout)
|
|
|
|
except SubprocessError as ex:
|
|
|
|
try:
|
|
|
|
return json.loads(ex.stdout)
|
|
|
|
except:
|
|
|
|
raise ex # pylint: disable=locally-disabled, raising-bad-type
|
|
|
|
|
|
|
|
|
|
|
|
def docker_network_disconnect(args, container_id, network):
|
|
|
|
"""
|
|
|
|
:param args: EnvironmentConfig
|
|
|
|
:param container_id: str
|
|
|
|
:param network: str
|
|
|
|
"""
|
|
|
|
docker_command(args, ['network', 'disconnect', network, container_id], capture=True)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_network_inspect(args, network):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type network: str
|
|
|
|
:rtype: list[dict]
|
|
|
|
"""
|
|
|
|
if args.explain:
|
|
|
|
return []
|
|
|
|
|
|
|
|
try:
|
|
|
|
stdout, _ = docker_command(args, ['network', 'inspect', network], capture=True)
|
|
|
|
return json.loads(stdout)
|
|
|
|
except SubprocessError as ex:
|
|
|
|
try:
|
|
|
|
return json.loads(ex.stdout)
|
|
|
|
except:
|
|
|
|
raise ex # pylint: disable=locally-disabled, raising-bad-type
|
|
|
|
|
|
|
|
|
|
|
|
def docker_exec(args, container_id, cmd, options=None, capture=False, stdin=None, stdout=None):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type container_id: str
|
|
|
|
:type cmd: list[str]
|
|
|
|
:type options: list[str] | None
|
|
|
|
:type capture: bool
|
|
|
|
:type stdin: file | None
|
|
|
|
:type stdout: file | None
|
|
|
|
:rtype: str | None, str | None
|
|
|
|
"""
|
|
|
|
if not options:
|
|
|
|
options = []
|
|
|
|
|
|
|
|
return docker_command(args, ['exec'] + options + [container_id] + cmd, capture=capture, stdin=stdin, stdout=stdout)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_command(args, cmd, capture=False, stdin=None, stdout=None):
|
|
|
|
"""
|
|
|
|
:type args: EnvironmentConfig
|
|
|
|
:type cmd: list[str]
|
|
|
|
:type capture: bool
|
|
|
|
:type stdin: file | None
|
|
|
|
:type stdout: file | None
|
|
|
|
:rtype: str | None, str | None
|
|
|
|
"""
|
|
|
|
env = docker_environment()
|
|
|
|
return run_command(args, ['docker'] + cmd, env=env, capture=capture, stdin=stdin, stdout=stdout)
|
|
|
|
|
|
|
|
|
|
|
|
def docker_environment():
|
|
|
|
"""
|
|
|
|
:rtype: dict[str, str]
|
|
|
|
"""
|
|
|
|
env = common_environment()
|
|
|
|
env.update(dict((key, os.environ[key]) for key in os.environ if key.startswith('DOCKER_')))
|
|
|
|
return env
|