docs icon indicating copy to clipboard operation
docs copied to clipboard

🐛(s3/backend) Doc content is not saved to s3 | XAmzContentSHA256Mismatch

Open jco-c opened this issue 4 months ago • 5 comments

Bug Report

Problematic behavior Document content is not saved. When creating a document and adding content and then switching document, the document still exists but the content is gone.

The logs show repeatedly a XAmzContentSHA256Mismatch error when the backend tries to use _saveobj.upload_fileobj(content, ExtraArgs=params, Config=self.transfer_config) and subsequent calls (not familiar with the backend code). Error message is shown below.

Expected behavior/code That the document content is saved.

Steps to Reproduce

  1. Open a new document
  2. Add content
  3. Switch to another doc and back
  4. Content is gone.

Environment

  • Docs version: 3.4.2
  • Instance url: docs.lielacloud.org
  • s3 backend: Ionos (german hoster for object storage & more)

Possible Solution

Additional context/Screenshots Error message:

Error Message in Logs

backend-1 | 2025-07-20 10:29:13,441 request.summary ERROR An error occurred (XAmzContentSHA256Mismatch) when calling the PutObject operation: None backend-1 | 2025-07-20 10:29:13,442 django.request ERROR Internal Server Error: /api/v1.0/documents/3fa9ad28-c31c-4d34-98af-d353628a7b28/ backend-1 | Traceback (most recent call last): backend-1 | File "/usr/local/lib/python3.13/site-packages/django/core/handlers/exception.py", line 55, in inner backend-1 | response = get_response(request) backend-1 | File "/usr/local/lib/python3.13/site-packages/django/core/handlers/base.py", line 197, in _get_response backend-1 | response = wrapped_callback(request, *callback_args, **callback_kwargs) backend-1 | File "/usr/local/lib/python3.13/site-packages/django/views/decorators/csrf.py", line 65, in _view_wrapper backend-1 | return view_func(request, *args, **kwargs) backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/viewsets.py", line 125, in view backend-1 | return self.dispatch(request, *args, **kwargs) backend-1 | ~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/views.py", line 515, in dispatch backend-1 | response = self.handle_exception(exc) backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/views.py", line 475, in handle_exception backend-1 | self.raise_uncaught_exception(exc) backend-1 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/views.py", line 486, in raise_uncaught_exception backend-1 | raise exc backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/views.py", line 512, in dispatch backend-1 | response = handler(request, *args, **kwargs) backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/mixins.py", line 82, in partial_update backend-1 | return self.update(request, *args, **kwargs) backend-1 | ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/mixins.py", line 68, in update backend-1 | self.perform_update(serializer) backend-1 | ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ backend-1 | File "/app/core/api/viewsets.py", line 567, in perform_update backend-1 | return super().perform_update(serializer) backend-1 | ~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/mixins.py", line 78, in perform_update backend-1 | serializer.save() backend-1 | ~~~~~~~~~~~~~~~^^ backend-1 | File "/app/core/api/serializers.py", line 291, in save backend-1 | return super().save(**kwargs) backend-1 | ~~~~~~~~~~~~^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/serializers.py", line 205, in save backend-1 | self.instance = self.update(self.instance, validated_data) backend-1 | ~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/rest_framework/serializers.py", line 1035, in update backend-1 | instance.save() backend-1 | ~~~~~~~~~~~~~^^ backend-1 | File "/app/core/models.py", line 467, in save backend-1 | default_storage.save(file_key, content_file) backend-1 | ~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/django/core/files/storage/base.py", line 49, in save backend-1 | name = self._save(name, content) backend-1 | File "/usr/local/lib/python3.13/site-packages/storages/backends/s3.py", line 565, in _save backend-1 | obj.upload_fileobj(content, ExtraArgs=params, Config=self.transfer_config) backend-1 | ~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/boto3/s3/inject.py", line 764, in object_upload_fileobj backend-1 | return self.meta.client.upload_fileobj( backend-1 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^ backend-1 | Fileobj=Fileobj, backend-1 | ^^^^^^^^^^^^^^^^ backend-1 | ...<4 lines>... backend-1 | Config=Config, backend-1 | ^^^^^^^^^^^^^^ backend-1 | ) backend-1 | ^ backend-1 | File "/usr/local/lib/python3.13/site-packages/botocore/context.py", line 123, in wrapper backend-1 | return func(*args, **kwargs) backend-1 | File "/usr/local/lib/python3.13/site-packages/boto3/s3/inject.py", line 675, in upload_fileobj backend-1 | return future.result() backend-1 | ~~~~~~~~~~~~~^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/s3transfer/futures.py", line 111, in result backend-1 | return self._coordinator.result() backend-1 | ~~~~~~~~~~~~~~~~~~~~~~~~^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/s3transfer/futures.py", line 287, in result backend-1 | raise self._exception backend-1 | File "/usr/local/lib/python3.13/site-packages/s3transfer/tasks.py", line 142, in call backend-1 | return self._execute_main(kwargs) backend-1 | ~~~~~~~~~~~~~~~~~~^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/s3transfer/tasks.py", line 165, in _execute_main backend-1 | return_value = self._main(**kwargs) backend-1 | File "/usr/local/lib/python3.13/site-packages/s3transfer/upload.py", line 796, in _main backend-1 | client.put_object(Bucket=bucket, Key=key, Body=body, **extra_args) backend-1 | ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/botocore/client.py", line 601, in _api_call backend-1 | return self._make_api_call(operation_name, kwargs) backend-1 | ~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^ backend-1 | File "/usr/local/lib/python3.13/site-packages/botocore/context.py", line 123, in wrapper backend-1 | return func(*args, **kwargs) backend-1 | File "/usr/local/lib/python3.13/site-packages/botocore/client.py", line 1074, in _make_api_call backend-1 | raise error_class(parsed_response, operation_name) backend-1 | botocore.exceptions.ClientError: An error occurred (XAmzContentSHA256Mismatch) when calling the PutObject operation: None

