Example #1
0
 def docker_build(self):
     self.build_update.emit("Starting Docker image build.")
     try:
         resp = self.__client.api.build(path="./core/Docker", dockerfile=self.dockerfile,
                                        tag=self.tag, quiet=False, rm=True)
         if isinstance(resp, str):
             return self.docker_run(self.__client.images.get(resp))
         last_event = None
         image_id = None
         result_stream, internal_stream = itertools.tee(json_stream(resp))
         for chunk in internal_stream:
             if 'error' in chunk:
                 self.build_error.emit(chunk['error'])
                 raise BuildError(chunk['error'], result_stream)
             if 'stream' in chunk:
                 self.build_progress.emit(chunk['stream'])
                 match = re.search(
                     r'(^Successfully built |sha256:)([0-9a-f]+)$',
                     chunk['stream']
                 )
                 if match:
                     image_id = match.group(2)
             last_event = chunk
         if image_id:
             return self.docker_run(image_id)
         raise BuildError(last_event or 'Unknown', result_stream)
     except Exception as e:
         self.build_error.emit("An exception occurred while starting Docker image building process: {}".format(e))
         self.build_finished.emit(1)
         return
Example #2
0
    def build_image(self, dockerfileobj):

        response = []
        print("  Docker build output:")
        # extend for multiplatform
        for line in self.builder.api.build(
                fileobj=dockerfileobj,
                rm=True,
                custom_context=True,
                platform=
                "[linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x]"
        ):
            response.append(line)
            event = list(json_stream([line]))[0]
            if 'stream' in event:
                print("    " + event['stream'].rstrip())
            elif 'status' in event:
                print("    " + event['status'].rstrip())
            elif 'error' in event:
                raise BuildError(event['error'], json_stream(response))

        events = list(json_stream(response))
        if not events:
            raise BuildError('Unknown build error', events)
        event = events[-1]
        if 'stream' in event:
            match = re.search(r'Successfully built ([0-9a-f]+)',
                              event.get('stream', ''))
            if match:
                image_id = match.group(1)
        if image_id:
            return image_id
        raise BuildError(event, events)
Example #3
0
    def build_image(self, **kwargs):
        resp = self.client.api.build(path=self.config.context,
                                     dockerfile=os.path.basename(
                                         self.config.dockerfile.name),
                                     tag=self.config.tag,
                                     buildargs=self.config.args,
                                     rm=True)
        if isinstance(resp, str):
            return self.client.images.get(resp)

        events = []
        for event in json_stream(resp):
            # TODO: Redirect image pull logs
            line = event.get('stream', '')
            self.redirect_output(line)
            events.append(event)

        if not events:
            raise BuildError('Unknown')
        event = events[-1]
        if 'stream' in event:
            match = re.search(r'(Successfully built |sha256:)([0-9a-f]+)',
                              event.get('stream', ''))
            if match:
                image_id = match.group(2)
                return self.client.images.get(image_id)

        raise BuildError(event.get('error') or event)
Example #4
0
    def _prepare_log_lines(self, log_line):
        raw = log_line.decode('utf-8').strip()
        raw_lines = raw.split('\n')
        log_lines = []
        for raw_line in raw_lines:
            try:
                json_line = json.loads(raw_line)

                if json_line.get('error'):
                    raise BuildError(str(json_line.get('error', json_line)))
                else:
                    if json_line.get('stream'):
                        log_lines.append('Build: {}'.format(
                            json_line['stream'].strip()))
                    elif json_line.get('status'):
                        log_lines.append('Push: {} {}'.format(
                            json_line['status'], json_line.get('progress')))
                    elif json_line.get('aux'):
                        log_lines.append('Push finished: {}'.format(
                            json_line.get('aux')))
                    else:
                        log_lines.append(str(json_line))
            except json.JSONDecodeError:
                log_lines.append('JSON decode error: {}'.format(raw_line))
        return log_lines
