diff --git a/dandiapi/api/storage.py b/dandiapi/api/storage.py index 0fa1e1a34..f4ddf66e6 100644 --- a/dandiapi/api/storage.py +++ b/dandiapi/api/storage.py @@ -182,15 +182,20 @@ def etag_from_blob_name(self, blob_name) -> str | None: return etag[1:-1] return etag - def generate_presigned_put_object_url(self, blob_name: str, base64md5: str) -> str: + def generate_presigned_put_object_url( + self, blob_name: str, base64md5: str, content_type: str | None + ) -> str: + params = { + 'Bucket': self.bucket_name, + 'Key': blob_name, + 'ACL': 'bucket-owner-full-control', + 'ContentMD5': base64md5, + } + if content_type is not None: + params['ContentType'] = content_type return self.connection.meta.client.generate_presigned_url( ClientMethod='put_object', - Params={ - 'Bucket': self.bucket_name, - 'Key': blob_name, - 'ACL': 'bucket-owner-full-control', - 'ContentMD5': base64md5, - }, + Params=params, ExpiresIn=600, # TODO: proper expiration ) @@ -243,16 +248,23 @@ def etag_from_blob_name(self, blob_name) -> str | None: else: return response.etag - def generate_presigned_put_object_url(self, blob_name: str, _: str) -> str: + def generate_presigned_put_object_url( + self, blob_name: str, _: str, content_type: str | None + ) -> str: # Note: minio-py doesn't support using Content-MD5 headers + headers = {} + if content_type is not None: + headers['response-content-type'] = content_type # storage.client will generate URLs like `http://minio:9000/...` when running in # docker. To avoid this, use the secondary base_url_client which is configured to # generate URLs like `http://localhost:9000/...`. - return self.base_url_client.presigned_put_object( + return self.base_url_client.get_presigned_url( + method='PUT', bucket_name=self.bucket_name, object_name=blob_name, expires=timedelta(seconds=600), # TODO: proper expiration + response_headers=headers, ) def generate_presigned_head_object_url(self, key: str) -> str: diff --git a/dandiapi/zarr/models.py b/dandiapi/zarr/models.py index 512b3266e..bcd2f384f 100644 --- a/dandiapi/zarr/models.py +++ b/dandiapi/zarr/models.py @@ -88,7 +88,9 @@ def s3_path(self, zarr_path: str) -> str: def generate_upload_urls(self, path_md5s: list[dict]): return [ - self.storage.generate_presigned_put_object_url(self.s3_path(o['path']), o['base64md5']) + self.storage.generate_presigned_put_object_url( + self.s3_path(o['path']), o['base64md5'], content_type=o['content_type'] + ) for o in path_md5s ] diff --git a/dandiapi/zarr/views/__init__.py b/dandiapi/zarr/views/__init__.py index 19bba2571..171b189d9 100644 --- a/dandiapi/zarr/views/__init__.py +++ b/dandiapi/zarr/views/__init__.py @@ -36,6 +36,7 @@ class ZarrFileCreationSerializer(serializers.Serializer): path = serializers.CharField() base64md5 = serializers.CharField() + content_type = serializers.CharField(allow_null=True, default=None) class ZarrDeleteFileRequestSerializer(serializers.Serializer):