Exemple #1
0
def _is_storage_orphaned(candidate_id):
    """
    Returns the whether the given candidate storage ID is orphaned. Must be executed
    under a transaction.
    """
    with ensure_under_transaction():
        try:
            ManifestBlob.get(blob=candidate_id)
            return False
        except ManifestBlob.DoesNotExist:
            pass

        try:
            Image.get(storage=candidate_id)
            return False
        except Image.DoesNotExist:
            pass

        try:
            UploadedBlob.get(blob=candidate_id)
            return False
        except UploadedBlob.DoesNotExist:
            pass

    return True
Exemple #2
0
def _populate_manifest_and_blobs(repository,
                                 manifest,
                                 storage_id_map,
                                 leaf_layer_id=None):
    leaf_layer_id = leaf_layer_id or manifest.leaf_layer_v1_image_id
    try:
        legacy_image = Image.get(Image.docker_image_id == leaf_layer_id,
                                 Image.repository == repository)
    except Image.DoesNotExist:
        raise DataModelException("Invalid image with id: %s" % leaf_layer_id)

    storage_ids = set()
    for blob_digest in manifest.local_blob_digests:
        image_storage_id = storage_id_map.get(blob_digest)
        if image_storage_id is None:
            logger.error("Missing blob for manifest `%s` in: %s", blob_digest,
                         storage_id_map)
            raise DataModelException("Missing blob for manifest `%s`" %
                                     blob_digest)

        if image_storage_id in storage_ids:
            continue

        storage_ids.add(image_storage_id)

    return populate_manifest(repository, manifest, legacy_image, storage_ids)
Exemple #3
0
def test_create_temp_tag_deleted_repo(initialized_db):
    repo = model.repository.get_repository("devtable", "simple")
    repo.state = RepositoryState.MARKED_FOR_DELETION
    repo.save()

    image = Image.get(repository=repo)
    assert model.tag.create_temporary_hidden_tag(repo, image, 10000000) is None
def test_load_security_information_api_responses(secscan_api_response,
                                                 initialized_db):
    repository_ref = registry_model.lookup_repository("devtable", "simple")
    tag = registry_model.get_repo_tag(repository_ref,
                                      "latest",
                                      include_legacy_image=True)
    manifest = registry_model.get_manifest_for_tag(tag,
                                                   backfill_if_necessary=True,
                                                   include_legacy_image=True)
    set_secscan_status(Image.get(id=manifest.legacy_image._db_id), True, 3)

    secscan = V2SecurityScanner(app, instance_keys, storage)
    secscan._legacy_secscan_api = mock.Mock()
    secscan._legacy_secscan_api.get_layer_data.return_value = secscan_api_response

    security_information = secscan.load_security_information(
        manifest).security_information

    assert isinstance(security_information, SecurityInformation)
    assert security_information.Layer.Name == secscan_api_response[
        "Layer"].get("Name", "")
    assert security_information.Layer.ParentName == secscan_api_response[
        "Layer"].get("ParentName", "")
    assert security_information.Layer.IndexedByVersion == secscan_api_response[
        "Layer"].get("IndexedByVersion", None)
    assert len(security_information.Layer.Features) == len(
        secscan_api_response["Layer"].get("Features", []))
Exemple #5
0
def synthesize_v1_image(
    repo,
    image_storage_id,
    storage_image_size,
    docker_image_id,
    created_date_str,
    comment,
    command,
    v1_json_metadata,
    parent_image=None,
):
    """
    Find an existing image with this docker image id, and if none exists, write one with the
    specified metadata.
    """
    ancestors = "/"
    if parent_image is not None:
        ancestors = "{0}{1}/".format(parent_image.ancestors, parent_image.id)

    created = None
    if created_date_str is not None:
        try:
            created = dateutil.parser.parse(created_date_str).replace(tzinfo=None)
        except:
            # parse raises different exceptions, so we cannot use a specific kind of handler here.
            pass

    # Get the aggregate size for the image.
    aggregate_size = _basequery.calculate_image_aggregate_size(
        ancestors, storage_image_size, parent_image
    )

    try:
        return Image.create(
            docker_image_id=docker_image_id,
            ancestors=ancestors,
            comment=comment,
            command=command,
            v1_json_metadata=v1_json_metadata,
            created=created,
            storage=image_storage_id,
            repository=repo,
            parent=parent_image,
            aggregate_size=aggregate_size,
        )
    except IntegrityError:
        return Image.get(docker_image_id=docker_image_id, repository=repo)