Example #5
0
def build(image_name, version, workspace, stream_handler=None):
    tag = ":".join([image_name, version])
    log_result = ''
    img = None
    try:
        resp = client.images.client.api.build(path=workspace, tag=tag)
        if isinstance(resp, six.string_types):
            return client.images.get(resp)
        last_event = None
        image_id = None
        result_stream, internal_stream = itertools.tee(json_stream(resp))
        for chunk in internal_stream:
            if stream_handler:
                stream_handler(chunk)
            if 'error' in chunk:
                raise BuildError(chunk['error'], result_stream)
            if 'stream' in chunk:
                match = re.search(
                    r'(^Successfully built |sha256:)([0-9a-f]+)$',
                    chunk['stream']
                )
                if match:
                    image_id = match.group(2)
            last_event = chunk
        if image_id:
            img = client.images.get(image_id)
        else:
            raise BuildError(last_event or 'Unknown', result_stream)
    except BuildError as e:
        log_result = ''.join([chunk.get('stream') or chunk.get('error') for chunk in e.build_log if
                              'stream' in chunk or 'error' in chunk])
        logger.error("BuildError while building {}@{} :\n{}".format(image_name, version, log_result))
    except APIError as e:
        log_result = str(e)
        logger.error("APIError while building {}@{} :\n{}".format(image_name, version, log_result))
    except ConnectionError as e:
        log_result = str(e)
        logger.error("ConnectionError  while building {}@{} :\n{}".format(image_name, version, log_result))
    except Exception as e:
        log_result = str(e)
        logger.error("Exception while building {}@{} :\n{}".format(image_name, version, log_result))

    return img, log_result
Example #6
0
    def build_logs(self, resp: t.Iterator, image_tag: str) -> None:
        """
        Stream build logs to stderr.

        Args:
            resp (:obj:`t.Iterator`):
                blocking generator from docker.api.build
            image_tag (:obj:`str`):
                given model server tags.
                Ex: bento-server:0.13.0-python3.8-debian-runtime

        Raises:
            docker.errors.BuildErrors:
                When errors occurs during build process. Usually
                this comes when generated Dockerfile are incorrect.
        """
        last_event: str = ""
        image_id: str = ""
        output: str = ""
        logs: t.List = []
        built_regex = re.compile(r"(^Successfully built |sha256:)([0-9a-f]+)$")
        try:
            while True:
                try:
                    # output logs to stdout
                    # https://docker-py.readthedocs.io/en/stable/user_guides/multiplex.html
                    output = next(resp).decode("utf-8")
                    json_output: t.Dict = json.loads(output.strip("\r\n"))
                    # output to stderr when running in docker
                    if "stream" in json_output:
                        sprint(json_output["stream"])
                        matched = built_regex.search(json_output["stream"])
                        if matched:
                            image_id = matched.group(2)
                        last_event = json_output["stream"]
                        logs.append(json_output)
                except StopIteration:
                    log.info(f"Successfully built {image_tag}.")
                    break
                except ValueError:
                    log.error(f"Errors while building image:\n{output}")
            if image_id:
                self._push_context[image_tag] = docker_client.images.get(
                    image_id)
            else:
                raise BuildError(last_event or "Unknown", logs)
        except BuildError as e:
            log.error(f"Failed to build {image_tag} :\n{e.msg}")
            for line in e.build_log:
                if "stream" in line:
                    sprint(line["stream"].strip())
            log.fatal("ABORTING due to failure!")
Example #7
0
    def _get_image(build_logger_obj):
        """Helper to check for build errors and return image. This method is
        in the DockerImage class so that errors are raised in the main thread.

        This method borrows from the higher-level API of docker-py.
        See https://github.com/docker/docker-py/pull/1581.
        """
        import re
        from docker.errors import BuildError

        if isinstance(build_logger_obj.generator, str):
            return client.images.get(build_logger_obj.generator)
        if not build_logger_obj.logs:
            return BuildError('Unknown')
        for event in build_logger_obj.logs:
            if 'stream' in event:
                match = re.search(r'(Successfully built |sha256:)([0-9a-f]+)',
                                  event.get('stream', ''))
                if match:
                    image_id = match.group(2)
                    return client.images.get(image_id)

        last_event = build_logger_obj.logs[-1]
        raise BuildError(last_event.get('error') or last_event)
    def test_build_image_fails_with_BuildError(self, generate_dockerfile_patch,
                                               path_patch, uuid_patch,
                                               create_tarball_patch):
        uuid_patch.uuid4.return_value = "uuid"
        generate_dockerfile_patch.return_value = "Dockerfile content"

        docker_full_path_mock = Mock()
        docker_full_path_mock.exists.return_value = False
        path_patch.return_value = docker_full_path_mock

        docker_client_mock = Mock()
        docker_client_mock.api.build.side_effect = BuildError(
            "buildError", "buildlog")
        layer_downloader_mock = Mock()
        layer_downloader_mock.layer_cache = "cached layers"

        tarball_fileobj = Mock()
        create_tarball_patch.return_value.__enter__.return_value = tarball_fileobj

        layer_version1 = Mock()
        layer_version1.codeuri = "somevalue"
        layer_version1.name = "name"

        dockerfile_mock = Mock()
        m = mock_open(dockerfile_mock)
        with patch("samcli.local.docker.lambda_image.open", m):
            with self.assertRaises(ImageBuildException):
                LambdaImage(layer_downloader_mock,
                            True,
                            False,
                            docker_client=docker_client_mock)._build_image(
                                "base_image", "docker_tag", [layer_version1],
                                True)

        handle = m()
        handle.write.assert_called_with("Dockerfile content")
        path_patch.assert_called_once_with("cached layers", "dockerfile_uuid")
        docker_client_mock.api.build.assert_called_once_with(
            fileobj=tarball_fileobj,
            rm=True,
            tag="docker_tag",
            pull=False,
            custom_context=True)

        docker_full_path_mock.unlink.assert_not_called()
