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
8 changes: 8 additions & 0 deletions backend/conf/copr-be.conf.example
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,14 @@ prune_days=14
# some CDN caches (e.g. when RPMs in repository are re-signed).
#aws_cloudfront_distribution=EX55ITR8LVMOH

# Pulp content guard (pulp_href) assigned to every distribution that copr
# creates in Pulp. Typically a composite content guard combining the feature
# guards (e.g. RHEL-OS-x86_64, RHEL-OS-aarch64). When unset, distributions are
# created without a content guard. The guard itself must be created in the Pulp
# domain beforehand (feature and composite guards are not available in the Pulp
# CLI, only via the REST API).
#pulp_content_guard=/api/pulp/<domain>/api/v3/contentguards/core/composite/<uuid>/

# the domain name of the auto-generated sign key
# e.g. format: user#projectname@copr.{sign_domain}
#sign_domain=fedorahosted.org
Expand Down
3 changes: 3 additions & 0 deletions backend/copr_backend/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,9 @@ def _read_unsafe(self): # pylint: disable=too-many-statements
opts.pulp_content_url = _get_conf(
cp, "backend", "pulp_content_url", None)

opts.pulp_content_guard = _get_conf(
cp, "backend", "pulp_content_guard", None)

# ssh options
opts.ssh = Munch()
opts.ssh.builder_config = _get_conf(
Expand Down
11 changes: 9 additions & 2 deletions backend/copr_backend/pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,7 +454,8 @@ def get_package_by_prn(self, prn):
self.log.info("Pulp: get_by_prn: %s", uri)
return self.send("GET", uri)

def create_distribution(self, name, repository, basepath=None):
def create_distribution(self, name, repository, basepath=None,
content_guard=None):
"""
Create an RPM distribution
https://docs.pulpproject.org/pulp_rpm/restapi.html#tag/Distributions:-Rpm/operation/distributions_rpm_rpm_create
Expand All @@ -465,18 +466,22 @@ def create_distribution(self, name, repository, basepath=None):
"repository": repository,
"base_path": basepath or name,
}
if content_guard:
data["content_guard"] = content_guard
return PulpRequest("POST", uri, data,
f"create distribution {name}")

def update_distribution(self, distribution, publication=None,
repository=None):
repository=None, content_guard=None):
"""
Build a PulpRequest to update an RPM distribution.
https://pulpproject.org/pulp_rpm/restapi/#tag/Distributions:-Rpm/operation/distributions_rpm_rpm_update

This allows us to point a distribution to either a publication or
a repository. Not both, that doesn't make sense and Pulp would raise
"Only one of the attributes 'repository' and 'publication' may be used simultaneously."

A content guard can be assigned to the distribution at the same time.
"""
if publication and repository:
raise RuntimeError("Specify either publication or repository")
Expand All @@ -486,6 +491,8 @@ def update_distribution(self, distribution, publication=None,
"publication": publication,
"repository": repository,
}
if content_guard is not None:
data["content_guard"] = content_guard
return PulpRequest("PATCH", url, data,
f"update distribution {distribution}")

Expand Down
3 changes: 2 additions & 1 deletion backend/copr_backend/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ def init_project(self, dirname, chroot, reason=None):
try:
self.client.deliver_and_wait([
self.client.create_distribution(
distribution_name, repository_href),
distribution_name, repository_href,
content_guard=self.opts.pulp_content_guard),
])
except RequestError as ex:
if "This field must be unique" not in ex.response.text:
Expand Down
98 changes: 98 additions & 0 deletions backend/run/copr-pulp-set-content-guard
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/python3

"""
Assign a Pulp content guard to all distributions belonging to a given project.

Copr assigns the content guard from the `pulp_content_guard` configuration
option to distributions at the time they are created. This script is useful for
(re)setting the content guard on distributions of an already existing project,
e.g. after the configuration option is introduced or changed.

Example:

copr-pulp-set-content-guard @copr/copr \\
--content-guard /api/pulp/<domain>/api/v3/contentguards/core/composite/<uuid>/

When --content-guard is not specified, the value of the `pulp_content_guard`
option from the backend configuration is used.
"""

import argparse
import sys

from copr_backend.helpers import BackendConfigReader
from copr_backend.pulp import PulpClient


def get_arg_parser():
"""
CLI argument parser
"""
parser = argparse.ArgumentParser(
description="Assign a Pulp content guard to all distributions of a "
"given <owner>/<project>")
parser.add_argument(
"project",
help="Project in the <owner>/<project> format")
parser.add_argument(
"--content-guard",
help="The pulp_href of the content guard to assign. When not "
"specified, the pulp_content_guard backend configuration option "
"is used.")
return parser


def main():
"""
The main function
"""
parser = get_arg_parser()
args = parser.parse_args()

content_guard = args.content_guard
if not content_guard:
opts = BackendConfigReader().read()
content_guard = opts.pulp_content_guard
if not content_guard:
print("Error: No content guard specified and pulp_content_guard is "
"not configured.")
sys.exit(1)

client = PulpClient.create_from_config_file()

# Distributions are named <owner>/<project>/<chroot> (with an optional
# -devel suffix), so all distributions of a project share this prefix.
parts = args.project.rstrip("/").split("/")
if len(parts) != 2 or not all(parts):
parser.error("project must use the <owner>/<project> format")
prefix = f"{parts[0]}/{parts[1]}/"

response = client.list_distributions(prefix)
response.raise_for_status()
response_data = response.json()
if response_data["next"] is not None:
raise RuntimeError("More than one distribution page exists; "
"refusing a partial content-guard update")
distributions = response_data["results"]

if not distributions:
print(f"No distributions found for {args.project}")
return

requests = []
for distribution in distributions:
print(f"Setting content guard on {distribution['name']}")
requests.append(client.update_distribution(
distribution["pulp_href"],
content_guard=content_guard,
# Keep the old values (we use PATCH, None cleans the config).
publication=distribution["publication"],
repository=distribution["repository"],
))

client.deliver_and_wait(requests)
print(f"Done, updated {len(requests)} distribution(s)")


if __name__ == "__main__":
main()
31 changes: 31 additions & 0 deletions backend/tests/test_pulp.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,37 @@ def test_get_content_build_ids_empty(self):
assert "Content must be queried for specific builds" in str(ex)
assert not client.send.called

def test_distribution_without_content_guard(self):
client = PulpClient(self.config)
request = client.create_distribution("foo", "/repo/1/")
assert request.data == {
"name": "foo",
"repository": "/repo/1/",
"base_path": "foo",
}

def test_create_distribution_with_content_guard(self):
client = PulpClient(self.config)
request = client.create_distribution(
"foo", "/repo/1/", content_guard="/guard/1/")
assert request.data == {
"name": "foo",
"repository": "/repo/1/",
"base_path": "foo",
"content_guard": "/guard/1/",
}

def test_update_distribution_no_guard(self):
client = PulpClient(self.config)
request = client.update_distribution("/dist/1/", repository="/repo/1/")
assert "content_guard" not in request.data

def test_update_distribution_with_content_guard(self):
client = PulpClient(self.config)
request = client.update_distribution(
"/dist/1/", content_guard="/guard/1/")
assert request.data["content_guard"] == "/guard/1/"


class TestDeliverRequests:

Expand Down