Пример #1
0
    def __init__(self,
                 context,
                 spec,
                 build_status=None,
                 docker_version='auto'):
        self.context = context
        self.spec = spec
        self.repo_name = context.repository.split('/')[-1]
        self.commit_hash = context.source['commit']['hash']
        self.build_status = build_status or BuildStatus(
            bitbucket, context.source['repository']['full_name'],
            self.commit_hash, 'badwolf/test',
            url_for('log.build_log', sha=self.commit_hash, _external=True))

        self.docker = DockerClient(
            base_url=current_app.config['DOCKER_HOST'],
            timeout=current_app.config['DOCKER_API_TIMEOUT'],
            version=docker_version,
        )
        vault_url = spec.vault.url or current_app.config['VAULT_URL']
        vault_token = spec.vault.token or current_app.config['VAULT_TOKEN']
        if vault_url and vault_token:
            self.vault = hvac.Client(url=vault_url, token=vault_token)
        else:
            self.vault = None
Пример #2
0
 def __init__(self, context):
     self.context = context
     self.commit_hash = context.source['commit']['hash']
     self.build_status = BuildStatus(
         bitbucket, context.source['repository']['full_name'],
         self.commit_hash, 'badwolf/test',
         url_for('log.build_log', sha=self.commit_hash, _external=True))
Пример #3
0
    def deploy(self):
        if not self.providers:
            logger.info('No deploy provider active')
            return

        commit_hash = self.context.source['commit']['hash']
        run_after_deploy = False
        notification = self.spec.notification
        slack_webhook = notification.slack_webhook

        for provider_config in self.providers:
            provider_name = provider_config.provider
            provider_class = self.PROVIDERS.get(provider_name)
            if not provider_class:
                logger.warning('Provider %s not found', provider_name)
                continue

            provider = provider_class(self.working_dir, provider_config)
            if not provider.is_usable():
                logger.warning('Provider %s is not usable', provider_name)
                continue

            build_status = BuildStatus(
                bitbucket, self.context.source['repository']['full_name'],
                commit_hash, 'badwolf/deploy/{}'.format(provider_name),
                url_for('log.build_log',
                        sha=commit_hash,
                        task_id=self.context.task_id,
                        _external=True))
            self._update_build_status(
                build_status, 'INPROGRESS',
                '{} deploy in progress'.format(provider_name))
            succeed, output = provider.deploy()
            logger.info('Provider %s deploy %s,  output: \n%s', provider_name,
                        'succeed' if succeed else 'failed', output)

            state = 'SUCCESSFUL' if succeed else 'FAILED'
            self._update_build_status(
                build_status, state,
                '{} deploy {}'.format(provider_name, state.lower()))
            if succeed:
                run_after_deploy = True
                if slack_webhook and slack_webhook.on_success == 'always':
                    trigger_slack_webhook(slack_webhook.webhooks, self.context,
                                          provider, True)
            else:
                if slack_webhook and slack_webhook.on_failure == 'always':
                    trigger_slack_webhook(slack_webhook.webhooks, self.context,
                                          provider, False)

        # after deploy
        if not run_after_deploy or not self.spec.after_deploy:
            return

        for script in self.spec.after_deploy:
            exit_code, output = run_command(script, shell=True)
            logger.info(
                'After deploy command `%s` exit code: %s, output: \n %s',
                script, exit_code, output)
Пример #4
0
 def __init__(self, context):
     self.context = context
     self.commit_hash = context.source['commit']['hash']
     self.build_status = BuildStatus(
         bitbucket, context.repository, self.commit_hash, 'badwolf/test',
         url_for('log.build_log',
                 sha=self.commit_hash,
                 task_id=context.task_id,
                 _external=True))
     self.vault = None
