Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
dd42560
Improve generate_rocky_config.py version matching and naming flexibility
rockythorn Nov 10, 2025
95319c3
Simplify conditional logic in generate_rocky_config.py
rockythorn Nov 12, 2025
cb9aacc
Return all advisories with CVEs
rockythorn Oct 21, 2025
531bbd0
Remove redundant comments from OSV API
rockythorn Nov 12, 2025
f1d918b
Simplify OSV API advisory filtering logic
rockythorn Nov 13, 2025
25f74e0
Fix CSAF parser for modular packages and add EUS filtering
rockythorn Nov 7, 2025
48a57c8
Fix CSV merge to prioritize changes.csv over releases.csv
rockythorn Nov 7, 2025
c555bcf
Add web UI for managing CSAF index timestamp
rockythorn Nov 7, 2025
ced651a
Fix test_csaf_processing to work with refactored CSAF parser and Bazel
rockythorn Nov 10, 2025
d43f40c
Refactor EUS product identifiers into file-level constants
rockythorn Nov 13, 2025
12695ae
Simplify package extraction logic
rockythorn Nov 13, 2025
43ee733
Use Pythonic empty set check
rockythorn Nov 13, 2025
6b997e5
Remove redundant comments from CSAF processing code
rockythorn Nov 13, 2025
c87bb75
Improve exception handling in database_service
rockythorn Nov 13, 2025
db07012
Refactor nested functions to pure functions
rockythorn Nov 13, 2025
0dc061c
Move EUS-only check earlier to avoid unnecessary work
rockythorn Nov 13, 2025
42aea5c
Fix config import validation issues
rockythorn Nov 3, 2025
2e3b51f
Update tests for integer Decimal serialization
rockythorn Nov 6, 2025
6c7eb9e
Remove unnecessary comments
rockythorn Nov 13, 2025
98d9112
Remove redundant comments from validation module
rockythorn Nov 13, 2025
65d11ea
Add active field to mirror configuration
rockythorn Nov 4, 2025
f7e403d
Fix active checkbox not saving when unchecked
rockythorn Nov 4, 2025
e655284
Add mirror sorting and status visualization
rockythorn Nov 4, 2025
bd667c9
Skip inactive mirrors in RHMatcherWorkflow
rockythorn Nov 5, 2025
0d4054a
Fix duplicate loop bug in block_remaining_rh_advisories
rockythorn Nov 5, 2025
af9ee0c
Simplify checkbox handling for mirror active field
rockythorn Nov 6, 2025
a9d1aa3
Add tests for active field checkbox handling
rockythorn Nov 6, 2025
c2d0f59
Remove redundant comments from mirror active field code
rockythorn Nov 13, 2025
cfb5ed9
Extract duplicate repomd form validation into helper function
rockythorn Nov 13, 2025
6a47716
Remove unnecessary comments
rockythorn Nov 13, 2025
f7e6f49
Add v2 updateinfo API endpoint with FK-based filtering
rockythorn Nov 14, 2025
eafe874
Add ORM property to strip module prefix from package_name
rockythorn Nov 15, 2025
2222db3
Simplify updateinfo business logic using ORM package_name property
rockythorn Nov 15, 2025
f550104
Use centralized Architecture enum for v2 endpoint validation
rockythorn Nov 15, 2025
23fbc1e
Fix package_name NULL constraint violation in AdvisoryPackage ORM
rockythorn Nov 15, 2025
e1a1e50
Add v2 updateinfo API support to apollo_tree.py with backwards compat…
rockythorn Nov 17, 2025
c6ed506
Refactor apollo_tree.py to use aiohttp params and simplify API detection
rockythorn Nov 17, 2025
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
7 changes: 6 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
node_modules
.venv
.ijwb
.idea
.idea
temp
csaf_analysis
bazel-*
.git
container_data
4 changes: 4 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ jobs:
bazel test //apollo/tests:test_csaf_processing --test_output=all
bazel test //apollo/tests:test_api_keys --test_output=all
bazel test //apollo/tests:test_auth --test_output=all
bazel test //apollo/tests:test_api_updateinfo --test_output=all
bazel test //apollo/tests:test_validation --test_output=all
bazel test //apollo/tests:test_admin_routes_supported_products --test_output=all
bazel test //apollo/tests:test_api_osv --test_output=all
bazel test //apollo/tests:test_database_service --test_output=all
bazel test //apollo/tests:test_rh_matcher_activities --test_output=all

