Skip to content

Commit 5f9264f

Browse files
authored
Merge pull request #69 from rockythorn/feature/config-and-processing-refactor
Feature/config and processing refactor
2 parents c6c9983 + c6ed506 commit 5f9264f

29 files changed

+2262
-377
lines changed

.dockerignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
node_modules
22
.venv
33
.ijwb
4-
.idea
4+
.idea
5+
temp
6+
csaf_analysis
7+
bazel-*
8+
.git
9+
container_data

.github/workflows/test.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,12 @@ jobs:
3535
bazel test //apollo/tests:test_csaf_processing --test_output=all
3636
bazel test //apollo/tests:test_api_keys --test_output=all
3737
bazel test //apollo/tests:test_auth --test_output=all
38+
bazel test //apollo/tests:test_api_updateinfo --test_output=all
3839
bazel test //apollo/tests:test_validation --test_output=all
3940
bazel test //apollo/tests:test_admin_routes_supported_products --test_output=all
41+
bazel test //apollo/tests:test_api_osv --test_output=all
42+
bazel test //apollo/tests:test_database_service --test_output=all
43+
bazel test //apollo/tests:test_rh_matcher_activities --test_output=all
4044
4145
- name: Integration Tests
4246
run: ./build/scripts/test.bash

apollo/db/__init__.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,7 @@ class SupportedProductsRhMirror(Model):
201201
match_major_version = fields.IntField()
202202
match_minor_version = fields.IntField(null=True)
203203
match_arch = fields.CharField(max_length=255)
204+
active = fields.BooleanField(default=True)
204205

205206
rpm_repomds: fields.ReverseRelation["SupportedProductsRpmRepomd"]
206207
rpm_rh_overrides: fields.ReverseRelation["SupportedProductsRpmRhOverride"]
@@ -303,7 +304,7 @@ class AdvisoryPackage(Model):
303304
module_stream = fields.TextField(null=True)
304305
module_version = fields.TextField(null=True)
305306
repo_name = fields.TextField()
306-
package_name = fields.TextField()
307+
_package_name = fields.TextField(source_field='package_name')
307308
product_name = fields.TextField()
308309
supported_products_rh_mirror = fields.ForeignKeyField(
309310
"models.SupportedProductsRhMirror",
@@ -318,6 +319,30 @@ class Meta:
318319
table = "advisory_packages"
319320
unique_together = ("advisory_id", "nevra")
320321

322+
def __init__(self, **kwargs):
323+
if 'package_name' in kwargs:
324+
kwargs['_package_name'] = self._clean_package_name(
325+
kwargs.pop('package_name')
326+
)
327+
super().__init__(**kwargs)
328+
329+
@property
330+
def package_name(self):
331+
return self._clean_package_name(self._package_name)
332+
333+
@package_name.setter
334+
def package_name(self, value):
335+
self._package_name = self._clean_package_name(value)
336+
337+
def _clean_package_name(self, value):
338+
if isinstance(value, str) and value.startswith('module.'):
339+
return value.replace('module.', '')
340+
return value
341+
342+
async def save(self, *args, **kwargs):
343+
self._package_name = self._clean_package_name(self._package_name)
344+
await super().save(*args, **kwargs)
345+
321346

322347
class AdvisoryCVE(Model):
323348
id = fields.BigIntField(pk=True)
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- migrate:up
2+
alter table supported_products_rh_mirrors
3+
add column active boolean not null default true;
4+
5+
create index supported_products_rh_mirrors_active_idx
6+
on supported_products_rh_mirrors(active);
7+
8+
9+
-- migrate:down
10+
drop index if exists supported_products_rh_mirrors_active_idx;
11+
alter table supported_products_rh_mirrors drop column active;

apollo/publishing_tools/apollo_tree.py

Lines changed: 82 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,16 @@
66
import logging
77
import hashlib
88
import gzip
9+
import re
910
from dataclasses import dataclass
1011
import time
1112
from urllib.parse import quote
1213
from xml.etree import ElementTree as ET
1314

1415
import aiohttp
1516

17+
from apollo.server.routes.api_updateinfo import PRODUCT_SLUG_MAP
18+
1619
logging.basicConfig(level=logging.INFO)
1720
logger = logging.getLogger("apollo_tree")
1821

@@ -21,6 +24,31 @@
2124
"rpm": "http://linux.duke.edu/metadata/rpm"
2225
}
2326

27+
PRODUCT_NAME_TO_SLUG = {v: k for k, v in PRODUCT_SLUG_MAP.items()}
28+
API_BASE_URL = "https://apollo.build.resf.org/api/v3/updateinfo"
29+
30+
def get_product_slug(product_name: str) -> str:
31+
"""
32+
Convert product name to API slug for v2 endpoint.
33+
Strips version numbers and architecture placeholders before lookup.
34+
35+
Examples:
36+
"Rocky Linux 9 $arch" -> "Rocky Linux"
37+
"Rocky Linux 10.5" -> "Rocky Linux"
38+
"Rocky Linux SIG Cloud" -> "Rocky Linux SIG Cloud"
39+
"""
40+
clean_name = product_name.replace("$arch", "").strip()
41+
42+
clean_name = re.sub(r'\s+\d+(\.\d+)?$', '', clean_name).strip()
43+
44+
slug = PRODUCT_NAME_TO_SLUG.get(clean_name)
45+
if not slug:
46+
raise ValueError(
47+
f"Unknown product: {clean_name}. "
48+
f"Valid products: {', '.join(PRODUCT_NAME_TO_SLUG.keys())}"
49+
)
50+
return slug
51+
2452

