diff --git a/CHANGES/2040.bugfix b/CHANGES/2040.bugfix new file mode 100644 index 000000000..d95c1f32d --- /dev/null +++ b/CHANGES/2040.bugfix @@ -0,0 +1 @@ +Fixed a regression with migration 0056 failing on multiple null values on a unique constraint. diff --git a/pulp_ansible/app/migrations/0056_collectionversion_sha256.py b/pulp_ansible/app/migrations/0056_collectionversion_sha256.py index 7bb571b80..82f2d506e 100644 --- a/pulp_ansible/app/migrations/0056_collectionversion_sha256.py +++ b/pulp_ansible/app/migrations/0056_collectionversion_sha256.py @@ -16,8 +16,4 @@ class Migration(migrations.Migration): field=models.CharField(default='', max_length=64, null=True), preserve_default=False, ), - migrations.AlterUniqueTogether( - name="collectionversion", - unique_together={("sha256",), ("namespace", "name", "version")}, - ), ] diff --git a/pulp_ansible/app/migrations/0058_fix_0056_regression.py b/pulp_ansible/app/migrations/0058_fix_0056_regression.py new file mode 100644 index 000000000..7767326ad --- /dev/null +++ b/pulp_ansible/app/migrations/0058_fix_0056_regression.py @@ -0,0 +1,30 @@ +# Generated by Django 4.2.16 on 2024-11-22 12:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("ansible", "0057_collectionversion_sha256_migrate"), + ] + + operations = [ + # --------------------------------------------------------------------- + # In the current timeline of migrations, this step seems superfluous. + # But in version 0.23.0 we shipped a bad version of 0056. + # This repairs it. + migrations.AlterUniqueTogether( + name="collectionversion", + unique_together={("namespace", "name", "version")}, + ), + # --------------------------------------------------------------------- + migrations.AddConstraint( + model_name="collectionversion", + constraint=models.UniqueConstraint( + condition=models.Q(("sha256__isnull", False)), + fields=("sha256",), + name="unique_sha256", + ), + ), + ] diff --git a/pulp_ansible/app/models.py b/pulp_ansible/app/models.py index d4accf68f..0466e550f 100644 --- a/pulp_ansible/app/models.py +++ b/pulp_ansible/app/models.py @@ -236,13 +236,18 @@ def __str__(self): class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = (("namespace", "name", "version"), ("sha256",)) + unique_together = (("namespace", "name", "version"),) constraints = [ + UniqueConstraint( + fields=("sha256",), + name="unique_sha256", + condition=Q(sha256__isnull=False), + ), UniqueConstraint( fields=("collection", "is_highest"), name="unique_is_highest", condition=Q(is_highest=True), - ) + ), ] diff --git a/pulp_ansible/tests/unit/migrations/test_0057_collection_version_sha256_migrate.py b/pulp_ansible/tests/unit/migrations/test_0057_collection_version_sha256_migrate.py index c81f89afe..ef431d136 100644 --- a/pulp_ansible/tests/unit/migrations/test_0057_collection_version_sha256_migrate.py +++ b/pulp_ansible/tests/unit/migrations/test_0057_collection_version_sha256_migrate.py @@ -7,33 +7,49 @@ def test_collection_version_sha256_migrate(migrate): Collection = apps.get_model("ansible", "Collection") CollectionVersion = apps.get_model("ansible", "CollectionVersion") - artifact = Artifact.objects.create( - size=8, sha256="SENTINEL", file=SimpleUploadedFile("foo", b"deadbeef") + collection = Collection.objects.create(namespace="snap", name="crackle") + + # Create two collection versions, because `sha256=null` can violate the uniquenes constraint. + artifact1 = Artifact.objects.create( + size=8, sha256="SENTINEL1", file=SimpleUploadedFile("foo", b"deadbeef") ) - collection = Collection.objects.create( + cv1 = CollectionVersion.objects.create( + pulp_type="collection_version", + collection=collection, namespace="snap", name="crackle", + version="1.0.0", + version_minor="1", + version_major="0", + version_patch="0", ) - cv = CollectionVersion.objects.create( + cv1.contentartifact_set.create(artifact=artifact1) + + artifact2 = Artifact.objects.create( + size=8, sha256="SENTINEL2", file=SimpleUploadedFile("foo", b"beefdead") + ) + cv2 = CollectionVersion.objects.create( pulp_type="collection_version", collection=collection, namespace="snap", name="crackle", - version="pop", - version_minor="1", - version_major="2", - version_patch="3", + version="2.0.0", + version_minor="2", + version_major="0", + version_patch="0", ) - cv.contentartifact_set.create(artifact=artifact) + cv2.contentartifact_set.create(artifact=artifact2) apps = migrate([("ansible", "0056_collectionversion_sha256")]) CollectionVersion = apps.get_model("ansible", "CollectionVersion") - cv = CollectionVersion.objects.get(pk=cv.pk) - assert cv.sha256 == "" + cv1 = CollectionVersion.objects.get(pk=cv1.pk) + assert cv1.sha256 == "" apps = migrate([("ansible", "0057_collectionversion_sha256_migrate")]) CollectionVersion = apps.get_model("ansible", "CollectionVersion") - cv = CollectionVersion.objects.get(pk=cv.pk) - assert cv.sha256 == "SENTINEL" + cv1 = CollectionVersion.objects.get(pk=cv1.pk) + assert cv1.sha256 == "SENTINEL1" + + apps = migrate([("ansible", "0058_fix_0056_regression")])