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
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')
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, )
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 }
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()
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')
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')