Beispiel #1
0
    def _cleanup_uploads(self):
        """
        Performs cleanup on the blobupload table.
        """
        logger.debug("Performing blob upload cleanup")

        while True:
            # Find all blob uploads older than the threshold (typically a week) and delete them.
            with UseThenDisconnect(app.config):
                stale_upload = model.get_stale_blob_upload(DELETION_DATE_THRESHOLD)
                if stale_upload is None:
                    logger.debug("No additional stale blob uploads found")
                    return

            # Remove the stale upload from storage.
            logger.debug("Removing stale blob upload %s", stale_upload.uuid)
            assert stale_upload.created <= (datetime.utcnow() - DELETION_DATE_THRESHOLD)

            try:
                storage.cancel_chunked_upload(
                    [stale_upload.location_name], stale_upload.uuid, stale_upload.storage_metadata
                )
            except Exception as ex:
                logger.debug(
                    "Got error when trying to cancel chunked upload %s: %s",
                    stale_upload.uuid,
                    ex.message,
                )

            # Delete the stale upload's row.
            with UseThenDisconnect(app.config):
                model.delete_blob_upload(stale_upload)

            logger.debug("Removed stale blob upload %s", stale_upload.uuid)
Beispiel #2
0
def index_images(target_version, analyzer, token=None):
    """ Performs security indexing of all images in the database not scanned at the target version.
      If a token is provided, scanning will begin where the token indicates it previously completed.
  """
    iterator, next_token = model.candidates_to_scan(target_version,
                                                    start_token=token)
    if iterator is None:
        logger.debug("Found no additional images to scan")
        return None

    with UseThenDisconnect(app.config):
        for candidate, abt, num_remaining in iterator:
            try:
                analyzer.analyze_recursively(candidate)
            except PreemptedException:
                logger.info("Another worker pre-empted us for layer: %s",
                            candidate.id)
                abt.set()
            except APIRequestFailure:
                logger.exception("Security scanner service unavailable")
                return

            unscanned_images_gauge.Set(num_remaining)

    return next_token
Beispiel #3
0
    def _garbage_collection_repos(self, skip_lock_for_testing=False):
        """
        Performs garbage collection on repositories.
        """
        with UseThenDisconnect(app.config):
            policy = get_random_gc_policy()
            if policy is None:
                logger.debug("No GC policies found")
                return

            repo_ref = registry_model.find_repository_with_garbage(policy)
            if repo_ref is None:
                logger.debug("No repository with garbage found")
                return

            assert features.GARBAGE_COLLECTION

            try:
                with GlobalLock(
                        "REPO_GARBAGE_COLLECTION_%s" % repo_ref.id,
                        lock_ttl=REPOSITORY_GC_TIMEOUT + LOCK_TIMEOUT_PADDING,
                ) if not skip_lock_for_testing else empty_context():
                    try:
                        repository = Repository.get(id=repo_ref.id)
                    except Repository.DoesNotExist:
                        return

                    logger.debug("Starting GC of repository #%s (%s)",
                                 repository.id, repository.name)
                    garbage_collect_repo(repository)
                    logger.debug("Finished GC of repository #%s (%s)",
                                 repository.id, repository.name)
            except LockNotAcquiredException:
                logger.debug(
                    "Could not acquire repo lock for garbage collection")
Beispiel #4
0
    def perform_indexing(self, start_token=None):
        """
        Performs indexing of the next set of unindexed manifests/images.

        If start_token is given, the indexing should resume from that point. Returns a new start
        index for the next iteration of indexing. The tokens returned and given are assumed to be
        opaque outside of this implementation and should not be relied upon by the caller to conform
        to any particular format.
        """
        # NOTE: This import is in here because otherwise this class would depend upon app.
        # Its not great, but as this is intended to be legacy until its removed, its okay.
        from util.secscan.analyzer import PreemptedException

        iterator, next_token = self._candidates_to_scan(start_token)
        if iterator is None:
            logger.debug("Found no additional images to scan")
            return None

        with UseThenDisconnect(self.app.config):
            for candidate, abt, num_remaining in iterator:
                try:
                    self._analyzer.analyze_recursively(candidate)
                except PreemptedException:
                    logger.debug("Another worker pre-empted us for layer: %s",
                                 candidate.id)
                    abt.set()
                except APIRequestFailure:
                    logger.exception("Security scanner service unavailable")
                    return

                unscanned_images.set(num_remaining)

        return next_token
