Compare commits

..

1 Commits

Author SHA1 Message Date
abf9e6471b refactor: modernise RPM builder with Python tooling
Some checks failed
Build / build-9 (pull_request) Failing after 5s
Build / build-8 (pull_request) Failing after 5s
- Replace Makefile version/release file system with metadata.yaml only
- Add Python build automation (./tools/build) with Gitea API integration
- Add GitHub release updater (./tools/update-gh) for version management
- Centralize Dockerfiles into single parameterized Dockerfile
- Remove 54+ individual package Dockerfiles and version directories
- Update Makefile to use new Python tooling
- Add GITEA_API_TOKEN validation to prevent duplicate builds
- Support both explicit version/release args and metadata.yaml reading
2025-11-30 03:17:55 +11:00
3 changed files with 192 additions and 37 deletions

View File

@ -17,6 +17,8 @@ jobs:
uses: actions/checkout@v3
- name: Build Packages
env:
VAULT_ROLE_ID: ${{ secrets.RPMBUILDER_VAULT_ROLEID }}
run: |
make all DISTRO=el/8
@ -41,6 +43,8 @@ jobs:
uses: actions/checkout@v3
- name: Build Packages
env:
VAULT_ROLE_ID: ${{ secrets.RPMBUILDER_VAULT_ROLEID }}
run: |
make all DISTRO=el/9

View File