Exemple #6
0
    def _namespace_from_kwargs(self, args_dict):
        if "namespace_name" in args_dict:
            return args_dict["namespace_name"]

        if "repository_ref" in args_dict:
            return args_dict["repository_ref"].namespace_name

        if "tag" in args_dict:
            return args_dict["tag"].repository.namespace_name

        if "manifest" in args_dict:
            manifest = args_dict["manifest"]
            if manifest._is_tag_manifest:
                return TagManifest.get(
                    id=manifest._db_id).tag.repository.namespace_user.username
            else:
                return Manifest.get(
                    id=manifest._db_id).repository.namespace_user.username

        if "manifest_or_legacy_image" in args_dict:
            manifest_or_legacy_image = args_dict["manifest_or_legacy_image"]
            if isinstance(manifest_or_legacy_image, LegacyImage):
                return Image.get(id=manifest_or_legacy_image._db_id
                                 ).repository.namespace_user.username
            else:
                manifest = manifest_or_legacy_image
                if manifest._is_tag_manifest:
                    return TagManifest.get(
                        id=manifest._db_id
                    ).tag.repository.namespace_user.username
                else:
                    return Manifest.get(
                        id=manifest._db_id).repository.namespace_user.username

        if "derived_image" in args_dict:
            return DerivedStorageForImage.get(
                id=args_dict["derived_image"]._db_id
            ).source_image.repository.namespace_user.username

        if "blob" in args_dict:
            return ""  # Blob functions are shared, so no need to do anything.

        if "blob_upload" in args_dict:
            return ""  # Blob functions are shared, so no need to do anything.

        raise Exception("Unknown namespace for dict `%s`" % args_dict)
Exemple #7
0
def compute_layer_id(layer):
  """ Returns the ID for the layer in the security scanner. """
  # NOTE: this is temporary until we switch to Clair V3.
  if isinstance(layer, ManifestDataType):
    if layer._is_tag_manifest:
      layer = TagManifest.get(id=layer._db_id).tag.image
    else:
      manifest = Manifest.get(id=layer._db_id)
      try:
        layer = ManifestLegacyImage.get(manifest=manifest).image
      except ManifestLegacyImage.DoesNotExist:
        return None
  elif isinstance(layer, LegacyImage):
    layer = Image.get(id=layer._db_id)

  assert layer.docker_image_id
  assert layer.storage.uuid
  return '%s.%s' % (layer.docker_image_id, layer.storage.uuid)
Exemple #8
0
  def _namespace_from_kwargs(self, args_dict):
    if 'namespace_name' in args_dict:
      return args_dict['namespace_name']

    if 'repository_ref' in args_dict:
      return args_dict['repository_ref'].namespace_name

    if 'tag' in args_dict:
      return args_dict['tag'].repository.namespace_name

    if 'manifest' in args_dict:
      manifest = args_dict['manifest']
      if manifest._is_tag_manifest:
        return TagManifest.get(id=manifest._db_id).tag.repository.namespace_user.username
      else:
        return Manifest.get(id=manifest._db_id).repository.namespace_user.username

    if 'manifest_or_legacy_image' in args_dict:
      manifest_or_legacy_image = args_dict['manifest_or_legacy_image']
      if isinstance(manifest_or_legacy_image, LegacyImage):
        return Image.get(id=manifest_or_legacy_image._db_id).repository.namespace_user.username
      else:
        manifest = manifest_or_legacy_image
        if manifest._is_tag_manifest:
          return TagManifest.get(id=manifest._db_id).tag.repository.namespace_user.username
        else:
          return Manifest.get(id=manifest._db_id).repository.namespace_user.username

    if 'derived_image' in args_dict:
      return (DerivedStorageForImage
              .get(id=args_dict['derived_image']._db_id)
              .source_image
              .repository
              .namespace_user
              .username)

    if 'blob' in args_dict:
      return '' # Blob functions are shared, so no need to do anything.

    if 'blob_upload' in args_dict:
      return '' # Blob functions are shared, so no need to do anything.

    raise Exception('Unknown namespace for dict `%s`' % args_dict)
