示例#1
0
    def commit_blob_upload(self, blob_upload, blob_digest_str,
                           blob_expiration_seconds):
        """
        Commits the blob upload into a blob and sets an expiration before that blob will be GCed.
        """
        with db_disallow_replica_use():
            upload_record = model.blob.get_blob_upload_by_uuid(
                blob_upload.upload_id)
            if upload_record is None:
                return None

            repository_id = upload_record.repository_id

            # Create the blob and temporarily tag it.
            location_obj = model.storage.get_image_location_for_name(
                blob_upload.location_name)
            blob_record = model.blob.store_blob_record_and_temp_link_in_repo(
                repository_id,
                blob_digest_str,
                location_obj.id,
                blob_upload.byte_count,
                blob_expiration_seconds,
                blob_upload.uncompressed_byte_count,
            )

            # Delete the blob upload.
            upload_record.delete_instance()
            return Blob.for_image_storage(
                blob_record,
                storage_path=model.storage.get_layer_path(blob_record))
示例#2
0
文件: manifest.py 项目: kleesc/quay
def delete_manifest_by_digest(namespace_name, repo_name, manifest_ref):
    """
    Delete the manifest specified by the digest.

    Note: there is no equivalent method for deleting by tag name because it is
    forbidden by the spec.
    """
    with db_disallow_replica_use():
        repository_ref = registry_model.lookup_repository(
            namespace_name, repo_name)
        if repository_ref is None:
            raise NameUnknown()

        manifest = registry_model.lookup_manifest_by_digest(
            repository_ref, manifest_ref)
        if manifest is None:
            raise ManifestUnknown()

        tags = registry_model.delete_tags_for_manifest(manifest)
        if not tags:
            raise ManifestUnknown()

        for tag in tags:
            track_and_log("delete_tag",
                          repository_ref,
                          tag=tag.name,
                          digest=manifest_ref)

        return Response(status=202)
示例#3
0
    def update_blob_upload(
        self,
        blob_upload,
        uncompressed_byte_count,
        storage_metadata,
        byte_count,
        chunk_count,
        sha_state,
    ):
        """
        Updates the fields of the blob upload to match those given.

        Returns the updated blob upload or None if the record does not exists.
        """
        with db_disallow_replica_use():
            upload_record = model.blob.get_blob_upload_by_uuid(
                blob_upload.upload_id)
            if upload_record is None:
                return None

            upload_record.uncompressed_byte_count = uncompressed_byte_count
            upload_record.storage_metadata = storage_metadata
            upload_record.byte_count = byte_count
            upload_record.chunk_count = chunk_count
            upload_record.sha_state = sha_state
            upload_record.save()
            return BlobUpload.for_upload(upload_record)
示例#4
0
 def set_tags_expiration_for_manifest(self, manifest, expiration_sec):
     """
     Sets the expiration on all tags that point to the given manifest to that specified.
     """
     with db_disallow_replica_use():
         oci.tag.set_tag_expiration_sec_for_manifest(
             manifest._db_id, expiration_sec)
示例#5
0
def _write_manifest_and_log(namespace_name, repo_name, tag_name, manifest_impl):
    _validate_schema1_manifest(namespace_name, repo_name, manifest_impl)
    with db_disallow_replica_use():
        repository_ref, manifest, tag = _write_manifest(
            namespace_name, repo_name, tag_name, manifest_impl
        )

        # Queue all blob manifests for replication.
        if features.STORAGE_REPLICATION:
            blobs = registry_model.get_manifest_local_blobs(manifest, storage)
            if blobs is None:
                logger.error("Could not lookup blobs for manifest `%s`", manifest.digest)
            else:
                with queue_replication_batch(namespace_name) as queue_storage_replication:
                    for blob_digest in blobs:
                        queue_storage_replication(blob_digest)

        track_and_log("push_repo", repository_ref, tag=tag_name)
        spawn_notification(repository_ref, "repo_push", {"updated_tags": [tag_name]})
        image_pushes.labels("v2", 201, manifest.media_type).inc()

        return Response(
            "OK",
            status=201,
            headers={
                "Docker-Content-Digest": manifest.digest,
                "Location": url_for(
                    "v2.fetch_manifest_by_digest",
                    repository="%s/%s" % (namespace_name, repo_name),
                    manifest_ref=manifest.digest,
                ),
            },
        )
示例#6
0
def delete_manifest_by_digest(namespace_name, repo_name, manifest_ref):
    """
    Delete the manifest specified by the digest.

    Note: there is no equivalent method for deleting by tag name because it is
    forbidden by the spec.
    """
    with db_disallow_replica_use():
        repository_ref = registry_model.lookup_repository(namespace_name, repo_name)
        if repository_ref is None:
            raise NameUnknown("repository not found")

        manifest = registry_model.lookup_manifest_by_digest(repository_ref, manifest_ref)
        if manifest is None:
            raise ManifestUnknown()

        tags = registry_model.delete_tags_for_manifest(manifest)
        if not tags:
            raise ManifestUnknown()

        for tag in tags:
            track_and_log("delete_tag", repository_ref, tag=tag.name, digest=manifest_ref)

        if app.config.get("FEATURE_QUOTA_MANAGEMENT", False):
            repository.force_cache_repo_size(repository_ref.id)

        return Response(status=202)
示例#7
0
 def delete_blob_upload(self, blob_upload):
     """
     Deletes a blob upload record.
     """
     with db_disallow_replica_use():
         upload_record = model.blob.get_blob_upload_by_uuid(blob_upload.upload_id)
         if upload_record is not None:
             upload_record.delete_instance()
示例#8
0
    def create_manifest_and_retarget_tag(self,
                                         repository_ref,
                                         manifest_interface_instance,
                                         tag_name,
                                         storage,
                                         raise_on_error=False):
        """
        Creates a manifest in a repository, adding all of the necessary data in the model.

        The `manifest_interface_instance` parameter must be an instance of the manifest
        interface as returned by the image/docker package.

        Note that all blobs referenced by the manifest must exist under the repository or this
        method will fail and return None.

        Returns a reference to the (created manifest, tag) or (None, None) on error, unless
        raise_on_error is set to True, in which case a CreateManifestException may also be
        raised.
        """
        with db_disallow_replica_use():
            # Get or create the manifest itself.
            created_manifest = oci.manifest.get_or_create_manifest(
                repository_ref._db_id,
                manifest_interface_instance,
                storage,
                for_tagging=True,
                raise_on_error=raise_on_error,
            )
            if created_manifest is None:
                return (None, None)

            # Re-target the tag to it.
            tag = oci.tag.retarget_tag(
                tag_name,
                created_manifest.manifest,
                raise_on_error=raise_on_error,
            )
            if tag is None:
                return (None, None)

            wrapped_manifest = Manifest.for_manifest(
                created_manifest.manifest, self._legacy_image_id_handler)

            # Apply any labels that should modify the created tag.
            if created_manifest.labels_to_apply:
                for key, value in created_manifest.labels_to_apply.items():
                    apply_label_to_manifest(dict(key=key, value=value),
                                            wrapped_manifest, self)

                # Reload the tag in case any updates were applied.
                tag = database.Tag.get(id=tag.id)

            return (
                wrapped_manifest,
                Tag.for_tag(tag,
                            self._legacy_image_id_handler,
                            manifest_row=created_manifest.manifest),
            )
示例#9
0
    def change_repository_tag_expiration(self, tag, expiration_date):
        """
        Sets the expiration date of the tag under the matching repository to that given.

        If the expiration date is None, then the tag will not expire. Returns a tuple of the
        previous expiration timestamp in seconds (if any), and whether the operation succeeded.
        """
        with db_disallow_replica_use():
            return oci.tag.change_tag_expiration(tag._db_id, expiration_date)
示例#10
0
    def retarget_tag(
        self,
        repository_ref,
        tag_name,
        manifest_or_legacy_image,
        storage,
        legacy_manifest_key,
        is_reversion=False,
    ):
        """
        Creates, updates or moves a tag to a new entry in history, pointing to the manifest or
        legacy image specified.

        If is_reversion is set to True, this operation is considered a reversion over a previous tag
        move operation. Returns the updated Tag or None on error.
        """
        with db_disallow_replica_use():
            assert legacy_manifest_key is not None
            manifest = manifest_or_legacy_image.as_manifest()
            manifest_id = manifest._db_id

            # If the manifest is a schema 1 manifest and its tag name does not match that
            # specified, then we need to create a new manifest, but with that tag name.
            if manifest.media_type in DOCKER_SCHEMA1_CONTENT_TYPES:
                try:
                    parsed = manifest.get_parsed_manifest()
                except ManifestException:
                    logger.exception(
                        "Could not parse manifest `%s` in retarget_tag",
                        manifest._db_id,
                    )
                    return None

                if parsed.tag != tag_name:
                    logger.debug(
                        "Rewriting manifest `%s` for tag named `%s`",
                        manifest._db_id,
                        tag_name,
                    )

                    repository_id = repository_ref._db_id
                    updated = parsed.with_tag_name(tag_name,
                                                   legacy_manifest_key)
                    assert updated.is_signed

                    created = oci.manifest.get_or_create_manifest(
                        repository_id, updated, storage)
                    if created is None:
                        return None

                    manifest_id = created.manifest.id

            tag = oci.tag.retarget_tag(tag_name,
                                       manifest_id,
                                       is_reversion=is_reversion)
            return Tag.for_tag(tag, self._legacy_image_id_handler)
示例#11
0
    def delete_tags_for_manifest(self, manifest):
        """
        Deletes all tags pointing to the given manifest, making the manifest inaccessible for
        pulling.

        Returns the tags (ShallowTag) deleted. Returns None on error.
        """
        with db_disallow_replica_use():
            deleted_tags = oci.tag.delete_tags_for_manifest(manifest._db_id)
            return [ShallowTag.for_tag(tag) for tag in deleted_tags]
示例#12
0
    def lookup_blob_upload(self, repository_ref, blob_upload_id):
        """
        Looks up the blob upload withn the given ID under the specified repository and returns it or
        None if none.
        """
        with db_disallow_replica_use():
            upload_record = model.blob.get_blob_upload_by_uuid(blob_upload_id)
            if upload_record is None:
                return None

            return BlobUpload.for_upload(upload_record)
示例#13
0
    def get_repo_tag(self, repository_ref, tag_name, raise_on_error=True):
        """
        Returns the latest, *active* tag found in the repository, with the matching
        name or None if none.

        If both manifest and tag don't exist, fetches the manifest with the tag
        from upstream, and creates them both.
        If tag and manifest exists and the manifest is a placeholder, pull the
        upstream manifest and save it locally.
        """
        db_tag = oci.tag.get_current_tag(repository_ref.id, tag_name)
        existing_tag = Tag.for_tag(db_tag, self._legacy_image_id_handler)
        if existing_tag is None:
            try:
                _, tag = self._create_and_tag_manifest(
                    repository_ref, tag_name,
                    self._create_manifest_and_retarget_tag)
            except (UpstreamRegistryError, ManifestDoesNotExist) as e:
                raise TagDoesNotExist(str(e))
            return tag

        new_tag = False
        try:
            tag, new_tag = self._update_manifest_for_tag(
                repository_ref,
                existing_tag,
                existing_tag.manifest,
                tag_name,
                self._create_manifest_and_retarget_tag,
            )
        except ManifestDoesNotExist as e:
            raise TagDoesNotExist(str(e))
        except UpstreamRegistryError:
            # when the upstream fetch fails, we only return the tag if
            # it isn't yet expired. note that we don't bump the tag's
            # expiration here either - we only do this when we can ensure
            # the tag exists upstream.
            isplaceholder = existing_tag.manifest.internal_manifest_bytes.as_unicode(
            ) == ""
            return existing_tag if not existing_tag.expired and not isplaceholder else None

        # always bump tag expiration when retrieving tags that both are cached
        # and exist upstream, as a means to auto-renew the cache.
        if tag.expired or not new_tag:
            with db_disallow_replica_use():
                new_expiration = (get_epoch_timestamp_ms() +
                                  self._config.expiration_s * 1000
                                  if self._config.expiration_s else None)
                oci.tag.set_tag_end_ms(db_tag, new_expiration)
            return super().get_repo_tag(repository_ref,
                                        tag_name,
                                        raise_on_error=True)

        return tag
示例#14
0
    def delete_tag(self, repository_ref, tag_name):
        """
        Deletes the latest, *active* tag with the given name in the repository.
        """
        with db_disallow_replica_use():
            deleted_tag = oci.tag.delete_tag(repository_ref._db_id, tag_name)
            if deleted_tag is None:
                # TODO: This is only needed because preoci raises an exception. Remove and fix
                # expected status codes once PreOCIModel is gone.
                msg = "Invalid repository tag '%s' on repository" % tag_name
                raise DataModelException(msg)

            return Tag.for_tag(deleted_tag, self._legacy_image_id_handler)
示例#15
0
    def mount_blob_into_repository(self, blob, target_repository_ref,
                                   expiration_sec):
        """
        Mounts the blob from another repository into the specified target repository, and adds an
        expiration before that blob is automatically GCed.

        This function is useful during push operations if an existing blob from another repository
        is being pushed. Returns False if the mounting fails.
        """
        with db_disallow_replica_use():
            storage = model.blob.temp_link_blob(target_repository_ref._db_id,
                                                blob.digest, expiration_sec)
            return bool(storage)
示例#16
0
    def reset_security_status(self, manifest_or_legacy_image):
        """
        Resets the security status for the given manifest or legacy image, ensuring that it will get
        re-indexed.
        """
        with db_disallow_replica_use():
            # TODO: change from using the Image row once we've moved all security info into MSS.
            manifest_id = manifest_or_legacy_image.as_manifest()._db_id
            image = oci.shared.get_legacy_image_for_manifest(manifest_id)
            if image is None:
                return None

                assert image
                image.security_indexed = False
                image.security_indexed_engine = IMAGE_NOT_SCANNED_ENGINE_VERSION
                image.save()
示例#17
0
    def create_blob_upload(self, repository_ref, new_upload_id, location_name, storage_metadata):
        """
        Creates a new blob upload and returns a reference.

        If the blob upload could not be created, returns None.
        """
        with db_disallow_replica_use():
            repo = model.repository.lookup_repository(repository_ref._db_id)
            if repo is None:
                return None

            try:
                upload_record = model.blob.initiate_upload_for_repo(
                    repo, new_upload_id, location_name, storage_metadata
                )
                return BlobUpload.for_upload(upload_record, location_name=location_name)
            except database.Repository.DoesNotExist:
                return None
示例#18
0
    def _create_manifest_with_temp_tag(
        self,
        repository_ref: RepositoryReference,
        manifest: ManifestInterface,
        manifest_ref: str | None = None,
    ) -> tuple[Manifest | None, Tag | None]:
        with db_disallow_replica_use():
            with db_transaction():
                db_manifest = oci.manifest.create_manifest(
                    repository_ref.id, manifest)
                self._recalculate_repository_size(repository_ref)
                expiration = self._config.expiration_s or None
                tag = Tag.for_tag(
                    oci.tag.create_temporary_tag_if_necessary(
                        db_manifest, expiration),
                    self._legacy_image_id_handler,
                )
                wrapped_manifest = Manifest.for_manifest(
                    db_manifest, self._legacy_image_id_handler)

                if not manifest.is_manifest_list:
                    self._create_placeholder_blobs(manifest, db_manifest.id,
                                                   repository_ref.id)
                    return wrapped_manifest, tag

                manifests_to_connect = []
                for child in manifest.child_manifests(content_retriever=None):
                    m = oci.manifest.lookup_manifest(repository_ref.id,
                                                     child.digest)
                    if m is None:
                        m = oci.manifest.create_manifest(
                            repository_ref.id, child)
                    manifests_to_connect.append(m)

                oci.manifest.connect_manifests(manifests_to_connect,
                                               db_manifest, repository_ref.id)
                for db_manifest in manifests_to_connect:
                    oci.tag.create_temporary_tag_if_necessary(
                        db_manifest, expiration)

                return wrapped_manifest, tag
示例#19
0
    def create_manifest_with_temp_tag(
        self, repository_ref, manifest_interface_instance, expiration_sec, storage
    ):
        """
        Creates a manifest under the repository and sets a temporary tag to point to it.

        Returns the manifest object created or None on error.
        """
        with db_disallow_replica_use():
            # Get or create the manifest itself. get_or_create_manifest will take care of the
            # temporary tag work.
            created_manifest = oci.manifest.get_or_create_manifest(
                repository_ref._db_id,
                manifest_interface_instance,
                storage,
                temp_tag_expiration_sec=expiration_sec,
            )
            if created_manifest is None:
                return None

            return Manifest.for_manifest(created_manifest.manifest, self._legacy_image_id_handler)
示例#20
0
    def retarget_tag(
        self,
        repository_ref,
        tag_name,
        manifest_or_legacy_image,
        storage,
        legacy_manifest_key,
        is_reversion=False,
    ):
        """
        Creates, updates or moves a tag to a new entry in history, pointing to the manifest or
        legacy image specified.

        If is_reversion is set to True, this operation is considered a reversion over a previous tag
        move operation. Returns the updated Tag or None on error.
        """
        with db_disallow_replica_use():
            assert legacy_manifest_key is not None
            manifest = manifest_or_legacy_image.as_manifest()
            manifest_id = manifest._db_id

            # If the manifest is a schema 1 manifest and its tag name does not match that
            # specified, then we need to create a new manifest, but with that tag name.
            if manifest.media_type in DOCKER_SCHEMA1_CONTENT_TYPES:
                try:
                    parsed = manifest.get_parsed_manifest()
                except ManifestException:
                    logger.exception(
                        "Could not parse manifest `%s` in retarget_tag",
                        manifest._db_id,
                    )
                    return None

                if parsed.tag != tag_name:
                    logger.debug(
                        "Rewriting manifest `%s` for tag named `%s`",
                        manifest._db_id,
                        tag_name,
                    )

                    repository_id = repository_ref._db_id
                    updated = parsed.with_tag_name(tag_name, legacy_manifest_key)
                    assert updated.is_signed

                    created = oci.manifest.get_or_create_manifest(repository_id, updated, storage)
                    if created is None:
                        return None

                    manifest_id = created.manifest.id

            label_dict = next(
                (
                    label.asdict()
                    for label in self.list_manifest_labels(
                        manifest,
                        key_prefix="quay",
                    )
                    if label.key == LABEL_EXPIRY_KEY
                ),
                None,
            )

            expiration_seconds = None

            if label_dict is not None:
                try:
                    expiration_td = convert_to_timedelta(label_dict["value"])
                    expiration_seconds = expiration_td.total_seconds()
                except ValueError:
                    pass

            tag = oci.tag.retarget_tag(
                tag_name,
                manifest_id,
                is_reversion=is_reversion,
                expiration_seconds=expiration_seconds,
            )

            return Tag.for_tag(tag, self._legacy_image_id_handler)
示例#21
0
    def _update_manifest_for_tag(
        self,
        repo_ref: RepositoryReference,
        tag: Tag,
        manifest: Manifest,
        manifest_ref: str,
        create_manifest_fn,
    ) -> tuple[Tag, bool]:
        """
        Updates a placeholder manifest with the given tag name.

        If the manifest is stale, downloads it from the upstream registry
        and creates a new manifest and retargets the tag.

        A manifest is considered stale when the manifest's digest changed in
        the upstream registry.
        A manifest is considered a placeholder when its db entry exists, but
        its manifest_bytes field is empty.

        Raises UpstreamRegistryError if the upstream registry returns anything
        other than 200.
        Raises ManifestDoesNotExist if the given manifest was not found in the
        database.

        Returns a new tag if one was created, or the existing one with a manifest
        freshly out of the database, and a boolean indicating whether the returned
        tag was newly created or not.
        """
        upstream_manifest = None
        upstream_digest = self._proxy.manifest_exists(manifest_ref,
                                                      ACCEPTED_MEDIA_TYPES)
        up_to_date = manifest.digest == upstream_digest

        # manifest_exists will return an empty/None digest when the upstream
        # registry omits the docker-content-digest header.
        if not upstream_digest:
            upstream_manifest = self._pull_upstream_manifest(
                repo_ref.name, manifest_ref)
            up_to_date = manifest.digest == upstream_manifest.digest

        placeholder = manifest.internal_manifest_bytes.as_unicode() == ""
        if up_to_date and not placeholder:
            return tag, False

        if upstream_manifest is None:
            upstream_manifest = self._pull_upstream_manifest(
                repo_ref.name, manifest_ref)

        self._enforce_repository_quota(repo_ref)
        if up_to_date and placeholder:
            with db_disallow_replica_use():
                with db_transaction():
                    q = ManifestTable.update(
                        manifest_bytes=upstream_manifest.bytes.as_unicode(),
                        layers_compressed_size=upstream_manifest.
                        layers_compressed_size,
                    ).where(ManifestTable.id == manifest.id)
                    q.execute()
                    self._create_placeholder_blobs(upstream_manifest,
                                                   manifest.id, repo_ref.id)
                    db_tag = oci.tag.get_tag_by_manifest_id(
                        repo_ref.id, manifest.id)
                    self._recalculate_repository_size(repo_ref)
                    return Tag.for_tag(db_tag,
                                       self._legacy_image_id_handler), False

        # if we got here, the manifest is stale, so we both create a new manifest
        # entry in the db, and retarget the tag.
        _, tag = create_manifest_fn(repo_ref, upstream_manifest, manifest_ref)
        return tag, True
示例#22
0
    def lookup_manifest_by_digest(
        self,
        repository_ref,
        manifest_digest,
        allow_dead=False,
        require_available=False,
        raise_on_error=True,
    ):
        """
        Looks up the manifest with the given digest under the given repository and returns it or
        None if none.

        If a manifest with the digest does not exist, fetches the manifest upstream
        and creates it with a temp tag.
        """
        wrapped_manifest = super().lookup_manifest_by_digest(
            repository_ref,
            manifest_digest,
            allow_dead=True,
            require_available=False)
        if wrapped_manifest is None:
            try:
                wrapped_manifest, _ = self._create_and_tag_manifest(
                    repository_ref, manifest_digest,
                    self._create_manifest_with_temp_tag)
            except (UpstreamRegistryError, ManifestDoesNotExist) as e:
                raise ManifestDoesNotExist(str(e))
            return wrapped_manifest

        db_tag = oci.tag.get_tag_by_manifest_id(repository_ref.id,
                                                wrapped_manifest.id)
        existing_tag = Tag.for_tag(db_tag,
                                   self._legacy_image_id_handler,
                                   manifest_row=db_tag.manifest)
        new_tag = False
        try:
            tag, new_tag = self._update_manifest_for_tag(
                repository_ref,
                existing_tag,
                existing_tag.manifest,
                manifest_digest,
                self._create_manifest_with_temp_tag,
            )
        except ManifestDoesNotExist as e:
            raise e
        except UpstreamRegistryError:
            # when the upstream fetch fails, we only return the tag if
            # it isn't yet expired. note that we don't bump the tag's
            # expiration here either - we only do this when we can ensure
            # the tag exists upstream.
            isplaceholder = wrapped_manifest.internal_manifest_bytes.as_unicode(
            ) == ""
            return wrapped_manifest if not existing_tag.expired and not isplaceholder else None

        if tag.expired or not new_tag:
            with db_disallow_replica_use():
                new_expiration = (get_epoch_timestamp_ms() +
                                  self._config.expiration_s * 1000
                                  if self._config.expiration_s else None)
                oci.tag.set_tag_end_ms(db_tag, new_expiration)
                # if the manifest is a child of a manifest list in this repo, renew
                # the parent manifest list tag too.
                parent = ManifestChild.select(ManifestChild.manifest_id).where(
                    (ManifestChild.repository_id == repository_ref.id)
                    & (ManifestChild.child_manifest_id == wrapped_manifest.id))
                parent_tag = oci.tag.get_tag_by_manifest_id(
                    repository_ref.id, parent)
                if parent_tag is not None:
                    oci.tag.set_tag_end_ms(parent_tag, new_expiration)

        return super().lookup_manifest_by_digest(
            repository_ref,
            manifest_digest,
            allow_dead=True,
            require_available=False,
            raise_on_error=True,
        )
示例#23
0
    def _create_manifest_and_retarget_tag(
            self, repository_ref: RepositoryReference,
            manifest: ManifestInterface,
            tag_name: str) -> tuple[Manifest | None, Tag | None]:
        """
        Creates a manifest in the given repository.

        Also creates placeholders for the objects referenced by the manifest.
        For manifest lists, creates placeholder sub-manifests. For regular
        manifests, creates placeholder blobs.

        Placeholder objects will be "filled" with the objects' contents on
        upcoming client requests, as part of the flow described in the OCI
        distribution specification.

        Returns a reference to the (created manifest, tag) or (None, None) on error.
        """
        with db_disallow_replica_use():
            with db_transaction():
                db_manifest = oci.manifest.lookup_manifest(repository_ref.id,
                                                           manifest.digest,
                                                           allow_dead=True)
                if db_manifest is None:
                    db_manifest = oci.manifest.create_manifest(
                        repository_ref.id, manifest, raise_on_error=True)
                    self._recalculate_repository_size(repository_ref)
                    if db_manifest is None:
                        return None, None

                # 0 means a tag never expires - if we get 0 as expiration,
                # we set the tag expiration to None.
                expiration = self._config.expiration_s or None
                tag = oci.tag.retarget_tag(
                    tag_name,
                    db_manifest,
                    raise_on_error=True,
                    expiration_seconds=expiration,
                )
                if tag is None:
                    return None, None

                wrapped_manifest = Manifest.for_manifest(
                    db_manifest, self._legacy_image_id_handler)
                wrapped_tag = Tag.for_tag(tag,
                                          self._legacy_image_id_handler,
                                          manifest_row=db_manifest)

                if not manifest.is_manifest_list:
                    self._create_placeholder_blobs(manifest, db_manifest.id,
                                                   repository_ref.id)
                    return wrapped_manifest, wrapped_tag

                manifests_to_connect = []
                for child in manifest.child_manifests(content_retriever=None):
                    m = oci.manifest.lookup_manifest(repository_ref.id,
                                                     child.digest,
                                                     allow_dead=True)
                    if m is None:
                        m = oci.manifest.create_manifest(
                            repository_ref.id, child)
                        oci.tag.create_temporary_tag_if_necessary(
                            m, self._config.expiration_s or None)
                    try:
                        ManifestChild.get(manifest=db_manifest.id,
                                          child_manifest=m.id)
                    except ManifestChild.DoesNotExist:
                        manifests_to_connect.append(m)

                oci.manifest.connect_manifests(manifests_to_connect,
                                               db_manifest, repository_ref.id)

                return wrapped_manifest, wrapped_tag
示例#24
0
def test_readreplica(init_db_path, tmpdir_factory):
    primary_file = str(tmpdir_factory.mktemp("data").join("primary.db"))
    replica_file = str(tmpdir_factory.mktemp("data").join("replica.db"))

    # Copy the initialized database to two different locations.
    shutil.copy2(init_db_path, primary_file)
    shutil.copy2(init_db_path, replica_file)

    db_config = {
        "DB_URI": "sqlite:///{0}".format(primary_file),
        "DB_READ_REPLICAS": [{"DB_URI": "sqlite:///{0}".format(replica_file)},],
        "DB_CONNECTION_ARGS": {"threadlocals": True, "autorollback": True,},
        "DB_TRANSACTION_FACTORY": lambda x: FakeTransaction(),
        "FOR_TESTING": True,
        "DATABASE_SECRET_KEY": "anothercrazykey!",
    }

    # Initialize the DB with the primary and the replica.
    configure(db_config)
    assert not read_only_config.obj.is_readonly
    assert read_only_config.obj.read_replicas

    # Ensure we can read the data.
    devtable_user = User.get(username="******")
    assert devtable_user.username == "devtable"

    # Configure with a bad primary. Reading should still work since we're hitting the replica.
    db_config["DB_URI"] = "sqlite:///does/not/exist"
    configure(db_config)

    assert not read_only_config.obj.is_readonly
    assert read_only_config.obj.read_replicas

    devtable_user = User.get(username="******")
    assert devtable_user.username == "devtable"

    # Force us to hit the master and ensure it doesn't work.
    with db_disallow_replica_use():
        with pytest.raises(OperationalError):
            User.get(username="******")

    # Test read replica again.
    devtable_user = User.get(username="******")
    assert devtable_user.username == "devtable"

    # Try to change some data. This should fail because the primary is broken.
    with pytest.raises(OperationalError):
        devtable_user.email = "newlychanged"
        devtable_user.save()

    # Fix the primary and try again.
    db_config["DB_URI"] = "sqlite:///{0}".format(primary_file)
    configure(db_config)

    assert not read_only_config.obj.is_readonly
    assert read_only_config.obj.read_replicas

    devtable_user.email = "newlychanged"
    devtable_user.save()

    # Mark the system as readonly.
    db_config["DB_URI"] = "sqlite:///{0}".format(primary_file)
    db_config["REGISTRY_STATE"] = "readonly"
    configure(db_config)

    assert read_only_config.obj.is_readonly
    assert read_only_config.obj.read_replicas

    # Ensure all write operations raise a readonly mode exception.
    with pytest.raises(ReadOnlyModeException):
        devtable_user.email = "newlychanged2"
        devtable_user.save()

    with pytest.raises(ReadOnlyModeException):
        User.create(username="******")

    with pytest.raises(ReadOnlyModeException):
        User.delete().where(User.username == "foo").execute()

    with pytest.raises(ReadOnlyModeException):
        User.update(username="******").where(User.username == "foo").execute()

    # Reset the config on the DB, so we don't mess up other tests.
    configure(
        {
            "DB_URI": "sqlite:///{0}".format(primary_file),
            "DB_CONNECTION_ARGS": {"threadlocals": True, "autorollback": True,},
            "DB_TRANSACTION_FACTORY": lambda x: FakeTransaction(),
            "DATABASE_SECRET_KEY": "anothercrazykey!",
        }
    )
示例#25
0
    def create_manifest_and_retarget_tag(
        self, repository_ref, manifest_interface_instance, tag_name, storage, raise_on_error=False
    ):
        """
        Creates a manifest in a repository, adding all of the necessary data in the model.

        The `manifest_interface_instance` parameter must be an instance of the manifest
        interface as returned by the image/docker package.

        Note that all blobs referenced by the manifest must exist under the repository or this
        method will fail and return None.

        Returns a reference to the (created manifest, tag) or (None, None) on error, unless
        raise_on_error is set to True, in which case a CreateManifestException may also be
        raised.
        """
        with db_disallow_replica_use():
            # Get or create the manifest itself.
            created_manifest = oci.manifest.get_or_create_manifest(
                repository_ref._db_id,
                manifest_interface_instance,
                storage,
                for_tagging=True,
                raise_on_error=raise_on_error,
            )
            if created_manifest is None:
                return (None, None)

            wrapped_manifest = Manifest.for_manifest(
                created_manifest.manifest, self._legacy_image_id_handler
            )

            # Optional expiration label
            # NOTE: Since there is currently only one special label on a manifest that has an effect on its tags (expiration),
            #       it is just simpler to set that value at tag creation time (plus it saves an additional query).
            #       If we were to define more of these "special" labels in the future, we should use the handlers from
            #       data/registry_model/label_handlers.py
            if not created_manifest.newly_created:
                label_dict = next(
                    (
                        label.asdict()
                        for label in self.list_manifest_labels(
                            wrapped_manifest,
                            key_prefix="quay",
                        )
                        if label.key == LABEL_EXPIRY_KEY
                    ),
                    None,
                )
            else:
                label_dict = next(
                    (
                        dict(key=label_key, value=label_value)
                        for label_key, label_value in created_manifest.labels_to_apply.items()
                        if label_key == LABEL_EXPIRY_KEY
                    ),
                    None,
                )

            expiration_seconds = None

            if label_dict is not None:
                try:
                    expiration_td = convert_to_timedelta(label_dict["value"])
                    expiration_seconds = expiration_td.total_seconds()
                except ValueError:
                    pass

            # Re-target the tag to it.
            tag = oci.tag.retarget_tag(
                tag_name,
                created_manifest.manifest,
                raise_on_error=raise_on_error,
                expiration_seconds=expiration_seconds,
            )
            if tag is None:
                return (None, None)

            return (
                wrapped_manifest,
                Tag.for_tag(
                    tag, self._legacy_image_id_handler, manifest_row=created_manifest.manifest
                ),
            )