https://github.com/user-attachments/assets/dd9904c4-0491-4314-a1bd-c720b4526dfa

jco-c avatar Jul 20 '25 11:07 jco-c

I've done some more research and it seems to be an issue with my s3 backend, as it doesn't support the new PutObject operation introduced in boto3 1.36.0. This issue is described here: https://docs.ionos.com/cloud/storage-and-backup/ionos-object-storage/s3-tools/boto3-python-sdk

This can be fixed by adding the following env variables to the docker compose backend environment:

  • AWS_REQUEST_CHECKSUM_CALCULATION=when_required
  • AWS_RESPONSE_CHECKSUM_VALIDATION=when_required

If you want I can create a pull request to add that hint to the installation docs - does only make sense though, if this issue is not unique to my s3 provider, right? Let me know what you think :)

jco-c avatar Jul 20 '25 12:07 jco-c

@lunika your thoughts please 🙏

virgile-dev avatar Aug 28 '25 16:08 virgile-dev

Hi @jco-c

If you want to work on this solution, you have to declare this parameters as settings in the settings module src/backend/impress/settings.py. Declare the value use by default. Group them with the existing settings related to AWS.

Is it ok for you ?

lunika avatar Aug 28 '25 20:08 lunika

Hi, sure, I'll look into it! I think I however don't understand fully the role of the settings.py file (I am not that familiar with django). Will those variables get exportet as environment variables? To make boto3 compatible with older versions, those flags need to be set either as environment variables or in the Config object, propably in src/backend/core/api/utils.py, right?

jco-c avatar Aug 31 '25 13:08 jco-c

Hi,

We are not manipulating directly the boto3 library. We use the django-storages library, which is responsible to configure boto3. But in your case it seems it is not needed to use the settings, just declaring system wide environment variables could be enough. Does it work for you to just configure system wide environment variables ? If yes, then updating the documentation is enough.

Thanks.

lunika avatar Sep 01 '25 06:09 lunika