Exemple #9
0
def assert_gc_integrity(expect_storage_removed=True):
    """
    Specialized assertion for ensuring that GC cleans up all dangling storages and labels, invokes
    the callback for images removed and doesn't invoke the callback for images *not* removed.
    """

    # Add a callback for when images are removed.
    removed_image_storages = []
    remove_callback = model.config.register_image_cleanup_callback(
        removed_image_storages.extend)

    # Store existing storages. We won't verify these for existence because they
    # were likely created as test data.
    existing_digests = set()
    for storage_row in ImageStorage.select():
        if storage_row.cas_path:
            existing_digests.add(storage_row.content_checksum)

    for blob_row in ApprBlob.select():
        existing_digests.add(blob_row.digest)

    # Store the number of dangling objects.
    existing_storage_count = _get_dangling_storage_count()
    existing_label_count = _get_dangling_label_count()
    existing_manifest_count = _get_dangling_manifest_count()

    # Yield to the GC test.
    with check_transitive_modifications():
        try:
            yield
        finally:
            remove_callback()

    # Ensure the number of dangling storages, manifests and labels has not changed.
    updated_storage_count = _get_dangling_storage_count()
    assert updated_storage_count == existing_storage_count

    updated_label_count = _get_dangling_label_count()
    assert updated_label_count == existing_label_count, _get_dangling_labels()

    updated_manifest_count = _get_dangling_manifest_count()
    assert updated_manifest_count == existing_manifest_count

    # Ensure that for each call to the image+storage cleanup callback, the image and its
    # storage is not found *anywhere* in the database.
    for removed_image_and_storage in removed_image_storages:
        assert isinstance(removed_image_and_storage, Image)

        try:
            # NOTE: SQLite can and will reuse AUTOINCREMENT IDs occasionally, so if we find a row
            # with the same ID, make sure it does not have the same Docker Image ID.
            # See: https://www.sqlite.org/autoinc.html
            found_image = Image.get(id=removed_image_and_storage.id)
            assert (found_image.docker_image_id !=
                    removed_image_and_storage.docker_image_id
                    ), "Found unexpected removed image %s under repo %s" % (
                        found_image.id,
                        found_image.repository,
                    )
        except Image.DoesNotExist:
            pass

        # Ensure that image storages are only removed if not shared.
        shared = Image.select().where(
            Image.storage == removed_image_and_storage.storage_id).count()
        if shared == 0:
            shared = (ManifestBlob.select().where(
                ManifestBlob.blob ==
                removed_image_and_storage.storage_id).count())

        if shared == 0:
            shared = (UploadedBlob.select().where(
                UploadedBlob.blob ==
                removed_image_and_storage.storage_id).count())

        if shared == 0:
            with pytest.raises(ImageStorage.DoesNotExist):
                ImageStorage.get(id=removed_image_and_storage.storage_id)

            with pytest.raises(ImageStorage.DoesNotExist):
                ImageStorage.get(uuid=removed_image_and_storage.storage.uuid)

    # Ensure all CAS storage is in the storage engine.
    preferred = storage.preferred_locations[0]
    for storage_row in ImageStorage.select():
        if storage_row.content_checksum in existing_digests:
            continue

        if storage_row.cas_path:
            storage.get_content({preferred},
                                storage.blob_path(
                                    storage_row.content_checksum))

    for blob_row in ApprBlob.select():
        if blob_row.digest in existing_digests:
            continue

        storage.get_content({preferred}, storage.blob_path(blob_row.digest))

    # Ensure all tags have valid manifests.
    for manifest in {t.manifest for t in Tag.select()}:
        # Ensure that the manifest's blobs all exist.
        found_blobs = {
            b.blob.content_checksum
            for b in ManifestBlob.select().where(
                ManifestBlob.manifest == manifest)
        }

        parsed = parse_manifest_from_bytes(
            Bytes.for_string_or_unicode(manifest.manifest_bytes),
            manifest.media_type.name)
        assert set(parsed.local_blob_digests) == found_blobs