Beispiel #5
0
 def _report_stats(self):
     logger.debug("Reporting global stats")
     with UseThenDisconnect(app.config):
         repository_rows.set(get_repository_count())
         user_rows.set(get_active_user_count())
         org_rows.set(get_active_org_count())
         robot_rows.set(get_robot_count())
Beispiel #6
0
    def yield_log_rotation_context(self, cutoff_date, min_logs_per_rotation):
        """
        Yield a context manager for a group of outdated logs.
        """
        for log_model in LOG_MODELS:
            while True:
                with UseThenDisconnect(config.app_config):
                    start_id = get_stale_logs_start_id(log_model)

                    if start_id is None:
                        logger.warning("Failed to find start id")
                        break

                    logger.debug("Found starting ID %s", start_id)
                    lookup_end_id = start_id + min_logs_per_rotation
                    logs = [
                        log for log in get_stale_logs(start_id, lookup_end_id,
                                                      log_model, cutoff_date)
                    ]

                if not logs:
                    logger.debug("No further logs found")
                    break

                end_id = max([log.id for log in logs])
                context = DatabaseLogRotationContext(logs, log_model, start_id,
                                                     end_id)
                yield context
Beispiel #7
0
 def _load_repo_build(self):
     with UseThenDisconnect(app.config):
         try:
             return model.build.get_repository_build(self.build_uuid)
         except model.InvalidRepositoryBuildException:
             raise BuildJobLoadException(
                 "Could not load repository build with ID %s" % self.build_uuid
             )
Beispiel #8
0
 def _operation_func():
     try:
         with UseThenDisconnect(app.config):
             return operation_func()
     except Exception:
         logger.exception('Operation raised exception')
         if self._raven_client:
             logger.debug('Logging exception to Sentry')
             self._raven_client.captureException()
Beispiel #9
0
    def _report_stats(self):
        logger.debug('Reporting global stats')
        with UseThenDisconnect(app.config):
            # Repository count.
            metric_queue.repository_count.Set(model.get_repository_count())

            # User counts.
            metric_queue.user_count.Set(model.get_active_user_count())
            metric_queue.org_count.Set(model.get_active_org_count())
            metric_queue.robot_count.Set(model.get_robot_count())
Beispiel #10
0
 def _cleanup_queue(self):
     """ Performs garbage collection on the queueitem table. """
     with UseThenDisconnect(app.config):
         while True:
             # Find all queue items older than the threshold (typically a week) and delete them.
             expiration_threshold = datetime.now() - DELETION_DATE_THRESHOLD
             deleted_count = delete_expired(expiration_threshold,
                                            DELETION_COUNT_THRESHOLD,
                                            BATCH_SIZE)
             if deleted_count == 0:
                 return
    def _backfill_labels(self):
        with UseThenDisconnect(app.config):
            iterator = self._candidates_to_backfill()
            if iterator is None:
                logger.debug("Found no additional labels to backfill")
                time.sleep(10000)
                return None

            for candidate, abt, _ in iterator:
                if not backfill_label(candidate):
                    logger.info("Another worker pre-empted us for label: %s", candidate.id)
                    abt.set()
Beispiel #12
0
    def _backfill_tags(self):
        with UseThenDisconnect(app.config):
            iterator = self._candidates_to_backfill()
            if iterator is None:
                logger.debug('Found no additional tags to backfill')
                time.sleep(10000)
                return None

            for candidate, abt, _ in iterator:
                if not backfill_tag(candidate):
                    logger.info('Another worker pre-empted us for tag: %s',
                                candidate.id)
                    abt.set()
Beispiel #13
0
    def _garbage_collection_repos(self):
        """ Performs garbage collection on repositories. """
        with UseThenDisconnect(app.config):
            repository = find_repository_with_garbage(get_random_gc_policy())
            if repository is None:
                logger.debug('No repository with garbage found')
                return

            assert features.GARBAGE_COLLECTION

            logger.debug('Starting GC of repository #%s (%s)', repository.id,
                         repository.name)
            garbage_collect_repo(repository)
            logger.debug('Finished GC of repository #%s (%s)', repository.id,
                         repository.name)