def dbuild(path: str,
           image_tag: str,
           build_args=None,
           docker_client=docker.from_env(),
           verbose=True,
           nocache=False):
    """
    Adopted from https://github.com/docker/docker-py/blob/master/docker/models/images.py
    """
    try:
        logger.info(f"Begin build for {pjoin(path, 'Dockerfile')}")
        logger.info(
            f"dbuild(path={path}, image_tag={image_tag}, build_args={build_args}, "
            f"verbose={True}, nocache={nocache})")

        resp = docker_client.api.build(
            path=path,
            dockerfile='Dockerfile',
            tag=image_tag,
            buildargs=build_args,
            nocache=nocache,
        )
        meta = []

        result_stream, internal_stream = itertools.tee(json_stream(resp))
        last_event = None
        image_id = None
        last_layer_cmd = None
        last_layer_start_time = None
        last_layer_finish_time = None
        last_layer_id = None
        last_layer_logs = []

        ptrn_step = r'^Step (\d+\/\d+) : (.+)'
        ptrn_layer = r'^ ---> ([a-z0-9]+)'
        ptrn_success = r'(^Successfully built |sha256:)([0-9a-f]+)$'

        for chunk in internal_stream:
            logger.info(chunk)

            if 'error' in chunk:
                raise BuildError(chunk['error'], result_stream)

            if 'stream' in chunk:
                if verbose:
                    print(chunk['stream'], end='')

                raw_output = chunk['stream']
                if (match_step := re.search(ptrn_step,
                                            raw_output)) is not None:
                    if last_layer_cmd:
                        meta.append(
                            LayerMeta(
                                CMD=last_layer_cmd,
                                START_T=last_layer_start_time,
                                FINISH_T=last_layer_finish_time,
                                LOGS=last_layer_logs,
                                ID=last_layer_id,
                            )._asdict())
                        last_layer_cmd = None
                        last_layer_start_time = None
                        last_layer_finish_time = None
                        last_layer_id = None
                        last_layer_logs = []
                    last_layer_cmd = match_step.group(2)
                    last_layer_start_time = time.time()
                elif (match_layer := re.search(ptrn_layer,
                                               raw_output)) is not None:
                    last_layer_id = match_layer.group(1)
                    last_layer_finish_time = time.time()
                elif (match_success := re.search(ptrn_success,
                                                 raw_output)) is not None:
                    meta.append(
                        LayerMeta(
                            CMD=last_layer_cmd,
                            START_T=last_layer_start_time,
                            FINISH_T=last_layer_finish_time,
                            LOGS=last_layer_logs,
                            ID=last_layer_id,
                        )._asdict())
                    image_id = match_success.group(2)
                else:
                            FINISH_T=last_layer_finish_time,
                            LOGS=last_layer_logs,
                            ID=last_layer_id,
                        )._asdict())
                    image_id = match_success.group(2)
                else:
                    last_layer_logs.append(raw_output)

            last_event = chunk

        if image_id:
            image = docker_client.images.get(image_id)
            logger.info(f"Successfully finish build for {image}")
            return image, meta

        raise BuildError(last_event or 'Unknown', result_stream)

    except BuildError as err:
        print('!!! Build failed !!!')
        # for l in err.build_log:
        #     if 'stream' in l:
        #         print(' ', l['stream'], end='')
        logger.error(f"Build failed when building {image_tag}: {err.msg}")
        raise err

    except KeyboardInterrupt:
        logger.error('Interrupted')
        raise KeyboardInterrupt(f'{image_tag}')


