<commit_before># coding=utf-8
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License 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.


"""Functions that wrap docker functions.

All functions take
    ctx: cloudify context
    client: docker client
as parameters.
"""


import re

import docker

from cloudify import exceptions


_ERR_MSG_UNKNOWN_IMAGE_IMPORT = 'Unknown error during image import'
_ERR_MSG_UNKNOWN_IMAGE_BUILD = 'Unknown error while building image'

# Postision of image id in image_import result
_IMAGE_IMPORT_ID_POSITION = 2
# Postision of error in image_import result
_IMAGE_IMPORT_ERROR_POSITION = 2
# Postision of image id in image_build result
_IMAGE_BUILD_ID_POSITION = 3

# Those ctx.properties that will not be relayed to container as
# environmental variables
_NON_ENV_KEYS = [
    'daemon_client',
    'image_import',
    'image_build',
    'container_create',
    'container_start',
    'container_stop',
    'container_remove'
]


def _is_image_id_valid(ctx, image_id):
    return re.match('^[a-f0-9]{12,64}$', image_id) is not None


def _get_import_image_id(ctx, client, import_image_output):
    # It is useful beacause
    # import_image returns long string where in the second line
    # after last status is image id
    try:
        output_line = import_image_output.split('\n')[-2]
    except IndexError:
        _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)
    position_of_last_status = output_line.rfind('status')
    if position_of_last_status < 0:
        # If there was an error, there is no 'status'
        # in second output line
        # error message is after last 'error'
        position_of_error = output_line.rfind('error')
        try:
            err_msg = output_line[position_of_error:].\
                split('"')[_IMAGE_IMPORT_ERROR_POSITION]
        except IndexError:
            _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)
        err_msg = 'Error during image import {}'.format(err_msg)
        _log_and_raise(ctx, client, err_msg)
    try:
        image_id = output_line[position_of_last_status:].\
            split('"')[_IMAGE_IMPORT_ID_POSITION]
    except IndexError:
        _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)
    else:
        if _is_image_id_valid(ctx, image_id):
            return image_id
        else:
            _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)


def _get_build_image_id(ctx, client, stream_generator):
    stream = None
    for stream in stream_generator:
        pass
    # Fourth word in a string stream is an id
    if stream is None:
        _log_and_raise(
            ctx,
            client,
            _ERR_MSG_UNKNOWN_IMAGE_BUILD,
            exceptions.RecoverableError
        )
    try:
        image_id = re.sub(r'[\W_]+', ' ', stream).\
            split()[_IMAGE_BUILD_ID_POSITION]
    except IndexError:
        _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_BUILD)
    if _is_image_id_valid(ctx, image_id):
        return image_id
    else:
        _log_and_raise(
            ctx,
            client,
            _ERR_MSG_UNKNOWN_IMAGE_BUILD
        )


def _get_container_or_raise(ctx, client):
    container = ctx.runtime_properties.get('container')
    if container is None:
        _log_and_raise(ctx, client, 'No container specified')
    return container


def _get_image_or_raise(ctx, client):
    image = ctx.runtime_properties.get('image')
    if image is None:
        _log_and_raise(ctx, client, 'No image specified')
    return image


def _log_container_info(ctx, message=''):
    if 'container' in ctx.runtime_properties:
        message = '{} {}'.format(message, ctx.runtime_properties['container'])
    ctx.logger.info(message)


def _log_and_raise(ctx,
                   client,
                   err_msg='',
                   exc_class=exceptions.NonRecoverableError):
    _log_error_container_logs(ctx, client, err_msg)
    raise exc_class(err_msg)


def _log_error_container_logs(ctx, client, message=''):
    container = ctx.runtime_properties.get('container')
    if container is not None:
        if message:
            message += '\n'
        message += 'Container: {}'.format(container)
        try:
            logs = client.logs(container)
        except docker.errors.APIError as e:
            ctx.logger.error(str(e))
        if logs:
            message += '\nLogs:\n{}'.format(logs)
    if message:
        ctx.logger.error(message)