2553
@dataclass
2654
class Repository:
@@ -142,16 +170,37 @@ async def fetch_updateinfo_from_apollo(
142170
repo: dict,
143171
product_name: str,
144172
api_base: str = None,
173+
major_version: int = None,
174+
minor_version: int = None,
145175
) -> str:
146-
pname_arch = product_name.replace("$arch", repo["arch"])
176+
"""
177+
Fetch updateinfo.xml from Apollo API.
178+
179+
Args:
180+
repo: Repository dict with 'name' and 'arch' keys
181+
product_name: Product name
182+
api_base: Optional API base URL override
183+
major_version: Required for api_version=2
184+
minor_version: Optional for api_version=2
185+
"""
147186
if not api_base:
148-
api_base = "https://apollo.build.resf.org/api/v3/updateinfo"
149-
api_url = f"{api_base}/{quote(pname_arch)}/{quote(repo['name'])}/updateinfo.xml"
150-
api_url += f"?req_arch={repo['arch']}"
187+
api_base = API_BASE_URL
188+
189+
if major_version:
190+
product_slug = get_product_slug(product_name)
191+
api_url = f"{api_base}/{product_slug}/{major_version}/{quote(repo['name'])}/updateinfo.xml"
192+
api_params = {'arch': repo['arch']}
193+
if minor_version is not None:
194+
api_params['minor_version'] = minor_version
195+
logger.info("Using v2 endpoint: %s with params %s", api_url, api_params)
196+
else:
197+
pname_arch = product_name.replace("$arch", repo["arch"])
198+
api_url = f"{api_base}/{quote(pname_arch)}/{quote(repo['name'])}/updateinfo.xml"
199+
api_params = {'req_arch': repo['arch']}
200+
logger.info("Using legacy endpoint: %s with params %s", api_url, api_params)
151201

152-
logger.info("Fetching updateinfo from %s", api_url)
153202
async with aiohttp.ClientSession() as session:
154-
async with session.get(api_url) as resp:
203+
async with session.get(api_url, params=api_params) as resp:
155204
if resp.status != 200 and resp.status != 404:
156205
logger.warning(
157206
"Failed to fetch updateinfo from %s, skipping", api_url
@@ -303,6 +352,9 @@ async def run_apollo_tree(
303352
ignore: list[str],
304353
ignore_arch: list[str],
305354
product_name: str,
355+
major_version: int = None,
356+
minor_version: int = None,
357+
api_base: str = None,
306358
):
307359
if manual:
308360
raise Exception("Manual mode not implemented yet")
@@ -320,6 +372,9 @@ async def run_apollo_tree(
320372
updateinfo = await fetch_updateinfo_from_apollo(
321373
repo,
322374
product_name,
375+
api_base=api_base,
376+
major_version=major_version,
377+
minor_version=minor_version,
323378
)
324379
if not updateinfo:
325380
logger.warning("No updateinfo found for %s", repo["name"])
@@ -394,13 +449,30 @@ async def run_apollo_tree(
394449
"-n",
395450
"--product-name",
396451
required=True,
397-
help="Product name",
452+
help="Product name (e.g., 'Rocky Linux', 'Rocky Linux 8 $arch')",
453+
)
454+
parser.add_argument(
455+
"--major-version",
456+
type=int,
457+
help="Major version (required for --api-version 2)",
458+
)
459+
parser.add_argument(
460+
"--minor-version",
461+
type=int,
462+
help="Minor version filter (optional, only with --api-version 2)",
463+
)
464+
parser.add_argument(
465+
"--api-base",
466+
help="API base URL (default: https://apollo.build.resf.org/api/v3/updateinfo)",
398467
)
399468

400469
p_args = parser.parse_args()
401470
if p_args.auto_scan and p_args.manual:
402471
parser.error("Cannot use --auto-scan and --manual together")
403472

473+
if p_args.minor_version and not p_args.major_version:
474+
parser.error("--minor-version can only be used with --major-version")
475+
404476
if p_args.manual and not p_args.repos:
405477
parser.error("Must specify repos to publish in manual mode")
406478

@@ -416,5 +488,8 @@ async def run_apollo_tree(
416488
[y for x in p_args.ignore for y in x],
417489
[y for x in p_args.ignore_arch for y in x],
418490
p_args.product_name,
491+
p_args.major_version,
492+
p_args.minor_version,
493+
p_args.api_base,
419494
)
420495
)

0 commit comments

Comments
 (0)