Beispiel #14
0
  def _cleanup_uploads(self):
    """ Performs garbage collection on the blobupload table. """
    while True:
      # Find all blob uploads older than the threshold (typically a week) and delete them.
      with UseThenDisconnect(app.config):
        stale_upload = model.get_stale_blob_upload(DELETION_DATE_THRESHOLD)
        if stale_upload is None:
          logger.debug('No additional stale blob uploads found')
          return

      # Remove the stale upload from storage.
      logger.debug('Removing stale blob upload %s', stale_upload.uuid)
      try:
        storage.cancel_chunked_upload([stale_upload.location_name], stale_upload.uuid,
                                      stale_upload.storage_metadata)
      except Exception as ex:
        logger.debug('Got error when trying to cancel chunked upload %s: %s', stale_upload.uuid,
                     ex.message)

      # Delete the stale upload's row.
      with UseThenDisconnect(app.config):
        model.delete_blob_upload(stale_upload)

      logger.debug('Removed stale blob upload %s', stale_upload.uuid)
Beispiel #15
0
    def _determine_cached_tag_by_tag(self):
        """ Determines the cached tag by looking for one of the tags being built, and seeing if it
        exists in the repository. This is a fallback for when no comment information is available.
    """
        with UseThenDisconnect(app.config):
            tags = self.build_config.get("docker_tags", ["latest"])
            repository = RepositoryReference.for_repo_obj(self.repo_build.repository)
            matching_tag = registry_model.find_matching_tag(repository, tags)
            if matching_tag is not None:
                return matching_tag.name

            most_recent_tag = registry_model.get_most_recent_tag(repository)
            if most_recent_tag is not None:
                return most_recent_tag.name

            return None
Beispiel #16
0
    def send_notification(self,
                          kind,
                          error_message=None,
                          image_id=None,
                          manifest_digests=None):
        with UseThenDisconnect(app.config):
            tags = self.build_config.get("docker_tags", ["latest"])
            trigger = self.repo_build.trigger
            if trigger is not None and trigger.id is not None:
                trigger_kind = trigger.service.name
            else:
                trigger_kind = None

            event_data = {
                "build_id": self.repo_build.uuid,
                "build_name": self.repo_build.display_name,
                "docker_tags": tags,
                "trigger_id": trigger.uuid if trigger is not None else None,
                "trigger_kind": trigger_kind,
                "trigger_metadata":
                self.build_config.get("trigger_metadata", {}),
            }

            if image_id is not None:
                event_data["image_id"] = image_id

            if manifest_digests:
                event_data["manifest_digests"] = manifest_digests

            if error_message is not None:
                event_data["error_message"] = error_message

            # TODO: remove when more endpoints have been converted to using
            # interfaces
            repo = AttrDict({
                "namespace_name":
                self.repo_build.repository.namespace_user.username,
                "name":
                self.repo_build.repository.name,
            })
            spawn_notification(
                repo,
                kind,
                event_data,
                subpage="build/%s" % self.repo_build.uuid,
                pathargs=["build", self.repo_build.uuid],
            )
Beispiel #17
0
def update_phase_then_close(build_uuid, phase):
    """ A function to change the phase of a build """
    with UseThenDisconnect(config.app_config):
        try:
            build = _get_build_row(build_uuid)
        except RepositoryBuild.DoesNotExist:
            return False

        # Can't update a cancelled build
        if build.phase == BUILD_PHASE.CANCELLED:
            return False

        updated = (RepositoryBuild.update(phase=phase).where(
            RepositoryBuild.id == build.id,
            RepositoryBuild.phase == build.phase).execute())

        return updated > 0
