Example #1
0
def do_job(hook_call_id):
    """A Celery task that does a job specified by a hook call.

    Creates a :class:`Job` instance and executes a build script prescribed
    by a triggered :class:`Hook`. Also sends job output to :attr:`Job.task_uuid`
    Redis pub-sub channel and updates build status.

    :param hook_call_id: int, :class:`HookCall` identifier
    """
    hook_call = HookCall.query.get(hook_call_id)
    assert hook_call, 'HookCall#{} does not exist.'.format(hook_call_id)

    job = Job(
        build=hook_call.build,
        hook_call=hook_call,
        task_uuid=do_job.request.id)
    db.session.add(job)
    job.started()
    db.session.commit()

    logger.info('start sleeping for some time...')
    while(True):
      sleep(5)
      logger.info('zzzzzz...')
    logger.info('done sleeping')

    hook = hook_call.hook
    project = hook.project
    config = current_app.config

    redis_client = redis.StrictRedis(host=config['KOZMIC_REDIS_HOST'],
                                     port=config['KOZMIC_REDIS_PORT'],
                                     db=config['KOZMIC_REDIS_DATABASE'])
    publisher = Publisher(redis_client=redis_client, channel=job.task_uuid)

    stdout = ''
    try:
        kwargs = dict(
            publisher=publisher,
            stall_timeout=config['KOZMIC_STALL_TIMEOUT'],
            clone_url=(project.gh_https_clone_url if project.is_public else
                       project.gh_ssh_clone_url),
            commit_sha=hook_call.build.gh_commit_sha)

        message = 'Pulling "{}" Docker image...'.format(hook.docker_image)
        logger.info(message)
        publisher.publish(message)
        stdout = message + '\n'

        try:
            logger.info(docker.base_url)
            docker.pull(hook.docker_image)
            # Make sure that image has been successfully pulled by calling
            # `inspect_image` on it:
            docker.inspect_image(hook.docker_image)
        except DockerAPIError as e:
            logger.info('Failed to pull %s: %s.', hook.docker_image, e)
            job.finished(1)
            job.stdout = str(e)
            db.session.commit()
            return
        else:
            message = '"{}" image has been pulled.'.format(hook.docker_image)
            logger.info(message)
            publisher.publish(message)

        #if not project.is_public:
        #    project.deploy_key.ensure()

        #if not project.is_public:
        #    kwargs['deploy_key'] = (
        #        project.deploy_key.rsa_private_key,
        #        project.passphrase)

        if job.hook_call.hook.install_script:
            cached_image = 'kozmic-cache/{}'.format(job.get_cache_id())
            cached_image_tag = str(project.id)
            if does_docker_image_exist(cached_image, cached_image_tag):
                install_stdout = ('Skipping install script as tracked files '
                                  'did not change...')
                publisher.publish(install_stdout)
                stdout += install_stdout + '\n'
            else:
                with _run(docker_image=hook.docker_image,
                          script=hook.install_script,
                          remove_container=False,
                          **kwargs) as (return_code, install_stdout, container):
                    stdout += install_stdout
                    if return_code == 0:
                        # Install script has finished successfully. So we
                        # promote the resulting container to an image that
                        # we will use for running the build script in
                        # this and consequent jobs
                        docker.commit(container['Id'], repository=cached_image,
                                      tag=cached_image_tag)
                        docker.remove_container(container)
                    else:
                        job.finished(return_code)
                        job.stdout = stdout
                        db.session.commit()
                        return
                assert docker.images(cached_image)
            docker_image = cached_image + ':' + cached_image_tag
        else:
            docker_image = job.hook_call.hook.docker_image

        with _run(docker_image=docker_image,
                  script=hook.build_script,
                  remove_container=True,
                  **kwargs) as (return_code, docker_stdout, container):
            job.finished(return_code)
            if len(docker_stdout) > 1000000:
                build_stdout = docker_stdout[:200000] + docker_stdout[-800000:]
            else:
                build_stdout = docker_stdout

            job.stdout = stdout + build_stdout
            db.session.commit()
            return
    finally:
        publisher.finish()
Example #2
0
def _run(publisher, stall_timeout, clone_url, commit_sha,
         docker_image, script, deploy_key=None, remove_container=True):
    yielded = False
    stdout = ''
    try:
        with create_temp_dir() as working_dir:
            message_queue = Queue.Queue()
            builder = Builder(
                docker=docker._get_current_object(),  # `docker` is a local proxy
                deploy_key=deploy_key,
                clone_url=clone_url,
                commit_sha=commit_sha,
                docker_image=docker_image,
                script=script,
                working_dir=working_dir,
                message_queue=message_queue)

            log_path = os.path.join(working_dir, 'script.log')
            stop_reason = ''
            try:
                # Start Builder and wait until it will create the container
                builder.start()
                container = message_queue.get(block=True, timeout=60)

                # Now the container id is known and we can pass it to Tailer
                tailer = Tailer(
                    log_path=log_path,
                    publisher=publisher,
                    container=container,
                    kill_timeout=stall_timeout)
                tailer.start()
                try:
                    # Tell Builder to continue and wait for it to finish
                    message_queue.task_done()
                    builder.join()
                finally:
                    tailer.stop()
                    if tailer.has_killed_container:
                        stop_reason = '\nSorry, your script has stalled and been killed.\n'
            finally:
                if builder.container and remove_container:
                    docker.remove_container(builder.container)

                if os.path.exists(log_path):
                    with open(log_path, 'r') as log:
                        stdout = log.read()

                assert ((builder.return_code is not None) ^
                        (builder.exc_info is not None))
                if builder.exc_info:
                    # Re-raise exception happened in builder
                    # (it will be catched in the outer try-except)
                    raise builder.exc_info[1], None, builder.exc_info[2]
                else:
                    try:
                        yield (builder.return_code,
                               stdout + stop_reason,
                               builder.container)
                    except:
                        raise
                    finally:
                        yielded = True  # otherwise we get "generator didn't
                                        # stop after throw()" error if nested
                                        # code raised exception
    except:
        stdout += ('\nSorry, something went wrong. We are notified of '
                   'the issue and will fix it soon.')
        exc_type, exc_value, exc_traceback = sys.exc_info()
        lines = traceback.format_exception(exc_type, exc_value, exc_traceback)
        stdout += ('\n'.join('!! ' + line for line in lines))
        if not yielded:
            yield 1, stdout, None
        raise