Skip to content

Commit

Permalink
Add the pull-through cache feature.
Browse files Browse the repository at this point in the history
Closes #278
  • Loading branch information
decko committed Feb 19, 2025
1 parent 25360f5 commit f65095d
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 34 deletions.
1 change: 1 addition & 0 deletions CHANGES/278.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added pull-through cache feature.
36 changes: 18 additions & 18 deletions pulp_npm/app/models.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
"""
Check `Plugin Writer's Guide`_ for more details.
.. _Plugin Writer's Guide:
http://docs.pulpproject.org/en/3.0/nightly/plugins/plugin-writer/index.html
"""

import json
from logging import getLogger

Expand All @@ -21,7 +14,7 @@
)

from pulpcore.plugin.util import get_domain_pk
from .utils import urlpath_sanitize
from .utils import urlpath_sanitize, extract_package_info

logger = getLogger(__name__)

Expand All @@ -32,16 +25,6 @@ class Package(Content):
Define fields you need for your new content type and
specify uniqueness constraint to identify unit of this type.
For example::
field1 = models.TextField()
field2 = models.IntegerField()
field3 = models.CharField()
class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
unique_together = (field1, field2)
"""

TYPE = "package"
Expand All @@ -58,6 +41,12 @@ def relative_path(self):
"""
return f"{self.name}/-/{self.name}-{self.version}.tgz"

@staticmethod
def init_from_artifact_and_relative_path(artifact, relative_path):
name, version = extract_package_info(relative_path)

return Package(name=name, version=version)

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
unique_together = ("name", "version", "_pulp_domain")
Expand All @@ -72,6 +61,14 @@ class NpmRemote(Remote):

TYPE = "npm"

def get_remote_artifact_content_type(self, relative_path=None):
name, version = extract_package_info(relative_path)

if name and version:
return Package

return None

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"

Expand Down Expand Up @@ -105,6 +102,9 @@ class Meta:
def content_handler(self, path):
data = {}

if not self.repository:
return None

repository_version = self.repository_version
if not repository_version:
repository_version = self.repository.latest_version()
Expand Down
35 changes: 22 additions & 13 deletions pulp_npm/app/serializers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
from gettext import gettext as _
from rest_framework import serializers

from pulpcore.plugin import models as core_models
from pulpcore.plugin import serializers as platform
from pulpcore.plugin import serializers as core_serializers

from . import models

Expand All @@ -12,7 +13,7 @@
# If you want create content through upload, use "SingleArtifactContentUploadSerializer"
# If you change this, make sure to do so on "fields" below, also.
# Make sure your choice here matches up with the create() method of your viewset.
class PackageSerializer(platform.SingleArtifactContentUploadSerializer):
class PackageSerializer(core_serializers.SingleArtifactContentUploadSerializer):
"""
A Serializer for Package.
Expand All @@ -26,7 +27,7 @@ class PackageSerializer(platform.SingleArtifactContentUploadSerializer):
field3 = serializers.CharField()
class Meta:
fields = platform.SingleArtifactContentSerializer.Meta.fields + (
fields = core_serializers.SingleArtifactContentSerializer.Meta.fields + (
'field1', 'field2', 'field3'
)
model = models.Package
Expand All @@ -37,15 +38,15 @@ class Meta:
relative_path = serializers.CharField()

class Meta:
fields = platform.SingleArtifactContentUploadSerializer.Meta.fields + (
fields = core_serializers.SingleArtifactContentUploadSerializer.Meta.fields + (
"name",
"version",
"relative_path",
)
model = models.Package


class NpmRemoteSerializer(platform.RemoteSerializer):
class NpmRemoteSerializer(core_serializers.RemoteSerializer):
"""
A Serializer for NpmRemote.
Expand All @@ -56,9 +57,9 @@ class NpmRemoteSerializer(platform.RemoteSerializer):
For example::
class Meta:
validators = platform.RemoteSerializer.Meta.validators + [myValidator1, myValidator2]
validators = core_serializers.RemoteSerializer.Meta.validators + [myValidator1, ...]
By default the 'policy' field in platform.RemoteSerializer only validates the choice
By default the 'policy' field in core_serializers.RemoteSerializer only validates the choice
'immediate'. To add on-demand support for more 'policy' options, e.g. 'streamed' or 'on_demand',
re-define the 'policy' option as follows::
Expand All @@ -72,11 +73,11 @@ class Meta:
)

