# Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). You # may not use this file except in compliance with the License. A copy of # the License is located at # # http://aws.amazon.com/apache2.0/ # # or in the "license" file accompanying this file. This file is # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF # ANY KIND, either express or implied. See the License for the specific # language governing permissions and limitations under the License. import json from botocore.compat import six from cement.utils.misc import minimal_logger from ebcli.core import fileoperations from ebcli.lib import utils from ebcli.objects.exceptions import ValidationError, CommandError from ebcli.resources.strings import strings EXPOSE_CMD = 'EXPOSE' FROM_CMD = 'FROM' LATEST_TAG = ':latest' NETWORK_SETTINGS_KEY = 'NetworkSettings' PORTS_KEY = 'Ports' HOST_PORT_KEY = 'HostPort' STATE_KEY = 'State' RUNNING_KEY = 'Running' LOG = minimal_logger(__name__) def pull_img(full_docker_path): """ Pulls a base image found in Dockerfile. :param full_docker_path: str: path to the Dockerfile :return: None """ img = _get_base_img(full_docker_path) if not _is_tag_specified(img): img += LATEST_TAG _pull_img(img) def build_img(docker_path, file_path=None): """ Builds a docker image using Dockerfile found in docker path. :param docker_path: str: path of dir containing the Dockerfile :param file_path: str: optional name of Dockerfile :return: str: id of the new image """ img = utils.random_string(6) tag = utils.random_string(6) img_tag = '{}:{}'.format(img, tag) opts = ['-t', img_tag ,'-f', file_path] if file_path else ['-t', img_tag] args = ['docker', 'build'] + opts + [docker_path] output = _run_live(args) return _get_img_id_from_img_tag(img_tag) def run_container(full_docker_path, image_id, host_port=None, envvars_map=None, volume_map=None, name=None): """ Runs a Docker container. Container port comes from the Dockerfile, which is mapped to the given host port. :param full_docker_path: str: path to the Dockerfile :param image_id: str: id of the image being used to run :host_port: str: optional host port. Same as container port by default :envvars_map: dict: optional key-val map of environment variables :volume_map: dict: optional key-val map of host-container volume mounts :name: str: optional name to be assigned to the container :return: None """ container_port = _get_container_port(full_docker_path) if host_port is None: host_port = container_port _run_container(image_id, container_port, host_port, envvars_map, volume_map, name) def rm_container(container_id, force=False): """ Remove a container. :param container_id: str: the container's id or name :param force: bool: force the removal of the container (SIGKILL) :return None """ force_arg = ['-f'] if force else [] args = ['docker', 'rm'] + force_arg + [container_id] _run_quiet(args) def up(compose_path=None, allow_insecure_ssl=False): """ Build and run the entire app using services defined in docker-compose.yml. :param compose_path: str: optional alternate path to docker-compose.yml :param allow_insecure_ssl: bool: allow insecure connection to docker registry :return None """ file_opt = ['-f', '{}'.format(compose_path)] if compose_path else [] insecure_ssl_opt = ['--allow-insecure-ssl'] if allow_insecure_ssl else [] args = file_opt + ['up'] + insecure_ssl_opt LOG.debug(args) _compose_run(args) def _compose_run(args): utils.exec_cmd_live_output(['docker-compose'] + args) def get_container_lowlvl_info(container_id): """ Get a running container's low level info. :param container_id: str: the running container's id or name :return dict """ args = ['docker', 'inspect', container_id] info = json.loads(_run_quiet(args)) return info[0] def is_container_existent(container_id): """ Return whether container exists. :param container_id: str: the id or name of the container to check :return bool """ try: get_container_lowlvl_info(container_id) return True except CommandError: return False def is_running(container_id): """ Return whether container is currently running. :param container_id: str: the id or name of the container to check :return bool """ try: info = get_container_lowlvl_info(container_id) return info[STATE_KEY][RUNNING_KEY] except CommandError: return False def get_exposed_hostports(container_id): """ Get the host ports we exposed when we ran this container. :param container_id: str: the id or name of the running container :return list """ # Since we ran the container, we can guarantee that # one host port and one or more container ports are exposed. # Example of port_map: # # {'4848/tcp': None, # '8080/tcp': [{'HostPort': '8080', 'HostIp': '0.0.0.0'}], # '8181/tcp': None} try: port_map = _get_network_settings(container_id)[PORTS_KEY] or {} return utils.flatten([[p[HOST_PORT_KEY] for p in ports] for ports in six.itervalues(port_map) if ports]) except CommandError: # Not running return [] def version(): args = ['docker', '--version'] version_str = _run_quiet(args) # Format: Docker version 1.5.0, build a8a31ef return version_str.split()[2].strip(',') def compose_version(): args = ['docker-compose', '--version'] # Format: docker-compose 1.1.0 return _run_quiet(args).split()[-1] def _get_img_id_from_img_tag(img_tag): """ Get image id for a given image tag :param img_tag: str: image tag :return image id """ opts = ['-q'] args = ['docker', 'images'] + opts +[img_tag] output = _run_quiet(args) return output.split()[0] def _get_network_settings(container_id): info = get_container_lowlvl_info(container_id) return info[NETWORK_SETTINGS_KEY] def _pull_img(img): args = ['docker', 'pull', img] return _run_live(args) def _run_container(image_id, container_port, host_port, envvars_map, volume_map, name): port_mapping = '{}:{}'.format(host_port, container_port) interactive_opt = ['-i'] pseudotty_opt = ['-t'] rm_container_on_exit_opt = ['--rm'] port_opt = ['-p', port_mapping] envvar_opt = _get_env_opts(envvars_map) volume_opt = _get_volume_opts(volume_map) name_opt = ['--name', name] if name else [] opts = (interactive_opt + pseudotty_opt + rm_container_on_exit_opt + port_opt + envvar_opt + volume_opt + name_opt) args = ['docker', 'run'] + opts + [image_id] return _run_live(args) def _get_container_port(full_docker_path): return _fst_match_in_dockerfile(full_docker_path, lambda s: s.startswith(EXPOSE_CMD), strings['local.run.noportexposed'])[1] def _get_base_img(full_docker_path): return _fst_match_in_dockerfile(full_docker_path, lambda s: s.startswith(FROM_CMD), strings['local.run.nobaseimg'])[1] def _fst_match_in_dockerfile(full_docker_path, predicate, not_found_error_msg): raw_lines = fileoperations.readlines_from_text_file(full_docker_path) stripped_lines = (x.strip() for x in raw_lines) try: line = next(x for x in stripped_lines if predicate(x)) return line.split() except StopIteration: raise ValidationError(not_found_error_msg) def _is_tag_specified(img_name): return ':' in img_name def _get_env_opts(envvars_map): return _get_opts(envvars_map, '--env', '{}={}') def _get_volume_opts(volume_map): return _get_opts(volume_map, '-v', '{}:{}') def _get_opts(_map, opt_name, val_format): _map = _map or {} kv_pairs = six.iteritems(_map) return utils.flatten([[opt_name, val_format.format(k, v)] for k, v in kv_pairs]) def _run_quiet(args): try: return utils.exec_cmd_quiet(args) except CommandError as e: _handle_command_error(e) def _run_live(args): try: return utils.exec_cmd_live_output(args) except CommandError as e: _handle_command_error(e) def _handle_command_error(e): socket_perm_msg = "dial unix /var/run/docker.sock: permission denied." if socket_perm_msg in e.output: raise CommandError(strings['local.run.socketperms'], e.output, e.code) else: raise CommandError