Ejemplo n.º 1
0
 def save_build_config(self):
     """Save config in the build object."""
     pk = self.data.build['id']
     config = self.data.config.as_dict()
     api_v2.build(pk).patch({
         'config': config,
     })
     self.data.build['config'] = config
Ejemplo n.º 2
0
    def update_build(self, state):
        self.data.build['state'] = state

        # Attempt to stop unicode errors on build reporting
        # for key, val in list(self.data.build.items()):
        #     if isinstance(val, bytes):
        #         self.data.build[key] = val.decode('utf-8', 'ignore')

        try:
            api_v2.build(self.data.build['id']).patch(self.data.build)
        except Exception:
            # NOTE: I think we should fail the build if we cannot update it
            # at this point otherwise, the data will be inconsistent and we
            # may be serving "new docs" but saying the "build have failed"
            log.exception('Unable to update build')
Ejemplo n.º 3
0
    def _check_concurrency_limit(self):
        try:
            response = api_v2.build.concurrent.get(
                project__slug=self.data.project.slug)
            concurrency_limit_reached = response.get('limit_reached', False)
            max_concurrent_builds = response.get(
                'max_concurrent',
                settings.RTD_MAX_CONCURRENT_BUILDS,
            )
        except Exception:
            log.exception(
                'Error while hitting/parsing API for concurrent limit checks from builder.',
                project_slug=self.data.project.slug,
                version_slug=self.data.version.slug,
            )
            concurrency_limit_reached = False
            max_concurrent_builds = settings.RTD_MAX_CONCURRENT_BUILDS

        if concurrency_limit_reached:
            # TODO: this could be handled in `on_retry` probably
            log.warning(
                'Delaying tasks due to concurrency limit.',
                project_slug=self.data.project.slug,
                version_slug=self.data.version.slug,
            )

            # This is done automatically on the environment context, but
            # we are executing this code before creating one
            api_v2.build(self.data.build['id']).patch({
                'error':
                BuildMaxConcurrencyError.message.format(
                    limit=max_concurrent_builds, ),
                'builder':
                socket.gethostname(),
            })
            self.retry(
                exc=BuildMaxConcurrencyError,
                throw=False,
                # We want to retry this build more times
                max_retries=25,
            )
Ejemplo n.º 4
0
    def get_build(build_pk):
        """
        Retrieve build object from API.

        :param build_pk: Build primary key
        """
        build = {}
        if build_pk:
            build = api_v2.build(build_pk).get()
        private_keys = [
            'project',
            'version',
            'resource_uri',
            'absolute_uri',
        ]
        # TODO: try to use the same technique than for ``APIProject``.
        return {
            key: val
            for key, val in build.items() if key not in private_keys
        }
Ejemplo n.º 5
0
 def _reset_build(self):
     # Reset build only if it has some commands already.
     if self.data.build.get('commands'):
         api_v2.build(self.data.build['id']).reset.post()
