prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Overwriting existing packages in backend storage can lead to caching issues
  If an existing package is re-added to pulp, the default behavior will overwrite the existing file in backing storage. This is typically fine.
  - If using Azure Blobstore, the timestamp of the blob is updated (Last-Modified time and ETag).
  - Conversely, some CDN's (notably Azure Front Door) use Last-Modified Time as a signal that a file in origin has updated.
  - This can lead to poor cache behavior, and in some cases, incomplete downloads as the CDN attempts to resolve disparate content.
  - If we set `AZURE_OVERWRITE_FILES` to `false` this partially mitigates the issue (Last-Modified/ETag are unmodified). However, this results in duplicate copies written to storage (with a suffix to differentiate from the original).
  - We should have an option that does "nothing" if the uploaded file already exists (don't overwrite, and don't write a new copy).
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `pulpcore/plugin/viewsets/content.py`
  Content:
  ```
  1 from drf_spectacular.utils import extend_schema
  2 
  3 from django.db import DatabaseError
  4 from django.db.utils import IntegrityError
  5 
  6 from pulpcore.app import tasks
  7 from pulpcore.plugin.serializers import (
  8     ArtifactSerializer,
  9     AsyncOperationResponseSerializer,
  10 )
  11 from pulpcore.plugin.models import Artifact, PulpTemporaryFile
  12 from pulpcore.plugin.tasking import dispatch
  13 from pulpcore.plugin.viewsets import (
  14     ContentViewSet,
  15     OperationPostponedResponse,
  16 )
  17 
  18 
  19 class DefaultDeferredContextMixin:
  20     """A mixin that provides a method for retrieving the default deferred context."""
  21 
  22     def get_deferred_context(self, request):
  23         """
  24         Supply context for deferred validation.
  25 
  26         When overwriting this method, it must return a dict, that is JSON serializable by
  27         and does _not_ contain 'request' as a key.
  28         """
  29         return {}
  30 
  31 
  32 class NoArtifactContentViewSet(DefaultDeferredContextMixin, ContentViewSet):
  33     """A ViewSet for content creation that does not require a file to be uploaded."""
  34 
  35     @extend_schema(
  36         description="Trigger an asynchronous task to create content,"
  37         "optionally create new repository version.",
  38         responses={202: AsyncOperationResponseSerializer},
  39     )
  40     def create(self, request):
  41         """Create a content unit."""
  42         serializer = self.get_serializer(data=request.data)
  43         serializer.is_valid(raise_exception=True)
  44 
  45         exclusive_resources = [
  46             item for item in (serializer.validated_data.get(key) for key in ("repository",)) if item
  47         ]
  48 
  49         task = dispatch(
  50             tasks.base.general_create,
  51             exclusive_resources=exclusive_resources,
  52             args=(self.queryset.model._meta.app_label, serializer.__class__.__name__),
  53             kwargs={
  54                 "data": {k: v for k, v in request.data.items()},
  55                 "context": self.get_deferred_context(request),
  56             },
  57         )
  58         return OperationPostponedResponse(task, request)
  59 
  60 
  61 class NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):
  62     """A ViewSet for uploads that do not require to store an uploaded content as an Artifact."""
  63 
  64     @extend_schema(
  65         description="Trigger an asynchronous task to create content,"
  66         "optionally create new repository version.",
  67         responses={202: AsyncOperationResponseSerializer},
  68     )
  69     def create(self, request):
  70         """Create a content unit."""
  71         serializer = self.get_serializer(data=request.data)
  72         serializer.is_valid(raise_exception=True)
  73 
  74         task_payload = {k: v for k, v in request.data.items()}
  75 
  76         file_content = task_payload.pop("file", None)
  77         temp_file = PulpTemporaryFile.init_and_validate(file_content)
  78         temp_file.save()
  79 
  80         exclusive_resources = [
  81             item for item in (serializer.validated_data.get(key) for key in ("repository",)) if item
  82         ]
  83 
  84         app_label = self.queryset.model._meta.app_label
  85         context = self.get_deferred_context(request)
  86         context["pulp_temp_file_pk"] = str(temp_file.pk)
  87         task = dispatch(
  88             tasks.base.general_create,
  89             exclusive_resources=exclusive_resources,
  90             args=(app_label, serializer.__class__.__name__),
  91             kwargs={"data": task_payload, "context": context},
  92         )
  93         return OperationPostponedResponse(task, request)
  94 
  95 
  96 class SingleArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):
  97     """A ViewSet which can be used to store an uploaded content as an Artifact."""
  98 
  99     @extend_schema(
  100         description="Trigger an asynchronous task to create content,"
  101         "optionally create new repository version.",
  102         responses={202: AsyncOperationResponseSerializer},
  103     )
  104     def create(self, request):
  105         """Create a content unit."""
  106         serializer = self.get_serializer(data=request.data)
  107         serializer.is_valid(raise_exception=True)
  108 
  109         task_payload = self.init_content_data(serializer, request)
  110 
  111         exclusive_resources = [
  112             item
  113             for item in (serializer.validated_data.get(key) for key in ("upload", "repository"))
  114             if item
  115         ]
  116 
  117         app_label = self.queryset.model._meta.app_label
  118         task = dispatch(
  119             tasks.base.general_create,
  120             exclusive_resources=exclusive_resources,
  121             args=(app_label, serializer.__class__.__name__),
  122             kwargs={
  123                 "data": task_payload,
  124                 "context": self.get_deferred_context(request),
  125             },
  126         )
  127         return OperationPostponedResponse(task, request)
  128 
  129     def init_content_data(self, serializer, request):
  130         """Initialize the reference to an Artifact along with relevant task's payload data."""
  131         task_payload = {k: v for k, v in request.data.items()}
  132         if "file" in task_payload:
  133             # in the upload code path make sure, the artifact exists, and the 'file'
  134             # parameter is replaced by 'artifact'
  135             artifact = Artifact.init_and_validate(task_payload.pop("file"))
  136             try:
  137                 artifact.save()
  138             except IntegrityError:
  139                 # if artifact already exists, let's use it
  140                 try:
  141                     artifact = Artifact.objects.get(
  142                         sha256=artifact.sha256, pulp_domain=request.pulp_domain
  143                     )
  144                     artifact.touch()
  145                 except (Artifact.DoesNotExist, DatabaseError):
  146                     # the artifact has since been removed from when we first attempted to save it
  147                     artifact.save()
  148 
  149             task_payload["artifact"] = ArtifactSerializer(
  150                 artifact, context={"request": request}
  151             ).data["pulp_href"]
  152         elif "artifact" in serializer.validated_data:
  153             serializer.validated_data["artifact"].touch()
  154         # In case of a provided upload object, there is no artifact to touch yet.
  155 
  156         return task_payload
  157
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/pulpcore/plugin/viewsets/content.py b/pulpcore/plugin/viewsets/content.py
  --- a/pulpcore/plugin/viewsets/content.py
  +++ b/pulpcore/plugin/viewsets/content.py
  @@ -133,18 +133,20 @@
               # in the upload code path make sure, the artifact exists, and the 'file'
               # parameter is replaced by 'artifact'
               artifact = Artifact.init_and_validate(task_payload.pop("file"))
  +            # if artifact already exists, let's use it
               try:
  -                artifact.save()
  -            except IntegrityError:
  -                # if artifact already exists, let's use it
  +                artifact = Artifact.objects.get(
  +                    sha256=artifact.sha256, pulp_domain=request.pulp_domain
  +                )
  +                artifact.touch()
  +            except (Artifact.DoesNotExist, DatabaseError):
                   try:
  +                    artifact.save()
  +                except IntegrityError:
                       artifact = Artifact.objects.get(
                           sha256=artifact.sha256, pulp_domain=request.pulp_domain
                       )
                       artifact.touch()
  -                except (Artifact.DoesNotExist, DatabaseError):
  -                    # the artifact has since been removed from when we first attempted to save it
  -                    artifact.save()
   
               task_payload["artifact"] = ArtifactSerializer(
                   artifact, context={"request": request}
  
verification_info: {"golden_diff": "diff --git a/pulpcore/plugin/viewsets/content.py b/pulpcore/plugin/viewsets/content.py\n--- a/pulpcore/plugin/viewsets/content.py\n+++ b/pulpcore/plugin/viewsets/content.py\n@@ -133,18 +133,20 @@\n             # in the upload code path make sure, the artifact exists, and the 'file'\n             # parameter is replaced by 'artifact'\n             artifact = Artifact.init_and_validate(task_payload.pop(\"file\"))\n+            # if artifact already exists, let's use it\n             try:\n-                artifact.save()\n-            except IntegrityError:\n-                # if artifact already exists, let's use it\n+                artifact = Artifact.objects.get(\n+                    sha256=artifact.sha256, pulp_domain=request.pulp_domain\n+                )\n+                artifact.touch()\n+            except (Artifact.DoesNotExist, DatabaseError):\n                 try:\n+                    artifact.save()\n+                except IntegrityError:\n                     artifact = Artifact.objects.get(\n                         sha256=artifact.sha256, pulp_domain=request.pulp_domain\n                     )\n                     artifact.touch()\n-                except (Artifact.DoesNotExist, DatabaseError):\n-                    # the artifact has since been removed from when we first attempted to save it\n-                    artifact.save()\n \n             task_payload[\"artifact\"] = ArtifactSerializer(\n                 artifact, context={\"request\": request}\n", "issue": "Overwriting existing packages in backend storage can lead to caching issues\nIf an existing package is re-added to pulp, the default behavior will overwrite the existing file in backing storage. This is typically fine.\r\n- If using Azure Blobstore, the timestamp of the blob is updated (Last-Modified time and ETag).\r\n- Conversely, some CDN's (notably Azure Front Door) use Last-Modified Time as a signal that a file in origin has updated.\r\n- This can lead to poor cache behavior, and in some cases, incomplete downloads as the CDN attempts to resolve disparate content.\r\n- If we set `AZURE_OVERWRITE_FILES` to `false` this partially mitigates the issue (Last-Modified/ETag are unmodified). However, this results in duplicate copies written to storage (with a suffix to differentiate from the original).\r\n- We should have an option that does \"nothing\" if the uploaded file already exists (don't overwrite, and don't write a new copy).\n", "before_files": [{"content": "from drf_spectacular.utils import extend_schema\n\nfrom django.db import DatabaseError\nfrom django.db.utils import IntegrityError\n\nfrom pulpcore.app import tasks\nfrom pulpcore.plugin.serializers import (\n    ArtifactSerializer,\n    AsyncOperationResponseSerializer,\n)\nfrom pulpcore.plugin.models import Artifact, PulpTemporaryFile\nfrom pulpcore.plugin.tasking import dispatch\nfrom pulpcore.plugin.viewsets import (\n    ContentViewSet,\n    OperationPostponedResponse,\n)\n\n\nclass DefaultDeferredContextMixin:\n    \"\"\"A mixin that provides a method for retrieving the default deferred context.\"\"\"\n\n    def get_deferred_context(self, request):\n        \"\"\"\n        Supply context for deferred validation.\n\n        When overwriting this method, it must return a dict, that is JSON serializable by\n        and does _not_ contain 'request' as a key.\n        \"\"\"\n        return {}\n\n\nclass NoArtifactContentViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet for content creation that does not require a file to be uploaded.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        exclusive_resources = [\n            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n        ]\n\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(self.queryset.model._meta.app_label, serializer.__class__.__name__),\n            kwargs={\n                \"data\": {k: v for k, v in request.data.items()},\n                \"context\": self.get_deferred_context(request),\n            },\n        )\n        return OperationPostponedResponse(task, request)\n\n\nclass NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet for uploads that do not require to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = {k: v for k, v in request.data.items()}\n\n        file_content = task_payload.pop(\"file\", None)\n        temp_file = PulpTemporaryFile.init_and_validate(file_content)\n        temp_file.save()\n\n        exclusive_resources = [\n            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        context = self.get_deferred_context(request)\n        context[\"pulp_temp_file_pk\"] = str(temp_file.pk)\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\"data\": task_payload, \"context\": context},\n        )\n        return OperationPostponedResponse(task, request)\n\n\nclass SingleArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet which can be used to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = self.init_content_data(serializer, request)\n\n        exclusive_resources = [\n            item\n            for item in (serializer.validated_data.get(key) for key in (\"upload\", \"repository\"))\n            if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\n                \"data\": task_payload,\n                \"context\": self.get_deferred_context(request),\n            },\n        )\n        return OperationPostponedResponse(task, request)\n\n    def init_content_data(self, serializer, request):\n        \"\"\"Initialize the reference to an Artifact along with relevant task's payload data.\"\"\"\n        task_payload = {k: v for k, v in request.data.items()}\n        if \"file\" in task_payload:\n            # in the upload code path make sure, the artifact exists, and the 'file'\n            # parameter is replaced by 'artifact'\n            artifact = Artifact.init_and_validate(task_payload.pop(\"file\"))\n            try:\n                artifact.save()\n            except IntegrityError:\n                # if artifact already exists, let's use it\n                try:\n                    artifact = Artifact.objects.get(\n                        sha256=artifact.sha256, pulp_domain=request.pulp_domain\n                    )\n                    artifact.touch()\n                except (Artifact.DoesNotExist, DatabaseError):\n                    # the artifact has since been removed from when we first attempted to save it\n                    artifact.save()\n\n            task_payload[\"artifact\"] = ArtifactSerializer(\n                artifact, context={\"request\": request}\n            ).data[\"pulp_href\"]\n        elif \"artifact\" in serializer.validated_data:\n            serializer.validated_data[\"artifact\"].touch()\n        # In case of a provided upload object, there is no artifact to touch yet.\n\n        return task_payload\n", "path": "pulpcore/plugin/viewsets/content.py"}], "after_files": [{"content": "from drf_spectacular.utils import extend_schema\n\nfrom django.db import DatabaseError\nfrom django.db.utils import IntegrityError\n\nfrom pulpcore.app import tasks\nfrom pulpcore.plugin.serializers import (\n    ArtifactSerializer,\n    AsyncOperationResponseSerializer,\n)\nfrom pulpcore.plugin.models import Artifact, PulpTemporaryFile\nfrom pulpcore.plugin.tasking import dispatch\nfrom pulpcore.plugin.viewsets import (\n    ContentViewSet,\n    OperationPostponedResponse,\n)\n\n\nclass DefaultDeferredContextMixin:\n    \"\"\"A mixin that provides a method for retrieving the default deferred context.\"\"\"\n\n    def get_deferred_context(self, request):\n        \"\"\"\n        Supply context for deferred validation.\n\n        When overwriting this method, it must return a dict, that is JSON serializable by\n        and does _not_ contain 'request' as a key.\n        \"\"\"\n        return {}\n\n\nclass NoArtifactContentViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet for content creation that does not require a file to be uploaded.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        exclusive_resources = [\n            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n        ]\n\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(self.queryset.model._meta.app_label, serializer.__class__.__name__),\n            kwargs={\n                \"data\": {k: v for k, v in request.data.items()},\n                \"context\": self.get_deferred_context(request),\n            },\n        )\n        return OperationPostponedResponse(task, request)\n\n\nclass NoArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet for uploads that do not require to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = {k: v for k, v in request.data.items()}\n\n        file_content = task_payload.pop(\"file\", None)\n        temp_file = PulpTemporaryFile.init_and_validate(file_content)\n        temp_file.save()\n\n        exclusive_resources = [\n            item for item in (serializer.validated_data.get(key) for key in (\"repository\",)) if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        context = self.get_deferred_context(request)\n        context[\"pulp_temp_file_pk\"] = str(temp_file.pk)\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\"data\": task_payload, \"context\": context},\n        )\n        return OperationPostponedResponse(task, request)\n\n\nclass SingleArtifactContentUploadViewSet(DefaultDeferredContextMixin, ContentViewSet):\n    \"\"\"A ViewSet which can be used to store an uploaded content as an Artifact.\"\"\"\n\n    @extend_schema(\n        description=\"Trigger an asynchronous task to create content,\"\n        \"optionally create new repository version.\",\n        responses={202: AsyncOperationResponseSerializer},\n    )\n    def create(self, request):\n        \"\"\"Create a content unit.\"\"\"\n        serializer = self.get_serializer(data=request.data)\n        serializer.is_valid(raise_exception=True)\n\n        task_payload = self.init_content_data(serializer, request)\n\n        exclusive_resources = [\n            item\n            for item in (serializer.validated_data.get(key) for key in (\"upload\", \"repository\"))\n            if item\n        ]\n\n        app_label = self.queryset.model._meta.app_label\n        task = dispatch(\n            tasks.base.general_create,\n            exclusive_resources=exclusive_resources,\n            args=(app_label, serializer.__class__.__name__),\n            kwargs={\n                \"data\": task_payload,\n                \"context\": self.get_deferred_context(request),\n            },\n        )\n        return OperationPostponedResponse(task, request)\n\n    def init_content_data(self, serializer, request):\n        \"\"\"Initialize the reference to an Artifact along with relevant task's payload data.\"\"\"\n        task_payload = {k: v for k, v in request.data.items()}\n        if \"file\" in task_payload:\n            # in the upload code path make sure, the artifact exists, and the 'file'\n            # parameter is replaced by 'artifact'\n            artifact = Artifact.init_and_validate(task_payload.pop(\"file\"))\n            # if artifact already exists, let's use it\n            try:\n                artifact = Artifact.objects.get(\n                    sha256=artifact.sha256, pulp_domain=request.pulp_domain\n                )\n                artifact.touch()\n            except (Artifact.DoesNotExist, DatabaseError):\n                try:\n                    artifact.save()\n                except IntegrityError:\n                    artifact = Artifact.objects.get(\n                        sha256=artifact.sha256, pulp_domain=request.pulp_domain\n                    )\n                    artifact.touch()\n\n            task_payload[\"artifact\"] = ArtifactSerializer(\n                artifact, context={\"request\": request}\n            ).data[\"pulp_href\"]\n        elif \"artifact\" in serializer.validated_data:\n            serializer.validated_data[\"artifact\"].touch()\n        # In case of a provided upload object, there is no artifact to touch yet.\n\n        return task_payload\n", "path": "pulpcore/plugin/viewsets/content.py"}]}