class DockerStackBuilder:
Example #11
0
def build_image(docker_client: DockerClient,
                image_name: str,
                remove_image: bool = True,
                file: Optional[TextIO] = sys.stderr,
                spinner: bool = True,
                **kwargs):
    """
    Create a docker image (similar to docker build command)
    At the end, deletes the image (using rmi command)
    Args:
        docker_client: DockerClient to be used to create the image
        image_name: Name of the image to be created
        remove_image: boolean, whether or not to delete the image at the end, default as True
        file: a file-like object (stream); defaults to the current sys.stderr. if set to None, will disable printing
        spinner: boolean, whether or not to use spinner (default as True), note that this param is set to False in
        case `file` param is not None
    """
    if file is None:
        file = open(os.devnull, 'w')
    else:
        spinner = False  # spinner splits into multiple lines in case stream is being printed at the same time
    image_tag = f'{image_name}:test'
    yaspin_spinner = _get_spinner(spinner)
    with yaspin_spinner(f'Creating image {image_tag}...'):
        kwargs = {'tag': image_tag, 'rm': True, 'forcerm': True, **kwargs}
        build_log = docker_client.api.build(**kwargs)
        for msg_b in build_log:
            msgs = str(msg_b, 'utf-8').splitlines()
            for msg in msgs:
                try:
                    parse_msg = json.loads(msg)
                except JSONDecodeError:
                    raise DockerException('error at build logs')
                s = parse_msg.get('stream')
                if s:
                    print(s, end='', flush=True, file=file)
                else:
                    # runtime errors
                    error_detail = parse_msg.get('errorDetail')
                    # parse errors
                    error_msg = parse_msg.get('message')
                    # steps of the image creation
                    status = parse_msg.get('status')
                    # end of process, will contain the ID of the temporary container created at the end
                    aux = parse_msg.get('aux')
                    if error_detail is not None:
                        raise BuildError(reason=error_detail, build_log=None)
                    elif error_msg is not None:
                        raise DockerfileParseException(reason=error_msg,
                                                       build_log=None)
                    elif status is not None:
                        print(status, end='', flush=True, file=file)
                    elif aux is not None:
                        print(aux, end='', flush=True, file=file)
                    else:
                        raise DockerException(parse_msg)
        yield image_tag
        if remove_image:
            try:
                docker_client.api.remove_image(image_tag)
            except ImageNotFound:
                # if the image was already deleted
                pass
Example #12
0
def execute_plan(plan):
    plan.status.blocking = False

    module_path = os.path.join(plan.arguments['base_path'], plan.module)
    images = [tag.full_interp for tag in plan.arguments['tags']]

    first_image = images.pop(0)
    plan.status.description = 'build %s' % first_image

    client = docker.from_env(version='auto')

    logger.debug('building: path=%s, tag=%s, args=%r', module_path,
                 first_image, plan.arguments['build_args'])

    build_log = plan.arguments['build_log']
    if plan.arguments['log_file']:
        log_file = io.open(plan.arguments['log_file'], 'w', encoding='UTF-8')
    else:
        log_file = None

    # build phase
    stream = client.api.build(buildargs=plan.arguments['build_args'],
                              path=module_path,
                              rm=True,
                              tag=first_image,
                              decode=True)
    last_events = deque(maxlen=2)
    for event in stream:
        last_events.append(event)

        if 'error' in event:
            logger.error(event['error'])
            plan.status.description = 'error'

        if 'stream' in event:
            m = REGEX_DOCKER_BUILD_STEP.match(event['stream'])

            for line in event['stream'].strip().splitlines():
                if build_log:
                    logger.info('build %s: %s', plan.module, line)

                if log_file:
                    log_file.write(line)
                    if not line.endswith('\n'):
                        log_file.write(u'\n')
                    log_file.flush()
            if m:
                step = m.group(1)
                plan.status.current = int(step)

                start, end = m.span()
                cmd_snippet = event['stream'][end:20].strip()
                plan.status.description = 'build %s %s %s' % (
                    first_image, m.group(3), cmd_snippet)

    if log_file:
        log_file.close()

    # grabbed from docker-py/docker/models/images.py:ImageCollection.build
    if not last_events[-1]:
        raise BuildError('Unknown')

    # the last line must say success, otherwise the build failed
    image_id = None
    build_errors = []
    for event in last_events:
        m = REGEX_DOCKER_BUILD_SUCCESS.match(event.get('stream') or '')
        if event.get('error'):
            build_errors.append(event.get('error'))

        if m:
            image_id = m.group(2)

    if build_errors:
        raise BuildError(build_errors)

    if not image_id:
        if build_errors:
            raise BuildError('Build failed, errors: %r' % build_errors)
        else:
            raise BuildError('Build did not succeed. Last '
                             'line: %s' % last_events[-1])

    image = client.images.get(image_id)

    plan.artifacts.append(first_image)

    # tagging phase
    plan.status.current = plan.status.total
    for extra_tag in images:
        repo, tag = extra_tag.rsplit(':', 1)
        image.tag(repo, tag=tag)

        plan.artifacts.append(extra_tag)
