-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Added upload support #113
base: main
Are you sure you want to change the base?
Added upload support #113
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
Added ability to upload Maven Artifacts to repositories. |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -29,3 +29,4 @@ set is the hostname and port:: | |
:maxdepth: 2 | ||
|
||
cache | ||
upload |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
Upload a Jar to a Maven Repository | ||
================================== | ||
|
||
Create a maven Repository for the | ||
|
||
Create a Maven Repository | ||
------------------------- | ||
|
||
``$ http POST http://localhost:24817/pulp/api/v3/repositories/maven/maven/ name='my-snapshot-repository'`` | ||
|
||
Create a Maven Distribution for the Maven Repository | ||
---------------------------------------------------- | ||
|
||
``$ http POST http://localhost:24817/pulp/api/v3/distributions/maven/maven/ name='my-snapshot-repository' base_path='my/local/snapshots' repository=$REPO_HREF`` | ||
|
||
|
||
.. code:: json | ||
|
||
{ | ||
"pulp_href": "/pulp/api/v3/distributions/67baa17e-0a9f-4302-b04a-dbf324d139de/" | ||
} | ||
|
||
Upload a Jar to the Repository | ||
------------------------------ | ||
|
||
``$ http --form POST http://localhost:24817/pulp/api/v3/content/maven/artifact/ group_id='org.openapitools' artifact_id='openapi-generator-cli' version='6.4.0-SNAPSHOT' filename='openapi-generator-cli-6.4.0.jar' file@./openapi-generator-cli.jar repository=$REPO_HREF`` | ||
|
||
|
||
.. code:: json | ||
|
||
{ | ||
"task": "/pulp/api/v3/tasks/03d5a40b-4bda-4ee7-96cb-f0639b6c5d6a/" | ||
} | ||
|
||
Add Pulp as mirror for Maven | ||
---------------------------- | ||
|
||
.. code:: xml | ||
|
||
<settings> | ||
<mirrors> | ||
<mirror> | ||
<id>pulp-maven-central</id> | ||
<name>Local Maven Central mirror </name> | ||
<url>http://localhost:24816/pulp/content/my/local/my/local/snapshots</url> | ||
<mirrorOf>central</mirrorOf> | ||
</mirror> | ||
</mirrors> | ||
</settings> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is it worth adding one last step which shows that the uploaded jar is consumable by the maven client? |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -2,48 +2,70 @@ | |||||||||||||||||||||||||
|
||||||||||||||||||||||||||
from rest_framework import serializers | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
from pulpcore.plugin import serializers as platform | ||||||||||||||||||||||||||
from pulpcore.plugin.serializers import ( | ||||||||||||||||||||||||||
ContentChecksumSerializer, | ||||||||||||||||||||||||||
DetailRelatedField, | ||||||||||||||||||||||||||
DistributionSerializer, | ||||||||||||||||||||||||||
RemoteSerializer, | ||||||||||||||||||||||||||
RepositorySerializer, | ||||||||||||||||||||||||||
SingleArtifactContentUploadSerializer, | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
from . import models | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class MavenRepositorySerializer(platform.RepositorySerializer): | ||||||||||||||||||||||||||
class MavenRepositorySerializer(RepositorySerializer): | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Serializer for Maven Repositories. | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class Meta: | ||||||||||||||||||||||||||
fields = platform.RepositorySerializer.Meta.fields | ||||||||||||||||||||||||||
fields = RepositorySerializer.Meta.fields | ||||||||||||||||||||||||||
model = models.MavenRepository | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class MavenArtifactSerializer(platform.SingleArtifactContentSerializer): | ||||||||||||||||||||||||||
class MavenArtifactSerializer(SingleArtifactContentUploadSerializer, ContentChecksumSerializer): | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
A Serializer for MavenArtifact. | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
group_id = serializers.CharField( | ||||||||||||||||||||||||||
help_text=_("Group Id of the artifact's package."), read_only=True | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
artifact_id = serializers.CharField( | ||||||||||||||||||||||||||
help_text=_("Artifact Id of the artifact's package."), read_only=True | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
version = serializers.CharField( | ||||||||||||||||||||||||||
help_text=_("Version of the artifact's package."), read_only=True | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
filename = serializers.CharField(help_text=_("Filename of the artifact."), read_only=True) | ||||||||||||||||||||||||||
group_id = serializers.CharField(help_text=_("Group Id of the artifact's package.")) | ||||||||||||||||||||||||||
artifact_id = serializers.CharField(help_text=_("Artifact Id of the artifact's package.")) | ||||||||||||||||||||||||||
version = serializers.CharField(help_text=_("Version of the artifact's package.")) | ||||||||||||||||||||||||||
filename = serializers.CharField(help_text=_("Filename of the artifact.")) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def deferred_validate(self, data): | ||||||||||||||||||||||||||
"""Validate the FileContent data.""" | ||||||||||||||||||||||||||
data = super().deferred_validate(data) | ||||||||||||||||||||||||||
data["relative_path"] = ( | ||||||||||||||||||||||||||
f"{data['group_id'].replace('.', '/')}/{data['artifact_id']}/{data['version']}/" | ||||||||||||||||||||||||||
f"{data['filename']}" | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
return data | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
def retrieve(self, validated_data): | ||||||||||||||||||||||||||
content = models.MavenArtifact.objects.filter( | ||||||||||||||||||||||||||
group_id=validated_data["group_id"], | ||||||||||||||||||||||||||
artifact_id=validated_data["artifact_id"], | ||||||||||||||||||||||||||
version=validated_data["version"], | ||||||||||||||||||||||||||
filename=validated_data["filename"], | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
return content.first() | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class Meta: | ||||||||||||||||||||||||||
fields = platform.SingleArtifactContentSerializer.Meta.fields + ( | ||||||||||||||||||||||||||
"group_id", | ||||||||||||||||||||||||||
"artifact_id", | ||||||||||||||||||||||||||
"version", | ||||||||||||||||||||||||||
"filename", | ||||||||||||||||||||||||||
fields = ( | ||||||||||||||||||||||||||
SingleArtifactContentUploadSerializer.Meta.fields | ||||||||||||||||||||||||||
+ ContentChecksumSerializer.Meta.fields | ||||||||||||||||||||||||||
+ ("group_id", "artifact_id", "version", "filename") | ||||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
# Remove relative_path | ||||||||||||||||||||||||||
fields = tuple(field for field in fields if field != "relative_path") | ||||||||||||||||||||||||||
Comment on lines
+56
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be a single expression? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I knew i've seen it before: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Using a set and then casting it into a tuple was clever. 🚀
Comment on lines
+56
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. something like this @mdellweg? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, exactly. |
||||||||||||||||||||||||||
model = models.MavenArtifact | ||||||||||||||||||||||||||
# Validation occurs in the task. | ||||||||||||||||||||||||||
validators = [] | ||||||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What was the validation about that we kill here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unique Togeth Validation .. I am still not sure how it is getting injected here. I followed the example from pulp_file for FileContentSerializer which doesn't have any validators, however, I kept having the UniqueTogetherValidator getting injected. This was the only way I found to fix that problem. However, I am open to other solutions. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't know of a better way.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @quba42 see, i knew i saw it before somewhere... |
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class MavenRemoteSerializer(platform.RemoteSerializer): | ||||||||||||||||||||||||||
class MavenRemoteSerializer(RemoteSerializer): | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
A Serializer for MavenRemote. | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
@@ -58,16 +80,16 @@ class Meta: | |||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class Meta: | ||||||||||||||||||||||||||
fields = platform.RemoteSerializer.Meta.fields | ||||||||||||||||||||||||||
fields = RemoteSerializer.Meta.fields | ||||||||||||||||||||||||||
model = models.MavenRemote | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class MavenDistributionSerializer(platform.DistributionSerializer): | ||||||||||||||||||||||||||
class MavenDistributionSerializer(DistributionSerializer): | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
Serializer for Maven Distributions. | ||||||||||||||||||||||||||
""" | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
remote = platform.DetailRelatedField( | ||||||||||||||||||||||||||
remote = DetailRelatedField( | ||||||||||||||||||||||||||
required=False, | ||||||||||||||||||||||||||
help_text=_("Remote that can be used to fetch content when using pull-through caching."), | ||||||||||||||||||||||||||
queryset=models.MavenRemote.objects.all(), | ||||||||||||||||||||||||||
|
@@ -76,5 +98,5 @@ class MavenDistributionSerializer(platform.DistributionSerializer): | |||||||||||||||||||||||||
) | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
class Meta: | ||||||||||||||||||||||||||
fields = platform.DistributionSerializer.Meta.fields + ("remote",) | ||||||||||||||||||||||||||
fields = DistributionSerializer.Meta.fields + ("remote",) | ||||||||||||||||||||||||||
model = models.MavenDistribution |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,62 +1,78 @@ | ||
from pulpcore.plugin import viewsets as core | ||
from pulpcore.plugin.viewsets import ( | ||
ContentFilter, | ||
DistributionViewSet, | ||
RemoteViewSet, | ||
RepositoryVersionViewSet, | ||
RepositoryViewSet, | ||
SingleArtifactContentUploadViewSet, | ||
) | ||
from pulpcore.plugin.actions import ModifyRepositoryActionMixin | ||
|
||
from . import models, serializers | ||
|
||
from pulp_maven.app.models import MavenArtifact, MavenRemote, MavenRepository, MavenDistribution | ||
|
||
class MavenArtifactFilter(core.ContentFilter): | ||
from pulp_maven.app.serializers import ( | ||
MavenArtifactSerializer, | ||
MavenRemoteSerializer, | ||
MavenRepositorySerializer, | ||
MavenDistributionSerializer, | ||
) | ||
|
||
|
||
class MavenArtifactFilter(ContentFilter): | ||
""" | ||
FilterSet for MavenArtifact. | ||
""" | ||
|
||
class Meta: | ||
model = models.MavenArtifact | ||
model = MavenArtifact | ||
fields = ["group_id", "artifact_id", "version", "filename"] | ||
|
||
|
||
class MavenArtifactViewSet(core.ContentViewSet): | ||
class MavenArtifactViewSet(SingleArtifactContentUploadViewSet): | ||
""" | ||
A ViewSet for MavenArtifact. | ||
""" | ||
|
||
endpoint_name = "artifact" | ||
queryset = models.MavenArtifact.objects.all() | ||
serializer_class = serializers.MavenArtifactSerializer | ||
queryset = MavenArtifact.objects.prefetch_related("_artifacts").all() | ||
serializer_class = MavenArtifactSerializer | ||
filterset_class = MavenArtifactFilter | ||
|
||
|
||
class MavenRemoteViewSet(core.RemoteViewSet): | ||
class MavenRemoteViewSet(RemoteViewSet): | ||
""" | ||
A ViewSet for MavenRemote. | ||
""" | ||
|
||
endpoint_name = "maven" | ||
queryset = models.MavenRemote.objects.all() | ||
serializer_class = serializers.MavenRemoteSerializer | ||
queryset = MavenRemote.objects.all() | ||
serializer_class = MavenRemoteSerializer | ||
|
||
|
||
class MavenRepositoryViewSet(core.RepositoryViewSet): | ||
class MavenRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin): | ||
""" | ||
A ViewSet for MavenRemote. | ||
""" | ||
|
||
endpoint_name = "maven" | ||
queryset = models.MavenRepository.objects.all() | ||
serializer_class = serializers.MavenRepositorySerializer | ||
queryset = MavenRepository.objects.all() | ||
serializer_class = MavenRepositorySerializer | ||
|
||
|
||
class MavenRepositoryVersionViewSet(core.RepositoryVersionViewSet): | ||
class MavenRepositoryVersionViewSet(RepositoryVersionViewSet): | ||
""" | ||
MavenRepositoryVersion represents a single Maven repository version. | ||
""" | ||
|
||
parent_viewset = MavenRepositoryViewSet | ||
|
||
|
||
class MavenDistributionViewSet(core.DistributionViewSet): | ||
class MavenDistributionViewSet(DistributionViewSet): | ||
""" | ||
ViewSet for Maven Distributions. | ||
""" | ||
|
||
endpoint_name = "maven" | ||
queryset = models.MavenDistribution.objects.all() | ||
serializer_class = serializers.MavenDistributionSerializer | ||
queryset = MavenDistribution.objects.all() | ||
serializer_class = MavenDistributionSerializer |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import hashlib | ||
from urllib.parse import urljoin | ||
|
||
from pulp_maven.tests.functional.utils import download_file | ||
|
||
|
||
def test_upload_workflow( | ||
maven_repo_api_client, | ||
maven_repo_factory, | ||
random_maven_artifact_factory, | ||
maven_artifact_api_client, | ||
maven_distribution_factory, | ||
gen_object_with_cleanup, | ||
): | ||
# Create a repository and assert that the latest version is 0 | ||
repo = maven_repo_factory() | ||
assert repo.latest_version_href.endswith("/versions/0/") | ||
|
||
# Create a random jar | ||
jar_file = random_maven_artifact_factory() | ||
|
||
# Upload the jar into the repository | ||
artifact_kwargs = dict( | ||
group_id=jar_file["group_id"], | ||
artifact_id=jar_file["artifact_id"], | ||
version=jar_file["version"], | ||
filename=jar_file["filename"], | ||
file=jar_file["full_path"], | ||
repository=repo.pulp_href, | ||
) | ||
maven_artifact = gen_object_with_cleanup(maven_artifact_api_client, **artifact_kwargs) | ||
|
||
# Assert that a Maven Artifact was created | ||
assert maven_artifact.group_id == jar_file["group_id"] | ||
assert maven_artifact.artifact_id == jar_file["artifact_id"] | ||
assert maven_artifact.version == jar_file["version"] | ||
assert maven_artifact.filename == jar_file["filename"] | ||
|
||
# Assert that a repository version was created | ||
repo = maven_repo_api_client.read(repo.pulp_href) | ||
assert repo.latest_version_href.endswith("/versions/1/") | ||
|
||
# Assert that this Maven Artifact is in the repository version | ||
content_in_repo_version = maven_artifact_api_client.list( | ||
repository_version=repo.latest_version_href | ||
) | ||
assert content_in_repo_version.results[0].pulp_href == maven_artifact.pulp_href | ||
|
||
# Create a second repository and assert that latest version is 0 | ||
repo2 = maven_repo_factory() | ||
assert repo2.latest_version_href.endswith("/versions/0/") | ||
|
||
# Assert that the same content unit can be uploaded again | ||
artifact_kwargs["repository"] = repo2.pulp_href | ||
maven_artifact2 = gen_object_with_cleanup(maven_artifact_api_client, **artifact_kwargs) | ||
|
||
# Assert that the existing artifact was identified by the upload API. | ||
assert maven_artifact.pulp_href == maven_artifact2.pulp_href | ||
|
||
# Assert that a new repository version was created. | ||
repo2 = maven_repo_api_client.read(repo2.pulp_href) | ||
assert repo2.latest_version_href.endswith("/versions/1/") | ||
|
||
# Assert that this Maven Artifact is in the repository version | ||
content_in_repo2_version = maven_artifact_api_client.list( | ||
repository_version=repo2.latest_version_href | ||
) | ||
assert content_in_repo2_version.results[0].pulp_href == maven_artifact2.pulp_href | ||
|
||
# Create a distribution and serve repo | ||
distribution = maven_distribution_factory(repository=repo.pulp_href) | ||
|
||
# Download the jar from the distribution | ||
unit_path = ( | ||
f"{jar_file['group_id'].replace('.', '/')}/{jar_file['artifact_id']}/" | ||
f"{jar_file['version']}/{jar_file['filename']}" | ||
) | ||
pulp_unit_url = urljoin(distribution.base_url, unit_path) | ||
downloaded_file = download_file(pulp_unit_url) | ||
downloaded_file_checksum = hashlib.sha256(downloaded_file.body).hexdigest() | ||
|
||
# Assert that the downloaded file's checksum matches the original | ||
assert jar_file["sha256"] == downloaded_file_checksum |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leftovers