- name: Integration Tests
run: ./build/scripts/test.bash
27 changes: 26 additions & 1 deletion apollo/db/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ class SupportedProductsRhMirror(Model):
match_major_version = fields.IntField()
match_minor_version = fields.IntField(null=True)
match_arch = fields.CharField(max_length=255)
active = fields.BooleanField(default=True)

rpm_repomds: fields.ReverseRelation["SupportedProductsRpmRepomd"]
rpm_rh_overrides: fields.ReverseRelation["SupportedProductsRpmRhOverride"]
Expand Down Expand Up @@ -303,7 +304,7 @@ class AdvisoryPackage(Model):
module_stream = fields.TextField(null=True)
module_version = fields.TextField(null=True)
repo_name = fields.TextField()
package_name = fields.TextField()
_package_name = fields.TextField(source_field='package_name')
product_name = fields.TextField()
supported_products_rh_mirror = fields.ForeignKeyField(
"models.SupportedProductsRhMirror",
Expand All @@ -318,6 +319,30 @@ class Meta:
table = "advisory_packages"
unique_together = ("advisory_id", "nevra")

def __init__(self, **kwargs):
if 'package_name' in kwargs:
kwargs['_package_name'] = self._clean_package_name(
kwargs.pop('package_name')
)
super().__init__(**kwargs)

@property
def package_name(self):
return self._clean_package_name(self._package_name)

@package_name.setter
def package_name(self, value):
self._package_name = self._clean_package_name(value)

def _clean_package_name(self, value):
if isinstance(value, str) and value.startswith('module.'):
return value.replace('module.', '')
return value

async def save(self, *args, **kwargs):
self._package_name = self._clean_package_name(self._package_name)
await super().save(*args, **kwargs)


class AdvisoryCVE(Model):
id = fields.BigIntField(pk=True)
Expand Down
11 changes: 11 additions & 0 deletions apollo/migrations/20251104111759_add_mirror_active_field.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
-- migrate:up
alter table supported_products_rh_mirrors
add column active boolean not null default true;

create index supported_products_rh_mirrors_active_idx
on supported_products_rh_mirrors(active);


-- migrate:down
drop index if exists supported_products_rh_mirrors_active_idx;
alter table supported_products_rh_mirrors drop column active;
89 changes: 82 additions & 7 deletions apollo/publishing_tools/apollo_tree.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import logging
import hashlib
import gzip
import re
from dataclasses import dataclass
import time
from urllib.parse import quote
from xml.etree import ElementTree as ET

import aiohttp

from apollo.server.routes.api_updateinfo import PRODUCT_SLUG_MAP

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("apollo_tree")

Expand All @@ -21,6 +24,31 @@
"rpm": "http://linux.duke.edu/metadata/rpm"
}

PRODUCT_NAME_TO_SLUG = {v: k for k, v in PRODUCT_SLUG_MAP.items()}
API_BASE_URL = "https://apollo.build.resf.org/api/v3/updateinfo"

def get_product_slug(product_name: str) -> str:
"""
Convert product name to API slug for v2 endpoint.
Strips version numbers and architecture placeholders before lookup.

Examples:
"Rocky Linux 9 $arch" -> "Rocky Linux"
"Rocky Linux 10.5" -> "Rocky Linux"
"Rocky Linux SIG Cloud" -> "Rocky Linux SIG Cloud"
"""
clean_name = product_name.replace("$arch", "").strip()