Пример #5
0
def handle_pull_request_approved(payload):
    if not current_app.config['AUTO_MERGE_ENABLED']:
        return

    repo = payload['repository']
    pr = payload['pullrequest']
    pr_id = pr['id']
    title = pr['title'].lower()
    description = (pr['description'] or '').lower()

    for keyword in ('wip', 'merge skip', 'working in progress'):
        if keyword in title or keyword in description:
            logger.info('%s found, ignore auto merge.', keyword)
            return

    pull_request = PullRequest(
        bitbucket,
        repo['full_name']
    )
    try:
        pr_info = pull_request.get(pr_id)
    except BitbucketAPIError as exc:
        logger.exception('Error calling Bitbucket API')
        if exc.code != 404:
            sentry.captureException()
        return

    if pr_info['state'] != 'OPEN':
        return

    participants = pr_info['participants']
    approved_users = [u for u in participants if u['approved']]
    if len(approved_users) < current_app.config['AUTO_MERGE_APPROVAL_COUNT']:
        return

    commit_hash = pr_info['source']['commit']['hash']

    build_status = BuildStatus(
        bitbucket,
        pr_info['source']['repository']['full_name'],
        commit_hash,
        'badwolf/test',
        url_for('log.build_log', sha=commit_hash, _external=True)
    )
    message = 'Auto merge pull request #{}: {}'.format(pr_id, pr['title'])
    if description:
        message += '\n\n{}'.format(pr['description'])
    try:
        status = build_status.get()
        if status['state'] == 'SUCCESSFUL':
            pull_request.merge(pr_id, message)
    except BitbucketAPIError as exc:
        logger.exception('Error calling Bitbucket API')
        if exc.code != 404:
            sentry.captureException()
Пример #6
0
 def __init__(self, context, spec, working_dir=None):
     self.context = context
     self.spec = spec
     self.working_dir = working_dir or context.clone_path
     self.problems = Problems()
     self.pr = PullRequest(bitbucket, context.repository)
     commit_hash = context.source['commit']['hash']
     self.build_status = BuildStatus(
         bitbucket, context.source['repository']['full_name'], commit_hash,
         'badwolf/lint', 'https://bitbucket.org/{}/pull-requests/{}'.format(
             context.repository, context.pr_id))
Пример #7
0
 def __init__(self, context, spec, working_dir):
     self.context = context
     self.spec = spec
     self.working_dir = working_dir
     self.problems = Problems()
     self.pr = PullRequest(bitbucket, context.repository)
     commit_hash = context.source['commit']['hash']
     self.build_status = BuildStatus(
         bitbucket, context.source['repository']['full_name'], commit_hash,
         'badwolf/lint',
         url_for('log.lint_log', sha=commit_hash, _external=True))
Пример #8
0
    def __init__(self, context, lock):
        self.context = context
        self.lock = lock
        self.repo_full_name = context.repository
        self.repo_name = context.repository.split('/')[-1]
        self.task_id = str(uuid.uuid4())
        self.commit_hash = context.source['commit']['hash']
        self.build_status = BuildStatus(
            bitbucket, context.source['repository']['full_name'],
            self.commit_hash, 'badwolf/test',
            url_for('log.build_log', sha=self.commit_hash, _external=True))

        self.docker = Client(
            base_url=current_app.config['DOCKER_HOST'],
            timeout=current_app.config['DOCKER_API_TIMEOUT'],
        )
Пример #9
0
    def __init__(self, context, spec, docker_version='auto'):
        self.context = context
        self.spec = spec
        self.repo_name = context.repository.split('/')[-1]
        self.commit_hash = context.source['commit']['hash']
        self.build_status = BuildStatus(
            bitbucket,
            context.source['repository']['full_name'],
            self.commit_hash,
            'badwolf/test',
            url_for('log.build_log', sha=self.commit_hash, _external=True)
        )

        self.docker = Client(
            base_url=current_app.config['DOCKER_HOST'],
            timeout=current_app.config['DOCKER_API_TIMEOUT'],
            version=docker_version,
        )