Example #13
0
def deploy_demo(demo_id, demo_dir):
    """
    Checks for the existence of demo container and redploy it if it exist

    Args:
        demo_id: Demo ID for the demo to be deployed(this is a unique ID from
            origami database)
        demo_dir: Absolute path to the demo directory where it was unzipped.
    """
    logging.info('Starting task to deploy demo with id : {}'.format(demo_id))
    # Before doing anything get the previously created image if any.
    try:
        remove_demo_instance_if_exist(demo_id, 'redeploying')
    except OrigamiDockerConnectionError as e:
        logging.error(e)
        return

    demo = Demos.get_or_none(Demos.demo_id == demo_id)
    if not demo:
        demo_logs_uid = uuid.uuid4().hex
        demo = Demos(demo_id=demo_id, log_id=demo_logs_uid, status='deploying')

    # Get the dockerfile from the demo dir from ORIGAMI_CONFIG_HOME
    dockerfile_dir = os.path.join(os.environ['HOME'], ORIGAMI_CONFIG_DIR,
                                  ORIGAMI_DEMOS_DIRNAME, demo_id)
    try:
        # Here we are using low level API bindings provided by docker-py to
        # interact with docker daemon. This enables us to collect image build
        # Logs and provide them to user for debugging purposes.
        logging.info('Trying to build image for demo.')
        cli = APIClient(base_url=DOCKER_UNIX_SOCKET)
        response = [
            json.loads(line.decode().strip())
            for line in cli.build(path=dockerfile_dir)
        ]

        # Write build logs to log file.
        logfile = os.path.join(get_origami_static_dir(),
                               ORIGAMI_DEPLOY_LOGS_DIR, demo.log_id)
        with open(logfile, LOGS_FILE_MODE_REQ) as fp:
            json.dump(response, fp)

        final_res = response[-1]['stream'].strip()
        match_obj = re.match(r'Successfully built (.*)', final_res, re.M | re.I)
        image_id = None

        try:
            match_obj.group(1)
            # SHA256 of the built image.
            image_id = response[-2]['aux']['ID'][7:]
        except IndexError as e:
            raise BuildError(e)
        except Exception as e:
            logging.error(
                "Error while parsing SHA256 ID of the image : {}".format(e))
            raise BuildError(e)

        # This was without using low level dockerpy client, it did not provide
        # logs for the build process.
        # image = docker_client.images.build(path=dockerfile_dir)[0]

        logging.info('Image built : ID: {}'.format(image_id))
        demo.image_id = image_id

        # Run a new container instance for the demo.
        if not demo.port:
            port = get_a_free_port()
            logging.info('New port for demo is {}'.format(port))
            demo.port = port

        port_map = '{}/tcp'.format(ORIGAMI_WRAPPED_DEMO_PORT)
        cont = docker_client.containers.run(
            image_id,
            detach=True,
            name=demo_id,
            ports={port_map: demo.port},
            remove=True)

        logging.info('Demo deployed with container id : {}'.format(cont.id))
        demo.container_id = cont.id
        demo.status = 'running'

    except BuildError as e:
        logging.error('Error while building image for {} : {}'.format(
            demo_id, e))
        demo.status = 'error'
    except APIError as e:
        logging.error(
            'Error while communicating to to docker API: {}'.format(e))
        demo.status = 'error'

    demo.save()