class Meta:
fields = platform.RemoteSerializer.Meta.fields
fields = core_serializers.RemoteSerializer.Meta.fields
model = models.NpmRemote


class NpmRepositorySerializer(platform.RepositorySerializer):
class NpmRepositorySerializer(core_serializers.RepositorySerializer):
"""
A Serializer for NpmRepository.
Expand All @@ -87,19 +88,27 @@ class NpmRepositorySerializer(platform.RepositorySerializer):
For example::
class Meta:
validators = platform.RepositorySerializer.Meta.validators + [myValidator1, myValidator2]
validators = core_serializers.RepositorySerializer.Meta.validators + [myValidator1, ...]
"""

class Meta:
fields = platform.RepositorySerializer.Meta.fields
fields = core_serializers.RepositorySerializer.Meta.fields
model = models.NpmRepository


class NpmDistributionSerializer(platform.DistributionSerializer):
class NpmDistributionSerializer(core_serializers.DistributionSerializer):
"""
Serializer for NPM Distributions.
"""

remote = core_serializers.DetailRelatedField(
required=False,
help_text=_("Remote that can be used to fetch content when using pull-through caching."),
view_name_pattern=r"remotes(-.*/.*)?-detail",
queryset=core_models.Remote.objects.all(),
allow_null=True,
)

class Meta:
fields = platform.DistributionSerializer.Meta.fields
fields = core_serializers.DistributionSerializer.Meta.fields + ("remote",)
model = models.NpmDistribution
22 changes: 22 additions & 0 deletions pulp_npm/app/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import re


def urlpath_sanitize(*args):
"""
Join an arbitrary number of strings into a /-separated path.
Expand All @@ -15,3 +18,22 @@ def urlpath_sanitize(*args):
if stripped:
segments.append(stripped)
return "/".join(segments)


def extract_package_info(relative_path):
"""
Tries to extract the name and the version of a package
from the relative path string.
Args:
The relative_path string. "package/-/package-version.tgz"
"""
pattern = r"^(?P<name>@?[^/]+(?:/[^/]+)?)/-/(?P<base_name>[^/]+)-(?P<version>[\d.]+)\.tgz$"
match = re.match(pattern, relative_path)

if match:
name = match.group("name")
version = match.group("version")
return name, version
else:
return None, None
4 changes: 1 addition & 3 deletions pulp_npm/tests/functional/api/test_download_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@

from pulpcore.client.pulp_npm import RepositorySyncURL

from pulp_npm.tests.functional.constants import (
NPM_FIXTURE_URL,
)
from pulp_npm.tests.functional.constants import NPM_FIXTURE_URL


@pytest.mark.parallel
Expand Down
26 changes: 26 additions & 0 deletions pulp_npm/tests/functional/api/test_pull_through_caching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import json

from pulp_npm.tests.functional.constants import NPM_FIXTURE_URL


def test_pull_through_install(
npm_bindings, npm_remote_factory, npm_distribution_factory, http_get, delete_orphans_pre
):
"""Test that a pull-through distro can be installed from."""
remote = npm_remote_factory(url=NPM_FIXTURE_URL)
distro = npm_distribution_factory(remote=remote.pulp_href)
PACKAGE = "react"

package_metadata = json.loads(http_get(f"{distro.base_url}{PACKAGE}"))
assert package_metadata["name"] == PACKAGE

latest_package_version = package_metadata["dist-tags"]["latest"]
latest_package_metadata = package_metadata["versions"][latest_package_version]
package_filename = latest_package_metadata["dist"]["tarball"].removeprefix(NPM_FIXTURE_URL)

package_download = http_get(f"{distro.base_url}{package_filename}")

assert len(package_download) > 100

content = npm_bindings.ContentPackagesApi.list(name=PACKAGE)
assert content.count == 1

0 comments on commit f65095d

Please sign in to comment.