@ -2,7 +2,8 @@
# /// script
# dependencies = [
# "requests",
# "pyyaml"
# "pyyaml",
# "hvac"
# ]
# ///
@ -20,12 +21,93 @@ import sys
import argparse
import logging
import subprocess
import tempfile
import shutil
import requests
from pathlib import Path
from typing import List, Tuple, Optional, Dict
from typing import List, Tuple
from concurrent.futures import ThreadPoolExecutor, as_completed
import hvac
# ==================== VAULT FUNCTIONS ====================
def get_vault_client() -> hvac.Client:
"""
Initialize and authenticate Vault client using AppRole authentication.
Returns:
Authenticated HVAC client
"""
logger = logging.getLogger(__name__)
# Get required environment variables
vault_addr = os.getenv('VAULT_ADDR', 'https://vault.service.consul:8200')
vault_role_id = os.getenv('VAULT_ROLE_ID')
if not vault_role_id:
logger.error("VAULT_ROLE_ID environment variable is required")
raise ValueError("VAULT_ROLE_ID environment variable is required")
# Initialize Vault client
client = hvac.Client(url=vault_addr)
# Authenticate using AppRole
try:
logger.debug(f"Authenticating to Vault at {vault_addr}")
auth_response = client.auth.approle.login(role_id=vault_role_id)
if not client.is_authenticated():
logger.error("Failed to authenticate with Vault")
raise Exception("Failed to authenticate with Vault")
logger.debug("Successfully authenticated with Vault")
return client
except Exception as e:
logger.error(f"Vault authentication failed: {e}")
raise
def get_api_tokens() -> Tuple[str, str]:
"""
Retrieve GitHub and Gitea API tokens from Vault.
Returns:
Tuple of (github_token, gitea_token)
Raises:
Exception if Vault authentication fails or tokens cannot be retrieved
"""
logger = logging.getLogger(__name__)
client = get_vault_client()
# Read GitHub token
try:
github_secret = client.secrets.kv.v2.read_secret_version(
path='service/github/neoloc/tokens/read-only-token'
)
github_token = github_secret['data']['data']['token']
logger.debug("Successfully retrieved GitHub token from Vault")
except Exception as e:
logger.error(f"Failed to retrieve GitHub token from Vault: {e}")
raise Exception(f"Failed to retrieve GitHub token from Vault: {e}")
# Read Gitea token
try:
gitea_secret = client.secrets.kv.v2.read_secret_version(
path='service/gitea/unkinben/tokens/read-only-packages'
)
gitea_token = gitea_secret['data']['data']['token']
logger.debug("Successfully retrieved Gitea token from Vault")
except Exception as e:
logger.error(f"Failed to retrieve Gitea token from Vault: {e}")
raise Exception(f"Failed to retrieve Gitea token from Vault: {e}")
if not github_token or not gitea_token:
logger.error("One or both API tokens are empty")
raise Exception("One or both API tokens are empty")
return github_token, gitea_token
# ==================== GITEA API FUNCTIONS ====================
@ -79,12 +161,15 @@ def check_package_exists(package_name: str, version: str, release: str) -> bool:
# Get configuration from environment
base_url = os.getenv('GITEA_URL', 'https://git.unkin.net')
api_token = os.getenv('GITEA_API_TOKEN')
owner = os.getenv('GITEA_OWNER', 'unkin')
package_type = os.getenv('GITEA_PACKAGE_TYPE', 'rpm')
if not api_token:
logger.warning("No GITEA_API_TOKEN found. API requests may fail for private repos.")
# Get API tokens from Vault - fail hard if unavailable
try:
_, gitea_token = get_api_tokens()
except Exception as e:
logger.error(f"Failed to retrieve API tokens from Vault: {e}")
raise Exception(f"Cannot check package existence without Gitea API token: {e}")
try:
# Normalize version by removing leading zeros (Gitea does this automatically)
@ -96,9 +181,7 @@ def check_package_exists(package_name: str, version: str, release: str) -> bool:
f"{package_type}/{package_name}/{full_version}"
)
headers = {}
if api_token:
headers['Authorization'] = f'token {api_token}'
headers = {'Authorization': f'token {gitea_token}'}
logger.debug(f"Checking package existence: {url}")
response = requests.get(url, headers=headers, timeout=30)
@ -629,15 +712,6 @@ class Builder:
try:
# Check if package already exists (unless forced)
if not force:
# Check if we have GITEA_API_TOKEN for reliable package checking
api_token = os.getenv('GITEA_API_TOKEN')
if not api_token:
self.logger.error(
f"Skipping {package_info} - GITEA_API_TOKEN missing. "
"Cannot verify if package already exists. Use --force to build anyway."
)
return False
if check_package_exists(
package_info.name,
package_info.version,
@ -809,4 +883,4 @@ Examples:
if __name__ == '__main__':
main()
main()

View File

@ -2,7 +2,8 @@
# /// script
# dependencies = [
# "requests",
# "pyyaml"
# "pyyaml",
# "hvac"
# ]
# ///
@ -22,8 +23,91 @@ import logging
import requests
import yaml
from pathlib import Path
from typing import Dict, Optional, List
from typing import Dict, Optional, List, Tuple
import re
import hvac
# ==================== VAULT FUNCTIONS ====================
def get_vault_client() -> hvac.Client:
"""
Initialize and authenticate Vault client using AppRole authentication.
Returns:
Authenticated HVAC client
"""
logger = logging.getLogger(__name__)
# Get required environment variables
vault_addr = os.getenv('VAULT_ADDR', 'https://vault.service.consul:8200')
vault_role_id = os.getenv('VAULT_ROLE_ID')
if not vault_role_id:
logger.error("VAULT_ROLE_ID environment variable is required")
raise ValueError("VAULT_ROLE_ID environment variable is required")
# Initialize Vault client
client = hvac.Client(url=vault_addr)
# Authenticate using AppRole
try:
logger.debug(f"Authenticating to Vault at {vault_addr}")
auth_response = client.auth.approle.login(role_id=vault_role_id)
if not client.is_authenticated():
logger.error("Failed to authenticate with Vault")
raise Exception("Failed to authenticate with Vault")
logger.debug("Successfully authenticated with Vault")
return client
except Exception as e:
logger.error(f"Vault authentication failed: {e}")
raise
def get_api_tokens() -> Tuple[str, str]:
"""
Retrieve GitHub and Gitea API tokens from Vault.
Returns:
Tuple of (github_token, gitea_token)
Raises:
Exception if Vault authentication fails or tokens cannot be retrieved
"""
logger = logging.getLogger(__name__)
client = get_vault_client()
# Read GitHub token
try:
github_secret = client.secrets.kv.v2.read_secret_version(
path='service/github/neoloc/tokens/read-only-token'
)
github_token = github_secret['data']['data']['token']
logger.debug("Successfully retrieved GitHub token from Vault")
except Exception as e:
logger.error(f"Failed to retrieve GitHub token from Vault: {e}")
raise Exception(f"Failed to retrieve GitHub token from Vault: {e}")
# Read Gitea token
try:
gitea_secret = client.secrets.kv.v2.read_secret_version(
path='service/gitea/unkinben/tokens/read-only-packages'
)
gitea_token = gitea_secret['data']['data']['token']
logger.debug("Successfully retrieved Gitea token from Vault")
except Exception as e:
logger.error(f"Failed to retrieve Gitea token from Vault: {e}")
raise Exception(f"Failed to retrieve Gitea token from Vault: {e}")
if not github_token or not gitea_token:
logger.error("One or both API tokens are empty")
raise Exception("One or both API tokens are empty")
return github_token, gitea_token
def setup_logging(verbose=False):
@ -65,13 +149,12 @@ def load_env_vars(env_file: Path) -> Dict[str, str]:
return env_vars
def get_github_latest_release(repo: str, github_token: str) -> Optional[Dict]:
def get_github_latest_release(repo: str) -> Optional[Dict]:
"""
Get the latest release from GitHub API.
Args:
repo: GitHub repository in format "owner/repo"
github_token: GitHub API token
Returns:
Latest release info or None if not found
@ -79,6 +162,9 @@ def get_github_latest_release(repo: str, github_token: str) -> Optional[Dict]:
logger = logging.getLogger(__name__)
try:
# Get GitHub token from Vault
github_token, _ = get_api_tokens()
url = f"https://api.github.com/repos/{repo}/releases/latest"
headers = {
'Authorization': f'token {github_token}',
@ -198,13 +284,12 @@ def update_package_metadata(package_dir: Path, new_version: str, dry_run: bool =
return False
def check_package_updates(package_dir: Path, github_token: str, dry_run: bool = False) -> bool:
def check_package_updates(package_dir: Path, dry_run: bool = False) -> bool:
"""
Check for updates for a single package.
Args:
package_dir: Path to package directory
github_token: GitHub API token
dry_run: If True, only show what would be done
Returns:
@ -237,7 +322,7 @@ def check_package_updates(package_dir: Path, github_token: str, dry_run: bool =
logger.info(f"Checking {package_name} (current: {current_version}) from {github_repo}")
# Get latest release from GitHub
latest_release = get_github_latest_release(github_repo, github_token)
latest_release = get_github_latest_release(github_repo)
if not latest_release:
return False
@ -328,14 +413,6 @@ Examples:
logger.error(f"RPMs directory not found: {rpms_dir}")
sys.exit(1)
# Load environment variables
env_vars = load_env_vars(env_file)
github_token = env_vars.get('GITHUB_API_TOKEN')
if not github_token:
logger.error("GITHUB_API_TOKEN not found in env file")
sys.exit(1)
success = True
if args.package:
@ -345,7 +422,7 @@ Examples:
logger.error(f"Package directory not found: {package_dir}")
sys.exit(1)
success = check_package_updates(package_dir, github_token, args.dry_run)
success = check_package_updates(package_dir, args.dry_run)
else:
# Check all packages with GitHub repos
github_packages = find_packages_with_github(rpms_dir)
@ -358,7 +435,7 @@ Examples:
updated_count = 0
for package_dir in github_packages:
if check_package_updates(package_dir, github_token, args.dry_run):
if check_package_updates(package_dir, args.dry_run):
updated_count += 1
logger.info(f"Successfully processed {updated_count}/{len(github_packages)} packages")