def get_top_info(ctx, client):
    """Get container top info.

    Get container top info using docker top function with container id
    from ctx.properties['container'].

    Transforms data into a simple top table.

    Args:
        ctx (cloudify context)
        client (docker client)

    Returns:
        top_table (str)

    Raises:
        NonRecoverableError: when container in ctx.runtime_properties is None.

    """

    def format_as_table(ctx, top_dict):<commit_msg>Fixed `UnboundLocalError' in case of exception while logging an error<commit_after># coding=utf-8
#
#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License 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.


"""Functions that wrap docker functions.

All functions take
    ctx: cloudify context
    client: docker client
as parameters.
"""


import re

import docker

from cloudify import exceptions


_ERR_MSG_UNKNOWN_IMAGE_IMPORT = 'Unknown error during image import'
_ERR_MSG_UNKNOWN_IMAGE_BUILD = 'Unknown error while building image'

# Postision of image id in image_import result
_IMAGE_IMPORT_ID_POSITION = 2
# Postision of error in image_import result
_IMAGE_IMPORT_ERROR_POSITION = 2
# Postision of image id in image_build result
_IMAGE_BUILD_ID_POSITION = 3

# Those ctx.properties that will not be relayed to container as
# environmental variables
_NON_ENV_KEYS = [
    'daemon_client',
    'image_import',
    'image_build',
    'container_create',
    'container_start',
    'container_stop',
    'container_remove'
]


def _is_image_id_valid(ctx, image_id):
    return re.match('^[a-f0-9]{12,64}$', image_id) is not None


def _get_import_image_id(ctx, client, import_image_output):
    # It is useful beacause
    # import_image returns long string where in the second line
    # after last status is image id
    try:
        output_line = import_image_output.split('\n')[-2]
    except IndexError:
        _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)
    position_of_last_status = output_line.rfind('status')
    if position_of_last_status < 0:
        # If there was an error, there is no 'status'
        # in second output line
        # error message is after last 'error'
        position_of_error = output_line.rfind('error')
        try:
            err_msg = output_line[position_of_error:].\
                split('"')[_IMAGE_IMPORT_ERROR_POSITION]
        except IndexError:
            _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)
        err_msg = 'Error during image import {}'.format(err_msg)
        _log_and_raise(ctx, client, err_msg)
    try:
        image_id = output_line[position_of_last_status:].\
            split('"')[_IMAGE_IMPORT_ID_POSITION]
    except IndexError:
        _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)
    else:
        if _is_image_id_valid(ctx, image_id):
            return image_id
        else:
            _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_IMPORT)


def _get_build_image_id(ctx, client, stream_generator):
    stream = None
    for stream in stream_generator:
        pass
    # Fourth word in a string stream is an id
    if stream is None:
        _log_and_raise(
            ctx,
            client,
            _ERR_MSG_UNKNOWN_IMAGE_BUILD,
            exceptions.RecoverableError
        )
    try:
        image_id = re.sub(r'[\W_]+', ' ', stream).\
            split()[_IMAGE_BUILD_ID_POSITION]
    except IndexError:
        _log_and_raise(ctx, client, _ERR_MSG_UNKNOWN_IMAGE_BUILD)
    if _is_image_id_valid(ctx, image_id):
        return image_id
    else:
        _log_and_raise(
            ctx,
            client,
            _ERR_MSG_UNKNOWN_IMAGE_BUILD
        )


def _get_container_or_raise(ctx, client):
    container = ctx.runtime_properties.get('container')
    if container is None:
        _log_and_raise(ctx, client, 'No container specified')
    return container


def _get_image_or_raise(ctx, client):
    image = ctx.runtime_properties.get('image')
    if image is None:
        _log_and_raise(ctx, client, 'No image specified')
    return image


def _log_container_info(ctx, message=''):
    if 'container' in ctx.runtime_properties:
        message = '{} {}'.format(message, ctx.runtime_properties['container'])
    ctx.logger.info(message)


def _log_and_raise(ctx,
                   client,
                   err_msg='',
                   exc_class=exceptions.NonRecoverableError):
    _log_error_container_logs(ctx, client, err_msg)
    raise exc_class(err_msg)


def _log_error_container_logs(ctx, client, message=''):
    container = ctx.runtime_properties.get('container')
    if container is not None:
        if message:
            message += '\n'
        message += 'Container: {}'.format(container)
        try:
            logs = client.logs(container)
        except docker.errors.APIError as e:
            ctx.logger.error(str(e))
        else:
            if logs:
                message += '\nLogs:\n{}'.format(logs)
    if message:
        ctx.logger.error(message)


def get_top_info(ctx, client):
    """Get container top info.

    Get container top info using docker top function with container id
    from ctx.properties['container'].

    Transforms data into a simple top table.

    Args:
        ctx (cloudify context)
        client (docker client)

    Returns:
        top_table (str)

    Raises:
        NonRecoverableError: when container in ctx.runtime_properties is None.

    """

    def format_as_table(ctx, top_dict):
{'fraction_non_alphanumeric': 0.08239941208892156, 'fraction_numerical': 0.002939555392246923, 'mean_word_length': 2.872998932764141, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4915185', 'n_tokens_mistral': 3286, 'n_tokens_neox': 3250, 'n_words': 905}