Пример #10
0
def check_mergeable(context, pr_api, pr_info):
    pr_id = pr_info['id']
    merge_status = BuildStatus(
        bitbucket, pr_info['source']['repository']['full_name'],
        pr_info['source']['commit']['hash'], 'badwolf/pr/mergeable',
        'https://bitbucket.org/{}/pull-requests/{}'.format(
            context.repository, pr_id))
    notify = False
    status = {'state': None}
    try:
        status = merge_status.get()
    except BitbucketAPIError as e:
        if e.code != 404:
            raise
        notify = True
    else:
        if status['state'] == 'SUCCESSFUL':
            notify = True

    diff = pr_api.diff(pr_id, raw=True)
    if '+<<<<<<< destination:' not in diff:
        # Mergeable
        logger.info('Pull request #%s is mergeable', pr_id)
        if status['state'] != 'SUCCESSFUL':
            merge_status.update('SUCCESSFUL', 'Pull request is mergeable')
        return

    # Unmergeable
    if not notify:
        return

    logger.info('Pull request #%s is not mergeable', pr_id)
    merge_status.update('FAILED', 'Pull request is not mergeable')
    comment = (
        ':umbrella: The latest upstream changes(presumably {}) made this pull request unmergeable. '
        'Please resolve the merge conflicts.')
    matches = _MERGE_COMMIT_RE.search(context.message)
    if matches:
        comment = comment.format('pull request #{}'.format(matches.group(1)))
    else:
        comment = comment.format('commit {}'.format(
            context.source['commit']['hash']))
    pr_api.comment(pr_id, comment)
Пример #11
0
    def _save_artifacts(self, build_success):
        def _should_exclude(path):
            excluded = self.spec.artifacts.excludes
            if not excluded:
                return False

            for pattern in excluded:
                if fnmatch.fnmatch(path, pattern):
                    return True
            return False

        logger.info('Saving artifacts for repository %s', self.context.repository)
        paths = []
        for path in self.spec.artifacts.paths:
            if '$' not in path:
                paths.append(path)
            else:
                cmd = 'echo {}'.format(path)
                exit_code, output = run_command(cmd, cwd=self.context.clone_path, shell=True)
                if exit_code == 0:
                    paths.extend(x for x in output.strip().split(':') if x and not _should_exclude(x))
        if not paths:
            logger.info('No artifacts paths found for repository %s', self.context.repository)
            return

        artifacts_repo_path = os.path.join(
            current_app.config['BADWOLF_ARTIFACTS_DIR'],
            self.context.repository,
        )
        artifacts_commit_path = os.path.join(
            artifacts_repo_path,
            self.commit_hash
        )
        os.makedirs(artifacts_commit_path, exist_ok=True)
        artifacts_file = os.path.join(artifacts_commit_path, 'artifacts.tar.gz')
        file_added = False
        with tarfile.open(artifacts_file, 'w:gz') as tar:
            for path in paths:
                file_path = os.path.join(self.context.clone_path, path)
                try:
                    tar.add(file_path, path)
                except FileNotFoundError as exc:
                    logger.error(str(exc))
                else:
                    file_added = True
        if not file_added:
            try:
                shutil.rmtree(artifacts_commit_path, ignore_errors=True)
            except OSError:
                logger.exception('Error clean empty artifacts files')
            return

        run_command('shasum artifacts.tar.gz > SHASUM', cwd=artifacts_commit_path, shell=True)
        logger.info('Saved artifacts to %s', artifacts_commit_path)

        if build_success and self.context.type in ('tag', 'branch'):
            artifacts_branch_path = os.path.join(
                artifacts_repo_path,
                self.context.source['branch']['name']
            )
            os.makedirs(artifacts_branch_path, exist_ok=True)
            for name in ('artifacts.tar.gz', 'SHASUM'):
                commit_path = os.path.join(artifacts_commit_path, name)
                branch_path = os.path.join(artifacts_branch_path, name)
                try:
                    os.remove(branch_path)
                except OSError:
                    pass
                os.symlink(commit_path, branch_path)
            logger.info('Saved artifacts to %s', artifacts_branch_path)

        build_status = BuildStatus(
            bitbucket,
            self.context.repository,
            self.commit_hash,
            'badwolf/artifacts',
            url_for('artifacts.download_artifacts',
                    user=self.context.repo_owner,
                    repo=self.context.repo_name,
                    sha=self.commit_hash,
                    filename='artifacts.tar.gz',
                    _external=True)
        )
        build_status.update('SUCCESSFUL', description='Build artifacts saved')