Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/6868.bugfix
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed repository version validation to allow legitimate shared artifacts between content units.
10 changes: 8 additions & 2 deletions pulpcore/plugin/repo_version_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,15 @@ def validate_version_paths(version):
Raises:
ValueError: If two artifact relative paths overlap
"""
paths = ContentArtifact.objects.filter(content__pk__in=version.content).values_list(
"relative_path", flat=True
# Get unique (path, artifact) pairs to allow artifacts shared across content
content_artifacts = (
ContentArtifact.objects.filter(content__pk__in=version.content)
.values_list("relative_path", "artifact")
.distinct()
)

paths = [path for path, artifact_id in content_artifacts]

try:
validate_file_paths(paths)
except ValueError as e:
Expand Down
85 changes: 84 additions & 1 deletion pulpcore/tests/unit/models/test_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

from itertools import compress

from pulpcore.plugin.models import Content, Repository
from pulpcore.plugin.models import Artifact, Content, ContentArtifact, Repository
from pulpcore.plugin.repo_version_utils import validate_version_paths


def pks_of_next_qs(qs_generator):
Expand Down Expand Up @@ -258,3 +259,85 @@ def test_next_version_with_multiple_versions():

assert repository.next_version == 4
assert repository.latest_version().number == 1


@pytest.mark.django_db
def test_shared_artifact_same_path_validation(tmp_path):
"""
Test that multiple content units can reference the same artifact with the same
relative path without causing validation errors.

This reproduces scenarios where different content units legitimately share
the same artifact (e.g. upstream source files).
"""
# Create a repository
repository = Repository.objects.create(name=uuid4())
repository.CONTENT_TYPES = [Content]

# Create a shared artifact using proper test pattern
artifact_path = tmp_path / "shared_file.txt"
artifact_path.write_text("Shared content data")
shared_artifact = Artifact.init_and_validate(str(artifact_path))
shared_artifact.save()

# Create two content units (simulates any content that shares artifacts)
content1 = Content.objects.create(pulp_type="core.content")
content2 = Content.objects.create(pulp_type="core.content")

# Both content units reference the same artifact with same path
ContentArtifact.objects.create(
content=content1, artifact=shared_artifact, relative_path="shared/common_file.txt"
)
ContentArtifact.objects.create(
content=content2, artifact=shared_artifact, relative_path="shared/common_file.txt"
)

# Create a repository version with both content units
with repository.new_version() as new_version:
new_version.add_content(Content.objects.filter(pk__in=[content1.pk, content2.pk]))

# This should not raise validation errors with our fix
validate_version_paths(new_version)


@pytest.mark.django_db
def test_different_artifacts_same_path_validation_fails(tmp_path):
"""
Test that different artifacts trying to use the same relative path
still fail validation (this is a real conflict that should be caught).
"""
# Create a repository
repository = Repository.objects.create(name=uuid4())
repository.CONTENT_TYPES = [Content]

# Create two different artifacts using proper test pattern
artifact1_path = tmp_path / "artifact1.txt"
artifact1_path.write_text("Content of first artifact")
artifact1 = Artifact.init_and_validate(str(artifact1_path))
artifact1.save()

artifact2_path = tmp_path / "artifact2.txt"
artifact2_path.write_text("Content of second artifact") # Different content
artifact2 = Artifact.init_and_validate(str(artifact2_path))
artifact2.save()

# Create two content units with different artifacts but same path
content1 = Content.objects.create(pulp_type="core.content")
content2 = Content.objects.create(pulp_type="core.content")

ContentArtifact.objects.create(
content=content1, artifact=artifact1, relative_path="conflicting/file.txt"
)
ContentArtifact.objects.create(
content=content2,
artifact=artifact2,
relative_path="conflicting/file.txt", # Same path, different artifact
)

# Create a repository version with both content units
with repository.new_version() as new_version:
new_version.add_content(Content.objects.filter(pk__in=[content1.pk, content2.pk]))

# This should raise a validation error due to path conflict
with pytest.raises(ValueError, match="Repository version errors"):
validate_version_paths(new_version)