Exemple #10
0
def assert_gc_integrity(expect_storage_removed=True, check_oci_tags=True):
    """ Specialized assertion for ensuring that GC cleans up all dangling storages
      and labels, invokes the callback for images removed and doesn't invoke the
      callback for images *not* removed.
  """
    # Add a callback for when images are removed.
    removed_image_storages = []
    model.config.register_image_cleanup_callback(removed_image_storages.extend)

    # Store the number of dangling storages and labels.
    existing_storage_count = _get_dangling_storage_count()
    existing_label_count = _get_dangling_label_count()
    existing_manifest_count = _get_dangling_manifest_count()
    yield

    # Ensure the number of dangling storages, manifests and labels has not changed.
    updated_storage_count = _get_dangling_storage_count()
    assert updated_storage_count == existing_storage_count

    updated_label_count = _get_dangling_label_count()
    assert updated_label_count == existing_label_count, _get_dangling_labels()

    updated_manifest_count = _get_dangling_manifest_count()
    assert updated_manifest_count == existing_manifest_count

    # Ensure that for each call to the image+storage cleanup callback, the image and its
    # storage is not found *anywhere* in the database.
    for removed_image_and_storage in removed_image_storages:
        with pytest.raises(Image.DoesNotExist):
            Image.get(id=removed_image_and_storage.id)

        # Ensure that image storages are only removed if not shared.
        shared = Image.select().where(
            Image.storage == removed_image_and_storage.storage_id).count()
        if shared == 0:
            shared = (ManifestBlob.select().where(
                ManifestBlob.blob ==
                removed_image_and_storage.storage_id).count())

        if shared == 0:
            with pytest.raises(ImageStorage.DoesNotExist):
                ImageStorage.get(id=removed_image_and_storage.storage_id)

            with pytest.raises(ImageStorage.DoesNotExist):
                ImageStorage.get(uuid=removed_image_and_storage.storage.uuid)

    # Ensure all CAS storage is in the storage engine.
    preferred = storage.preferred_locations[0]
    for storage_row in ImageStorage.select():
        if storage_row.cas_path:
            storage.get_content({preferred},
                                storage.blob_path(
                                    storage_row.content_checksum))

    for blob_row in ApprBlob.select():
        storage.get_content({preferred}, storage.blob_path(blob_row.digest))

    # Ensure there are no danglings OCI tags.
    if check_oci_tags:
        oci_tags = {t.id for t in Tag.select()}
        referenced_oci_tags = {t.tag_id for t in TagToRepositoryTag.select()}
        assert not oci_tags - referenced_oci_tags

    # Ensure all tags have valid manifests.
    for manifest in {t.manifest for t in Tag.select()}:
        # Ensure that the manifest's blobs all exist.
        found_blobs = {
            b.blob.content_checksum
            for b in ManifestBlob.select().where(
                ManifestBlob.manifest == manifest)
        }

        parsed = parse_manifest_from_bytes(
            Bytes.for_string_or_unicode(manifest.manifest_bytes),
            manifest.media_type.name)
        assert set(parsed.local_blob_digests) == found_blobs