Beispiel #18
0
    def send_notification(self,
                          kind,
                          error_message=None,
                          image_id=None,
                          manifest_digests=None):
        with UseThenDisconnect(app.config):
            tags = self.build_config.get('docker_tags', ['latest'])
            trigger = self.repo_build.trigger
            if trigger is not None and trigger.id is not None:
                trigger_kind = trigger.service.name
            else:
                trigger_kind = None

            event_data = {
                'build_id': self.repo_build.uuid,
                'build_name': self.repo_build.display_name,
                'docker_tags': tags,
                'trigger_id': trigger.uuid if trigger is not None else None,
                'trigger_kind': trigger_kind,
                'trigger_metadata':
                self.build_config.get('trigger_metadata', {})
            }

            if image_id is not None:
                event_data['image_id'] = image_id

            if manifest_digests:
                event_data['manifest_digests'] = manifest_digests

            if error_message is not None:
                event_data['error_message'] = error_message

            # TODO: remove when more endpoints have been converted to using
            # interfaces
            repo = AttrDict({
                'namespace_name':
                self.repo_build.repository.namespace_user.username,
                'name':
                self.repo_build.repository.name,
            })
            spawn_notification(repo,
                               kind,
                               event_data,
                               subpage='build/%s' % self.repo_build.uuid,
                               pathargs=['build', self.repo_build.uuid])
Beispiel #19
0
 def __exit__(self, ex_type, ex_value, ex_traceback):
     if ex_type is None and ex_value is None and ex_traceback is None:
         with UseThenDisconnect(config.app_config):
             logger.debug("Deleting logs from IDs %s to %s", self.start_id,
                          self.end_id)
             delete_stale_logs(self.start_id, self.end_id, self.log_model)
Beispiel #20
0
    def _build_complete(self, result):
        """ Wraps up a completed build. Handles any errors and calls self._build_finished. """
        build_id = self._current_job.repo_build.uuid

        try:
            # Retrieve the result. This will raise an ApplicationError on any error that occurred.
            result_value = result.result()
            kwargs = {}

            # Note: If we are hitting an older builder that didn't return ANY map data, then the result
            # value will be a bool instead of a proper CallResult object.
            # Therefore: we have a try-except guard here to ensure we don't hit this pitfall.
            try:
                kwargs = result_value.kwresults
            except:
                pass

            try:
                yield From(self._build_status.set_phase(BUILD_PHASE.COMPLETE))
            except InvalidRepositoryBuildException:
                logger.warning(
                    'Build %s was not found; repo was probably deleted',
                    build_id)
                raise Return()

            yield From(self._build_finished(BuildJobResult.COMPLETE))

            # Label the pushed manifests with the build metadata.
            manifest_digests = kwargs.get('digests') or []
            repository = registry_model.lookup_repository(
                self._current_job.namespace, self._current_job.repo_name)
            if repository is not None:
                for digest in manifest_digests:
                    with UseThenDisconnect(app.config):
                        manifest = registry_model.lookup_manifest_by_digest(
                            repository, digest, require_available=True)
                        if manifest is None:
                            continue

                        registry_model.create_manifest_label(
                            manifest, INTERNAL_LABEL_BUILD_UUID, build_id,
                            'internal', 'text/plain')

            # Send the notification that the build has completed successfully.
            self._current_job.send_notification(
                'build_success',
                image_id=kwargs.get('image_id'),
                manifest_digests=manifest_digests)
        except ApplicationError as aex:
            worker_error = WorkerError(aex.error, aex.kwargs.get('base_error'))

            # Write the error to the log.
            yield From(
                self._build_status.set_error(
                    worker_error.public_message(),
                    worker_error.extra_data(),
                    internal_error=worker_error.is_internal_error(),
                    requeued=self._current_job.has_retries_remaining()))

            # Send the notification that the build has failed.
            self._current_job.send_notification(
                'build_failure', error_message=worker_error.public_message())

            # Mark the build as completed.
            if worker_error.is_internal_error():
                logger.exception(
                    '[BUILD INTERNAL ERROR: Remote] Build ID: %s: %s',
                    build_id, worker_error.public_message())
                yield From(self._build_finished(BuildJobResult.INCOMPLETE))
            else:
                logger.debug('Got remote failure exception for build %s: %s',
                             build_id, aex)
                yield From(self._build_finished(BuildJobResult.ERROR))

        # Remove the current job.
        self._current_job = None