clean_name = re.sub(r'\s+\d+(\.\d+)?$', '', clean_name).strip()

slug = PRODUCT_NAME_TO_SLUG.get(clean_name)
if not slug:
raise ValueError(
f"Unknown product: {clean_name}. "
f"Valid products: {', '.join(PRODUCT_NAME_TO_SLUG.keys())}"
)
return slug


@dataclass
class Repository:
Expand Down Expand Up @@ -142,16 +170,37 @@ async def fetch_updateinfo_from_apollo(
repo: dict,
product_name: str,
api_base: str = None,
major_version: int = None,
minor_version: int = None,
) -> str:
pname_arch = product_name.replace("$arch", repo["arch"])
"""
Fetch updateinfo.xml from Apollo API.

Args:
repo: Repository dict with 'name' and 'arch' keys
product_name: Product name
api_base: Optional API base URL override
major_version: Required for api_version=2
minor_version: Optional for api_version=2
"""
if not api_base:
api_base = "https://apollo.build.resf.org/api/v3/updateinfo"
api_url = f"{api_base}/{quote(pname_arch)}/{quote(repo['name'])}/updateinfo.xml"
api_url += f"?req_arch={repo['arch']}"
api_base = API_BASE_URL

if major_version:
product_slug = get_product_slug(product_name)
api_url = f"{api_base}/{product_slug}/{major_version}/{quote(repo['name'])}/updateinfo.xml"
api_params = {'arch': repo['arch']}
if minor_version is not None:
api_params['minor_version'] = minor_version
logger.info("Using v2 endpoint: %s with params %s", api_url, api_params)
else:
pname_arch = product_name.replace("$arch", repo["arch"])
api_url = f"{api_base}/{quote(pname_arch)}/{quote(repo['name'])}/updateinfo.xml"
api_params = {'req_arch': repo['arch']}
logger.info("Using legacy endpoint: %s with params %s", api_url, api_params)

logger.info("Fetching updateinfo from %s", api_url)
async with aiohttp.ClientSession() as session:
async with session.get(api_url) as resp:
async with session.get(api_url, params=api_params) as resp:
if resp.status != 200 and resp.status != 404:
logger.warning(
"Failed to fetch updateinfo from %s, skipping", api_url
Expand Down Expand Up @@ -303,6 +352,9 @@ async def run_apollo_tree(
ignore: list[str],
ignore_arch: list[str],
product_name: str,
major_version: int = None,
minor_version: int = None,
api_base: str = None,
):
if manual:
raise Exception("Manual mode not implemented yet")
Expand All @@ -320,6 +372,9 @@ async def run_apollo_tree(
updateinfo = await fetch_updateinfo_from_apollo(
repo,
product_name,
api_base=api_base,
major_version=major_version,
minor_version=minor_version,
)
if not updateinfo:
logger.warning("No updateinfo found for %s", repo["name"])
Expand Down Expand Up @@ -394,13 +449,30 @@ async def run_apollo_tree(
"-n",
"--product-name",
required=True,
help="Product name",
help="Product name (e.g., 'Rocky Linux', 'Rocky Linux 8 $arch')",
)
parser.add_argument(
"--major-version",
type=int,
help="Major version (required for --api-version 2)",
)
parser.add_argument(
"--minor-version",
type=int,
help="Minor version filter (optional, only with --api-version 2)",
)
parser.add_argument(
"--api-base",
help="API base URL (default: https://apollo.build.resf.org/api/v3/updateinfo)",
)

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

if p_args.minor_version and not p_args.major_version:
parser.error("--minor-version can only be used with --major-version")

if p_args.manual and not p_args.repos:
parser.error("Must specify repos to publish in manual mode")

Expand All @@ -416,5 +488,8 @@ async def run_apollo_tree(
[y for x in p_args.ignore for y in x],
[y for x in p_args.ignore_arch for y in x],
p_args.product_name,
p_args.major_version,
p_args.minor_version,
p_args.api_base,
)
)
Loading
Loading