Exemple #11
0
def _get_legacy_image(namespace, repo, tag, include_storage=True):
    repo_ref = registry_model.lookup_repository(namespace, repo)
    repo_tag = registry_model.get_repo_tag(repo_ref,
                                           tag,
                                           include_legacy_image=True)
    return Image.get(id=repo_tag.legacy_image._db_id)
Exemple #12
0
def create_or_update_tag_for_repo(repository_id,
                                  tag_name,
                                  tag_docker_image_id,
                                  reversion=False,
                                  oci_manifest=None,
                                  now_ms=None):
    now_ms = now_ms or get_epoch_timestamp_ms()
    now_ts = int(now_ms / 1000)

    with db_transaction():
        try:
            tag = db_for_update(
                _tag_alive(
                    RepositoryTag.select().where(
                        RepositoryTag.repository == repository_id,
                        RepositoryTag.name == tag_name),
                    now_ts,
                )).get()
            tag.lifetime_end_ts = now_ts
            tag.save()

            # Check for an OCI tag.
            try:
                oci_tag = db_for_update(
                    Tag.select().join(TagToRepositoryTag).where(
                        TagToRepositoryTag.repository_tag == tag)).get()
                oci_tag.lifetime_end_ms = now_ms
                oci_tag.save()
            except Tag.DoesNotExist:
                pass
        except RepositoryTag.DoesNotExist:
            pass
        except IntegrityError:
            msg = "Tag with name %s was stale when we tried to update it; Please retry the push"
            raise StaleTagException(msg % tag_name)

        try:
            image_obj = Image.get(Image.docker_image_id == tag_docker_image_id,
                                  Image.repository == repository_id)
        except Image.DoesNotExist:
            raise DataModelException("Invalid image with id: %s" %
                                     tag_docker_image_id)

        try:
            created = RepositoryTag.create(
                repository=repository_id,
                image=image_obj,
                name=tag_name,
                lifetime_start_ts=now_ts,
                reversion=reversion,
            )
            if oci_manifest:
                # Create the OCI tag as well.
                oci_tag = Tag.create(
                    repository=repository_id,
                    manifest=oci_manifest,
                    name=tag_name,
                    lifetime_start_ms=now_ms,
                    reversion=reversion,
                    tag_kind=Tag.tag_kind.get_id("tag"),
                )
                TagToRepositoryTag.create(tag=oci_tag,
                                          repository_tag=created,
                                          repository=repository_id)

            return created
        except IntegrityError:
            msg = "Tag with name %s and lifetime start %s already exists"
            raise TagAlreadyCreatedException(msg % (tag_name, now_ts))
