1 Commits

Author SHA1 Message Date
unkinben 0751a721f3 feat: migrate to woodpeckerci
Build / build-8 (pull_request) Successful in 16s
Build / build-9 (pull_request) Successful in 18s
ci/woodpecker/pr/pre-commit Pipeline was successful
ci/woodpecker/pr/build-almalinux9 Pipeline failed
ci/woodpecker/pr/build-almalinux8 Pipeline failed
- update build tool for kubernetes auth
- update build tool to build packages without docker (native + buildah)
- add woodpecker pre-commit and build jobs
2026-03-07 15:01:57 +11:00
4 changed files with 215 additions and 19 deletions
+3 -2
View File
@@ -3,10 +3,11 @@ when:
steps:
- name: build rpms
image: git.unkin.net/unkin/almalinux8-rpmbuilder:latest
image: git.unkin.net/unkin/almalinux9-rpmbuilder:latest
commands:
- mkdir -p /app/dist
- ./tools/build build-all --distro almalinux/el8 --native
- dnf install buildah -y
- ./tools/build build-all --distro almalinux/el8 --buildah
backend_options:
kubernetes:
serviceAccountName: default
+2 -1
View File
@@ -6,7 +6,8 @@ steps:
image: git.unkin.net/unkin/almalinux9-rpmbuilder:latest
commands:
- mkdir -p /app/dist/
- ./tools/build build-all --distro almalinux/el9 --native
- dnf install buildah -y
- ./tools/build build-all --distro almalinux/el9 --buildah
backend_options:
kubernetes:
serviceAccountName: default
+5
View File
@@ -31,6 +31,11 @@ build-all-native:
@echo "Building all packages natively (no Docker) for distro $(DISTRO)..."
$(BUILD_TOOL) build-all --distro $(DISTRO) --native
# Build all packages using Buildah
build-all-buildah:
@echo "Building all packages using Buildah for distro $(DISTRO)..."
$(BUILD_TOOL) build-all --distro $(DISTRO) --buildah
# Build specific package using Python tool
.PHONY: $(PACKAGES)
$(PACKAGES):
+205 -16
View File
@@ -626,6 +626,25 @@ def check_native_build_deps() -> bool:
return True
def check_buildah_available() -> bool:
"""
Check if Buildah is available.
Returns:
True if Buildah is available, False otherwise
"""
try:
result = subprocess.run(
['buildah', 'version'],
capture_output=True,
text=True,
timeout=10
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
def cleanup_container(container_name: str) -> None:
"""
Remove a Docker container.
@@ -958,6 +977,158 @@ def build_package_native(
return False
def build_package_buildah(
package_dir: Path,
package_name: str,
package_version: str,
package_release: str,
dist_dir: Path,
repository: str,
base_image: str = "git.unkin.net/unkin/almalinux9-rpmbuilder:latest",
dry_run: bool = False
) -> bool:
"""
Build a package using Buildah without Docker daemon.
Args:
package_dir: Directory containing the package resources
package_name: Name of the package
package_version: Package version
package_release: Package release number
dist_dir: Directory to store built packages
repository: Repository path (e.g., 'almalinux/el9')
base_image: Base Docker image to use for building
dry_run: If True, only show what would be done
Returns:
True if build succeeded, False otherwise
"""
logger = logging.getLogger(__name__)
try:
# Ensure dist directory exists with repository structure
package_dist_dir = dist_dir / repository
if not dry_run:
package_dist_dir.mkdir(parents=True, exist_ok=True)
# Generate container name
container_name = f"{package_name}-{package_version}-buildah"
# Read metadata.yaml to get all package fields
metadata_file = package_dir / "metadata.yaml"
metadata = {}
if metadata_file.exists():
try:
with open(metadata_file, 'r') as f:
metadata = yaml.safe_load(f) or {}
except Exception as e:
logger.warning(f"Could not read metadata.yaml: {e}")
logger.info(f"Building RPM for {package_name} version {package_version} using Buildah")
if dry_run:
logger.info(f"[DRY RUN] Would use Buildah to build from: {base_image}")
logger.info("[DRY RUN] Would set environment variables:")
logger.info(f"[DRY RUN] PACKAGE_NAME={package_name}")
logger.info(f"[DRY RUN] PACKAGE_VERSION={package_version}")
logger.info(f"[DRY RUN] PACKAGE_RELEASE={package_release}")
logger.info(f"[DRY RUN] Would copy artifacts to: {package_dist_dir}")
return True
try:
# Step 1: Create a working container from base image
from_args = ['buildah', 'from', '--name', container_name, base_image]
logger.debug(f"Running: {' '.join(from_args)}")
result = subprocess.run(from_args, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Failed to create Buildah container from {base_image}")
logger.error(f"stderr: {result.stderr}")
return False
# Step 2: Set environment variables
env_vars = {
'PACKAGE_NAME': package_name,
'PACKAGE_VERSION': package_version,
'PACKAGE_RELEASE': package_release,
'PACKAGE_DESCRIPTION': metadata.get('description', ''),
'PACKAGE_MAINTAINER': metadata.get('maintainer', ''),
'PACKAGE_HOMEPAGE': metadata.get('homepage', ''),
'PACKAGE_LICENSE': metadata.get('license', ''),
'PACKAGE_ARCH': metadata.get('arch', 'amd64'),
'PACKAGE_PLATFORM': metadata.get('platform', 'linux')
}
for key, value in env_vars.items():
config_args = ['buildah', 'config', '--env', f'{key}={value}', container_name]
logger.debug(f"Running: {' '.join(config_args)}")
result = subprocess.run(config_args, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Failed to set environment variable {key}")
return False
# Step 3: Copy resources to container
copy_args = [
'buildah', 'copy', container_name,
str(package_dir / "resources"), '/app/resources'
]
logger.debug(f"Running: {' '.join(copy_args)}")
result = subprocess.run(copy_args, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Failed to copy resources to container")
logger.error(f"stderr: {result.stderr}")
return False
# Step 4: Create dist directory in container
run_args = ['buildah', 'run', container_name, 'mkdir', '-p', '/app/dist']
logger.debug(f"Running: {' '.join(run_args)}")
result = subprocess.run(run_args, capture_output=True, text=True)
# Step 5: Run the build script
run_args = ['buildah', 'run', '--workingdir', '/app', container_name, '/app/resources/build.sh']
logger.debug(f"Running: {' '.join(run_args)}")
result = subprocess.run(run_args, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Buildah build script failed for {package_name}")
logger.error(f"stdout: {result.stdout}")
logger.error(f"stderr: {result.stderr}")
return False
# Step 6: Copy artifacts from container to host
copy_out_args = [
'buildah', 'run', container_name,
'find', '/app/dist', '-type', 'f', '-name', '*.rpm'
]
result = subprocess.run(copy_out_args, capture_output=True, text=True)
if result.returncode == 0 and result.stdout.strip():
rpm_files = result.stdout.strip().split('\n')
for rpm_file in rpm_files:
rpm_file = rpm_file.strip()
if rpm_file:
copy_args = [
'buildah', 'copy', '--from', container_name,
rpm_file, str(package_dist_dir)
]
logger.debug(f"Running: {' '.join(copy_args)}")
subprocess.run(copy_args, capture_output=True, text=True)
logger.info(f"Successfully built {package_name}-{package_version}-{package_release} using Buildah")
return True
finally:
# Step 7: Clean up container
rm_args = ['buildah', 'rm', container_name]
logger.debug(f"Running: {' '.join(rm_args)}")
subprocess.run(rm_args, capture_output=True, text=True)
except Exception as e:
logger.error(f"Unexpected error building {package_name} with Buildah: {e}")
return False
def cleanup_images(image_pattern: str = "*-builder") -> None:
"""
Clean up Docker images matching a pattern.
@@ -1099,7 +1270,8 @@ class Builder:
dry_run: bool = False,
force: bool = False,
distro: str = 'almalinux/el9',
native: bool = False
native: bool = False,
buildah: bool = False
) -> bool:
"""
Build a single package.
@@ -1175,9 +1347,9 @@ class Builder:
return False
package_info = PackageInfo(package, version, release, package_dir, distro, base_image)
return self._build_package(package_info, dry_run, force, native)
return self._build_package(package_info, dry_run, force, native, buildah)
def build_all(self, dry_run: bool = False, force: bool = False, parallel: int = 4, distro: str = 'el/9', native: bool = False) -> bool:
def build_all(self, dry_run: bool = False, force: bool = False, parallel: int = 4, distro: str = 'el/9', native: bool = False, buildah: bool = False) -> bool:
"""
Build all packages.
@@ -1199,29 +1371,29 @@ class Builder:
self.logger.info(f"Found {len(packages)} packages to process")
if parallel == 1:
return self._build_sequential(packages, dry_run, force, native)
return self._build_sequential(packages, dry_run, force, native, buildah)
else:
return self._build_parallel(packages, dry_run, force, parallel, native)
return self._build_parallel(packages, dry_run, force, parallel, native, buildah)
def _build_sequential(self, packages: List[PackageInfo], dry_run: bool, force: bool, native: bool) -> bool:
def _build_sequential(self, packages: List[PackageInfo], dry_run: bool, force: bool, native: bool, buildah: bool) -> bool:
"""Build packages sequentially."""
success_count = 0
for package_info in packages:
if self._build_package(package_info, dry_run, force, native):
if self._build_package(package_info, dry_run, force, native, buildah):
success_count += 1
self.logger.info(f"Built {success_count}/{len(packages)} packages successfully")
return success_count == len(packages)
def _build_parallel(self, packages: List[PackageInfo], dry_run: bool, force: bool, parallel: int, native: bool) -> bool:
def _build_parallel(self, packages: List[PackageInfo], dry_run: bool, force: bool, parallel: int, native: bool, buildah: bool) -> bool:
"""Build packages in parallel."""
success_count = 0
with ThreadPoolExecutor(max_workers=parallel) as executor:
# Submit all build tasks
future_to_package = {
executor.submit(self._build_package, pkg, dry_run, force, native): pkg
executor.submit(self._build_package, pkg, dry_run, force, native, buildah): pkg
for pkg in packages
}
@@ -1238,7 +1410,7 @@ class Builder:
self.logger.info(f"Built {success_count}/{len(packages)} packages successfully")
return success_count == len(packages)
def _build_package(self, package_info: PackageInfo, dry_run: bool, force: bool, native: bool = False) -> bool:
def _build_package(self, package_info: PackageInfo, dry_run: bool, force: bool, native: bool = False, buildah: bool = False) -> bool:
"""
Build a single package.
@@ -1264,11 +1436,12 @@ class Builder:
return True
# Check build tool availability (unless dry run)
use_native = native or check_native_build_deps()
use_docker = not use_native and check_docker_available()
use_native = native or (not buildah and check_native_build_deps())
use_buildah = buildah or (not use_native and check_buildah_available())
use_docker = not use_native and not use_buildah and check_docker_available()
if not dry_run and not use_native and not use_docker:
self.logger.error("Neither native build dependencies nor Docker is available")
if not dry_run and not use_native and not use_buildah and not use_docker:
self.logger.error("No build tools available (tried native, Buildah, Docker)")
return False
# Build the package using available tool
@@ -1283,6 +1456,18 @@ class Builder:
repository=package_info.distro,
dry_run=dry_run
)
elif use_buildah:
self.logger.debug(f"Using Buildah to build {package_info.name}")
return build_package_buildah(
package_dir=package_info.directory,
package_name=package_info.name,
package_version=package_info.version,
package_release=package_info.release,
dist_dir=self.dist_dir,
repository=package_info.distro,
base_image=package_info.base_image,
dry_run=dry_run
)
else:
self.logger.debug(f"Using Docker to build {package_info.name}")
return build_package_docker(
@@ -1557,6 +1742,7 @@ def build(
dry_run: bool = typer.Option(False, "--dry-run", help="Show what would be built without building"),
force: bool = typer.Option(False, "--force", help="Build even if package exists in registry"),
native: bool = typer.Option(False, "--native", help="Force native build (skip Docker even if available)"),
buildah: bool = typer.Option(False, "--buildah", help="Force Buildah build (requires Buildah)"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging")
):
"""Build a specific package."""
@@ -1619,7 +1805,8 @@ def build(
dry_run=dry_run,
force=force,
distro=distro,
native=native
native=native,
buildah=buildah
)
if not success:
@@ -1636,6 +1823,7 @@ def build_all(
parallel: int = typer.Option(4, help="Number of parallel builds"),
distro: str = typer.Option("almalinux/el9", help="Target distro (almalinux/el8, almalinux/el9, or 'all' for all distros)"),
native: bool = typer.Option(False, "--native", help="Force native build (skip Docker even if available)"),
buildah: bool = typer.Option(False, "--buildah", help="Force Buildah build (requires Buildah)"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging")
):
"""Build all packages."""
@@ -1650,7 +1838,8 @@ def build_all(
force=force,
parallel=parallel,
distro=distro,
native=native
native=native,
buildah=buildah
)
if not success: