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))
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)
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)
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)
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, ), }, )
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)
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()
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), )
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)
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)
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]
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)
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
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)
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)
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()
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
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
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)
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)
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
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, )
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
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!", } )
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 ), )