Ejemplo n.º 6
0
    def update_build(self, state=None):
        """
        Record a build by hitting the API.

        This step is skipped if we aren't recording the build. To avoid
        recording successful builds yet (for instance, running setup commands
        for the build), set the ``update_on_success`` argument to False on
        environment instantiation.

        If there was an error on the build, update the build regardless of
        whether ``update_on_success`` is ``True`` or not.
        """
        if not self.record:
            return None

        self.build['project'] = self.project.pk
        self.build['version'] = self.version.pk
        self.build['builder'] = socket.gethostname()
        self.build['state'] = state
        if self.done:
            self.build['success'] = self.successful

            # TODO drop exit_code and provide a more meaningful UX for error
            # reporting
            if self.failure and isinstance(
                    self.failure,
                    BuildEnvironmentException,
            ):
                self.build['exit_code'] = self.failure.status_code
            elif self.commands:
                self.build['exit_code'] = max([
                    cmd.exit_code for cmd in self.commands
                ])

        self.build['setup'] = self.build['setup_error'] = ''
        self.build['output'] = self.build['error'] = ''

        if self.start_time:
            build_length = (datetime.utcnow() - self.start_time)
            self.build['length'] = int(build_length.total_seconds())

        if self.failure is not None:
            # Surface a generic error if the class is not a
            # BuildEnvironmentError
            # yapf: disable
            if not isinstance(
                self.failure,
                (
                    BuildEnvironmentException,
                    BuildEnvironmentWarning,
                ),
            ):
                # yapf: enable
                log.error(
                    'Build failed with unhandled exception: %s',
                    str(self.failure),
                    extra={
                        'stack': True,
                        'tags': {
                            'build': self.build.get('id'),
                            'project': self.project.slug,
                            'version': self.version.slug,
                        },
                    },
                )
                self.failure = BuildEnvironmentError(
                    BuildEnvironmentError.GENERIC_WITH_BUILD_ID.format(
                        build_id=self.build['id'],
                    ),
                )
            self.build['error'] = str(self.failure)

        # Attempt to stop unicode errors on build reporting
        for key, val in list(self.build.items()):
            if isinstance(val, bytes):
                self.build[key] = val.decode('utf-8', 'ignore')

        # We are selective about when we update the build object here
        update_build = (
            # Build isn't done yet, we unconditionally update in this state
            not self.done
            # Build is done, but isn't successful, always update
            or (self.done and not self.successful)
            # Otherwise, are we explicitly to not update?
            or self.update_on_success
        )
        if update_build:
            try:
                api_v2.build(self.build['id']).put(self.build)
            except HttpClientError:
                log.exception(
                    'Unable to update build: id=%d',
                    self.build['id'],
                )
            except Exception:
                log.exception('Unknown build exception')
Ejemplo n.º 7
0
    def update_build(self, state=None):
        """
        Record a build by hitting the API.

        This step is skipped if we aren't recording the build. To avoid
        recording successful builds yet (for instance, running setup commands
        for the build), set the ``update_on_success`` argument to False on
        environment instantiation.

        If there was an error on the build, update the build regardless of
        whether ``update_on_success`` is ``True`` or not.
        """
        if not self.record:
            return None

        self.build['project'] = self.project.pk
        self.build['version'] = self.version.pk
        self.build['builder'] = socket.gethostname()
        self.build['state'] = state
        if self.done:
            self.build['success'] = self.successful

            # TODO drop exit_code and provide a more meaningful UX for error
            # reporting
            if self.failure and isinstance(
                    self.failure,
                    BuildEnvironmentException,
            ):
                self.build['exit_code'] = self.failure.status_code
            elif self.commands:
                self.build['exit_code'] = max([
                    cmd.exit_code for cmd in self.commands
                ])

        self.build['setup'] = self.build['setup_error'] = ''
        self.build['output'] = self.build['error'] = ''

        if self.start_time:
            build_length = (datetime.utcnow() - self.start_time)
            self.build['length'] = int(build_length.total_seconds())

        if self.failure is not None:
            # Surface a generic error if the class is not a
            # BuildEnvironmentError
            # yapf: disable
            if not isinstance(
                self.failure,
                (
                    BuildEnvironmentException,
                    BuildEnvironmentWarning,
                ),
            ):
                # yapf: enable
                log.error(
                    'Build failed with unhandled exception: %s',
                    str(self.failure),
                    extra={
                        'stack': True,
                        'tags': {
                            'build': self.build.get('id'),
                            'project': self.project.slug,
                            'version': self.version.slug,
                        },
                    },
                )
                self.failure = BuildEnvironmentError(
                    BuildEnvironmentError.GENERIC_WITH_BUILD_ID.format(
                        build_id=self.build['id'],
                    ),
                )
            self.build['error'] = str(self.failure)

        # Attempt to stop unicode errors on build reporting
        for key, val in list(self.build.items()):
            if isinstance(val, bytes):
                self.build[key] = val.decode('utf-8', 'ignore')

        # We are selective about when we update the build object here
        update_build = (
            # Build isn't done yet, we unconditionally update in this state
            not self.done
            # Build is done, but isn't successful, always update
            or (self.done and not self.successful)
            # Otherwise, are we explicitly to not update?
            or self.update_on_success
        )
        if update_build:
            try:
                api_v2.build(self.build['id']).put(self.build)
            except HttpClientError:
                log.exception(
                    'Unable to update build: id=%d',
                    self.build['id'],
                )
            except Exception:
                log.exception('Unknown build exception')