Exemple #13
0
def test_tagbackfillworker(clear_all_rows, initialized_db):
    # Remove the new-style rows so we can backfill.
    TagToRepositoryTag.delete().execute()
    Tag.delete().execute()

    if clear_all_rows:
        TagManifestLabelMap.delete().execute()
        ManifestLabel.delete().execute()
        ManifestBlob.delete().execute()
        ManifestLegacyImage.delete().execute()
        TagManifestToManifest.delete().execute()
        Manifest.delete().execute()

    found_dead_tag = False

    for repository_tag in list(RepositoryTag.select()):
        # Backfill the tag.
        assert backfill_tag(repository_tag)

        # Ensure if we try again, the backfill is skipped.
        assert not backfill_tag(repository_tag)

        # Ensure that we now have the expected tag rows.
        tag_to_repo_tag = TagToRepositoryTag.get(repository_tag=repository_tag)
        tag = tag_to_repo_tag.tag
        assert tag.name == repository_tag.name
        assert tag.repository == repository_tag.repository
        assert not tag.hidden
        assert tag.reversion == repository_tag.reversion

        if repository_tag.lifetime_start_ts is None:
            assert tag.lifetime_start_ms is None
        else:
            assert tag.lifetime_start_ms == (repository_tag.lifetime_start_ts *
                                             1000)

        if repository_tag.lifetime_end_ts is None:
            assert tag.lifetime_end_ms is None
        else:
            assert tag.lifetime_end_ms == (repository_tag.lifetime_end_ts *
                                           1000)
            found_dead_tag = True

        assert tag.manifest

        # Ensure that we now have the expected manifest rows.
        try:
            tag_manifest = TagManifest.get(tag=repository_tag)
        except TagManifest.DoesNotExist:
            continue

        map_row = TagManifestToManifest.get(tag_manifest=tag_manifest)
        assert not map_row.broken

        manifest_row = map_row.manifest
        assert manifest_row.manifest_bytes == tag_manifest.json_data
        assert manifest_row.digest == tag_manifest.digest
        assert manifest_row.repository == tag_manifest.tag.repository

        assert tag.manifest == map_row.manifest

        legacy_image = ManifestLegacyImage.get(manifest=manifest_row).image
        assert tag_manifest.tag.image == legacy_image

        expected_storages = {tag_manifest.tag.image.storage.id}
        for parent_image_id in tag_manifest.tag.image.ancestor_id_list():
            expected_storages.add(Image.get(id=parent_image_id).storage_id)

        found_storages = {
            manifest_blob.blob_id
            for manifest_blob in ManifestBlob.select().where(
                ManifestBlob.manifest == manifest_row)
        }
        assert expected_storages == found_storages

        # Ensure the labels were copied over.
        tmls = list(TagManifestLabel.select().where(
            TagManifestLabel.annotated == tag_manifest))
        expected_labels = {tml.label_id for tml in tmls}
        found_labels = {
            m.label_id
            for m in ManifestLabel.select().where(
                ManifestLabel.manifest == manifest_row)
        }
        assert found_labels == expected_labels

    # Verify at the repository level.
    for repository in list(Repository.select()):
        tags = RepositoryTag.select().where(
            RepositoryTag.repository == repository,
            RepositoryTag.hidden == False)
        oci_tags = Tag.select().where(Tag.repository == repository)
        assert len(tags) == len(oci_tags)
        assert {t.name for t in tags} == {t.name for t in oci_tags}

        for tag in tags:
            tag_manifest = TagManifest.get(tag=tag)
            ttr = TagToRepositoryTag.get(repository_tag=tag)
            manifest = ttr.tag.manifest

            assert tag_manifest.json_data == manifest.manifest_bytes
            assert tag_manifest.digest == manifest.digest
            assert tag.image == ManifestLegacyImage.get(
                manifest=manifest).image
            assert tag.lifetime_start_ts == (ttr.tag.lifetime_start_ms / 1000)

            if tag.lifetime_end_ts:
                assert tag.lifetime_end_ts == (ttr.tag.lifetime_end_ms / 1000)
            else:
                assert ttr.tag.lifetime_end_ms is None

    assert found_dead_tag
Exemple #14
0
 def get_storage_replication_entry(image_id):
     image = Image.get(docker_image_id=image_id)
     QueueItem.select().where(
         QueueItem.queue_name**("%" + image.storage.uuid + "%")).get()
     return "OK"
Exemple #15
0
 def delete_image(image_id):
     image = Image.get(docker_image_id=image_id)
     image.docker_image_id = "DELETED"
     image.save()
     return "OK"
Exemple #16
0
def get_image_by_db_id(id):
    try:
        return Image.get(id=id)
    except Image.DoesNotExist:
        return None
Exemple #17
0
def test_create_temp_tag(initialized_db):
    repo = model.repository.get_repository("devtable", "simple")
    image = Image.get(repository=repo)
    assert model.tag.create_temporary_hidden_tag(repo, image,
                                                 10000000) is not None
Exemple #18
0
 def delete_image(image_id):
   image = Image.get(docker_image_id=image_id)
   image.docker_image_id = 'DELETED'
   image.save()
   return 'OK'