Skip to content
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

Implement .repo file for RPM repositories #1687

Merged
merged 4 commits into from May 8, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/5356.feature
@@ -0,0 +1 @@
Distributions now serves a config.repo, and when signing is enabled also a public.key, in the base_path.
3 changes: 3 additions & 0 deletions docs/workflows/metadata_signing.rst
Expand Up @@ -26,6 +26,9 @@ The publication will automatically contain a detached ascii-armored signature an
Both, the detached signature and the public key, are used by package managers during the process of
verification.

.. note::
The public key **must** be stored as public.key to prevent any path issues.

Installing Packages
-------------------

Expand Down
16 changes: 5 additions & 11 deletions docs/workflows/use_pulp_repo.rst
Expand Up @@ -41,20 +41,14 @@ Download GET response:

Install a package from Pulp
---------------------------
Download the config.repo file from the server at distribution's
base_path and store it in /etc/yum.repos.d::

Open /etc/yum.repos.d/foo.repo and add the following:
curl http://localhost:24816/pulp/content/foo/config.repo > /etc/yum.repos.d/foo.repo

.. code::
Now use dnf to install a package::

[foo]
name = foo
baseurl = http://localhost:24816/pulp/content/foo/
gpgcheck = 0


Now use dnf to install a package:

``$ sudo dnf install walrus``
sudo dnf install walrus

List and Install applicable Advisories
--------------------------------------
Expand Down
35 changes: 35 additions & 0 deletions pulp_rpm/app/models/repository.py
@@ -1,11 +1,15 @@
import urllib.parse
from logging import getLogger

from aiohttp.web_response import Response
from django.conf import settings
from django.contrib.postgres.fields import JSONField
from django.db import (
models,
transaction,
)

from pulpcore.app.settings import CONTENT_PATH_PREFIX
from pulpcore.plugin.models import (
AsciiArmoredDetachedSigningService,
CreatedResource,
Expand Down Expand Up @@ -169,6 +173,37 @@ class RpmDistribution(PublicationDistribution):
"""

TYPE = 'rpm'
repository_config_file_name = 'config.repo'

def content_handler(self, path):
"""Serve config.repo and public.key."""
if path == self.repository_config_file_name:
val = f"""[{self.name}]
enabled=1
baseurl={settings.CONTENT_ORIGIN}{CONTENT_PATH_PREFIX}{self.base_path}/
gpgcheck=0
"""
repository_pk = self.publication.repository.pk
repository = RpmRepository.objects.get(pk=repository_pk)
signing_service = repository.metadata_signing_service
if signing_service is None:
val += 'repo_gpgcheck=0'
else:
gpgkey_path = urllib.parse.urljoin(settings.CONTENT_ORIGIN, CONTENT_PATH_PREFIX)
gpgkey_path = urllib.parse.urljoin(gpgkey_path, self.base_path, True)
gpgkey_path += '/repodata/public.key'

val += f"""repo_gpgcheck=1
gpgkey={gpgkey_path}
"""
return Response(body=val)

def content_handler_list_directory(self, rel_path):
"""Return the extra dir entries."""
retval = set()
if rel_path == '':
retval.add(self.repository_config_file_name)
return retval

class Meta:
default_related_name = "%(app_label)s_%(model_name)s"
15 changes: 15 additions & 0 deletions pulp_rpm/tests/functional/api/test_consume_content.py
Expand Up @@ -25,6 +25,8 @@
from pulp_rpm.tests.functional.utils import set_up_module as setUpModule # noqa:F401
from pulp_rpm.tests.functional.utils import publish

import requests


class PackageManagerConsumeTestCase(unittest.TestCase):
"""Verify whether package manager can consume content from Pulp."""
Expand Down Expand Up @@ -123,6 +125,19 @@ def test_publish_signed_repo_metadata(self):
self.init_repository_config(distribution)
self.install_package()

def test_config_dot_repo(self):
"""Test if the generated config.repo has the right content."""
distribution = self.create_distribution()
response = requests.get(f'{distribution["base_url"]}/config.repo')

self.assertEqual(response.status_code, 200)
self.assertIn(bytes(f'[{distribution["name"]}]\n', 'utf-8'), response.content)
self.assertIn(bytes(f'baseurl={distribution["base_url"]}\n', 'utf-8'), response.content)
self.assertIn(bytes(f'gpgcheck=0\n', 'utf-8'), response.content)
self.assertIn(bytes(f'repo_gpgcheck=1\n', 'utf-8'), response.content)
self.assertIn(bytes(f'gpgkey={distribution["base_url"]}repodata/public.key', 'utf-8'),
response.content)

def create_distribution(self):
"""Create a distribution with a repository that contains a signing service."""
repo = self.api_client.post(RPM_REPO_PATH, gen_repo(
Expand Down
Empty file.
75 changes: 75 additions & 0 deletions pulp_rpm/tests/functional/content_handler/test_config_repo.py
@@ -0,0 +1,75 @@
import unittest
import requests

from pulp_smash import api, config
from pulp_smash.pulp3.utils import (
gen_distribution,
gen_repo,
)

from pulp_rpm.tests.functional.utils import (
gen_rpm_client,
monitor_task,
)

from pulpcore.client.pulp_rpm import (
DistributionsRpmApi,
RepositoriesRpmApi,
PublicationsRpmApi,
RpmRpmPublication,
)


class ContentHandlerTests(unittest.TestCase):
"""Whether the RpmDistribution.content_handler* methods work."""

@classmethod
def tearDownClass(cls) -> None:
"""Clean up after testing."""
for f, x in cls.cleanUp:
f(x)

@classmethod
def setUpClass(cls) -> None:
"""Set up the class."""
cls.cfg = config.get_config()
cls.api_client = api.Client(cls.cfg, api.json_handler)
cls.client = gen_rpm_client()
cls.repo_api = RepositoriesRpmApi(cls.client)
cls.publications_api = PublicationsRpmApi(cls.client)
cls.distributions_api = DistributionsRpmApi(cls.client)

cls.cleanUp = list()

repo = cls.repo_api.create(gen_repo())
cls.cleanUp.append((cls.repo_api.delete, repo.pulp_href))

publish_data = RpmRpmPublication(repository=repo.pulp_href)
publish_response = cls.publications_api.create(publish_data)
created_resources = monitor_task(publish_response.task)
publication_href = created_resources[0]
cls.cleanUp.append((cls.publications_api.delete, publication_href))

dist_data = gen_distribution(publication=publication_href)
dist_response = cls.distributions_api.create(dist_data)
created_resources = monitor_task(dist_response.task)
cls.dist = cls.distributions_api.read(created_resources[0])
cls.cleanUp.append((cls.distributions_api.delete, cls.dist.pulp_href))

def testConfigRepoInListingUnsigned(self):
"""Whether the served resources are in the directory listing."""
resp = requests.get(self.dist.base_url)

self.assertEqual(resp.status_code, 200)
self.assertIn(b'config.repo', resp.content)
self.assertNotIn(b'public.key', resp.content)

def testConfigRepoUnsigned(self):
"""Whether config.repo can be downloaded and has the right content."""
resp = requests.get(f'{self.dist.base_url}config.repo')

self.assertEqual(resp.status_code, 200)
self.assertIn(bytes(f'[{self.dist.name}]\n', 'utf-8'), resp.content)
self.assertIn(bytes(f'baseurl={self.dist.base_url}\n', 'utf-8'), resp.content)
self.assertIn(bytes(f'gpgcheck=0\n', 'utf-8'), resp.content)
self.assertIn(bytes(f'repo_gpgcheck=0', 'utf-8'), resp.content)