1 Commits

Author SHA1 Message Date
unkinben f4eb53998a feat: migrate to woodpeckerci
Build / build-9 (pull_request) Successful in 17s
Build / build-8 (pull_request) Successful in 18s
ci/woodpecker/pr/build-almalinux8 Pipeline failed
ci/woodpecker/pr/pre-commit Pipeline failed
ci/woodpecker/pr/build-almalinux9 Pipeline failed
- update build tool for kubernetes auth
- update build tool to build packages without docker
- add woodpecker pre-commit and build jobs
2026-03-07 12:02:39 +11:00
4 changed files with 102 additions and 96 deletions
+2 -2
View File
@@ -3,9 +3,9 @@ when:
steps:
- name: build rpms
image: gcr.io/kaniko-project/executor:latest
image: git.unkin.net/unkin/almalinux8-rpmbuilder:latest
commands:
- ./tools/build build-all --distro almalinux/el8 --use-kaniko
- ./tools/build build-all --distro almalinux/el8
backend_options:
kubernetes:
serviceAccountName: default
+2 -2
View File
@@ -3,9 +3,9 @@ when:
steps:
- name: build rpms
image: gcr.io/kaniko-project/executor:latest
image: git.unkin.net/unkin/almalinux9-rpmbuilder:latest
commands:
- ./tools/build build-all --distro almalinux/el8 --use-kaniko
- ./tools/build build-all --distro almalinux/el9 --native
backend_options:
kubernetes:
serviceAccountName: default
+5
View File
@@ -26,6 +26,11 @@ build-all:
@echo "Building all packages using Python tooling for distro $(DISTRO)..."
$(BUILD_TOOL) build-all --distro $(DISTRO)
# Build all packages using native build (no Docker)
build-all-native:
@echo "Building all packages natively (no Docker) for distro $(DISTRO)..."
$(BUILD_TOOL) build-all --distro $(DISTRO) --native
# Build specific package using Python tool
.PHONY: $(PACKAGES)
$(PACKAGES):
+93 -92
View File
@@ -601,23 +601,29 @@ def check_docker_available() -> bool:
return False
def check_kaniko_available() -> bool:
def check_native_build_deps() -> bool:
"""
Check if Kaniko executor is available.
Check if native build dependencies are available (nfpm, envsubst, etc.).
Returns:
True if Kaniko is available, False otherwise
True if native build dependencies are available, False otherwise
"""
try:
result = subprocess.run(
['/kaniko/executor', '--version'],
capture_output=True,
text=True,
timeout=10
)
return result.returncode == 0
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
required_commands = ['nfpm', 'envsubst', 'wget', 'tar']
for cmd in required_commands:
try:
result = subprocess.run(
[cmd, '--version'],
capture_output=True,
text=True,
timeout=5
)
if result.returncode != 0:
return False
except (subprocess.TimeoutExpired, FileNotFoundError):
return False
return True
def cleanup_container(container_name: str) -> None:
@@ -827,18 +833,17 @@ def build_package_docker(
return False
def build_package_kaniko(
def build_package_native(
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 Kaniko without Docker daemon.
Build a package natively without Docker, running build scripts directly.
Args:
package_dir: Directory containing the package resources
@@ -847,7 +852,6 @@ def build_package_kaniko(
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:
@@ -861,19 +865,23 @@ def build_package_kaniko(
if not dry_run:
package_dist_dir.mkdir(parents=True, exist_ok=True)
# Create a temporary workspace for Kaniko
# Create a temporary workspace for native build
import tempfile
with tempfile.TemporaryDirectory() as temp_dir:
temp_path = Path(temp_dir)
app_dir = temp_path / "app"
app_dist_dir = app_dir / "dist"
# Create directories
if not dry_run:
app_dir.mkdir(parents=True, exist_ok=True)
app_dist_dir.mkdir(parents=True, exist_ok=True)
# Copy package resources to temp directory
import shutil
temp_resources_dir = temp_path / "resources"
shutil.copytree(package_dir / "resources", temp_resources_dir)
# Copy Dockerfile to temp directory
central_dockerfile = package_dir.parent.parent / "Dockerfile"
shutil.copy2(central_dockerfile, temp_path / "Dockerfile")
temp_resources_dir = app_dir / "resources"
if not dry_run:
shutil.copytree(package_dir / "resources", temp_resources_dir)
# Read metadata.yaml to get all package fields
metadata_file = package_dir / "metadata.yaml"
@@ -885,79 +893,68 @@ def build_package_kaniko(
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 Kaniko")
logger.info(f"Building RPM for {package_name} version {package_version} natively")
if dry_run:
logger.info(f"[DRY RUN] Would use Kaniko to build from: {temp_path}")
logger.info(f"[DRY RUN] Would use base image: {base_image}")
logger.info("[DRY RUN] Would pass build arguments:")
logger.info(f"[DRY RUN] Would build natively from: {temp_path}")
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] PACKAGE_DESCRIPTION={metadata.get('description', '')}")
logger.info(f"[DRY RUN] Would run: {temp_resources_dir / 'build.sh'}")
logger.info(f"[DRY RUN] Would copy artifacts to: {package_dist_dir}")
return True
# Build using Kaniko
kaniko_args = [
'/kaniko/executor',
'--context', str(temp_path),
'--dockerfile', str(temp_path / "Dockerfile"),
'--build-arg', f'BASE_IMAGE={base_image}',
'--build-arg', f'PACKAGE_NAME={package_name}',
'--build-arg', f'PACKAGE_VERSION={package_version}',
'--build-arg', f'PACKAGE_RELEASE={package_release}',
'--build-arg', f'PACKAGE_DESCRIPTION={metadata.get("description", "")}',
'--build-arg', f'PACKAGE_MAINTAINER={metadata.get("maintainer", "")}',
'--build-arg', f'PACKAGE_HOMEPAGE={metadata.get("homepage", "")}',
'--build-arg', f'PACKAGE_LICENSE={metadata.get("license", "")}',
'--build-arg', f'PACKAGE_ARCH={metadata.get("arch", "amd64")}',
'--build-arg', f'PACKAGE_PLATFORM={metadata.get("platform", "linux")}',
'--no-push', # Don't push to registry, just build
'--tar-path', str(temp_path / "image.tar")
]
# Set up environment variables like the Dockerfile does
build_env = os.environ.copy()
build_env.update({
'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')
})
logger.debug(f"Running: {' '.join(kaniko_args)}")
# Run the build script
build_script = temp_resources_dir / "build.sh"
if not build_script.exists():
logger.error(f"Build script not found: {build_script}")
return False
# Make build script executable
build_script.chmod(0o755)
logger.debug(f"Running build script: {build_script}")
result = subprocess.run(
kaniko_args,
[str(build_script)],
cwd=app_dir,
env=build_env,
capture_output=True,
text=True,
cwd=temp_path
text=True
)
if result.returncode != 0:
logger.error(f"Kaniko build failed for {package_name}")
logger.error(f"Native build failed for {package_name}")
logger.error(f"stdout: {result.stdout}")
logger.error(f"stderr: {result.stderr}")
return False
# Extract the artifacts from the built image
extract_args = [
'tar', '-xf', str(temp_path / "image.tar"),
'-C', str(temp_path),
'--strip-components=1',
'app/dist'
]
logger.debug(f"Running: {' '.join(extract_args)}")
result = subprocess.run(extract_args, capture_output=True, text=True)
if result.returncode != 0:
logger.error(f"Failed to extract artifacts for {package_name}")
logger.error(f"stderr: {result.stderr}")
return False
# Copy artifacts to final destination
extracted_dist = temp_path / "app" / "dist"
if extracted_dist.exists():
for item in extracted_dist.iterdir():
if app_dist_dir.exists():
for item in app_dist_dir.iterdir():
if item.is_file():
shutil.copy2(item, package_dist_dir)
logger.info(f"Successfully built {package_name}-{package_version}-{package_release} using Kaniko")
logger.info(f"Successfully built {package_name}-{package_version}-{package_release} natively")
return True
except Exception as e:
logger.error(f"Unexpected error building {package_name} with Kaniko: {e}")
logger.error(f"Unexpected error building {package_name} natively: {e}")
return False
@@ -1101,7 +1098,8 @@ class Builder:
release: str,
dry_run: bool = False,
force: bool = False,
distro: str = 'almalinux/el9'
distro: str = 'almalinux/el9',
native: bool = False
) -> bool:
"""
Build a single package.
@@ -1177,9 +1175,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)
return self._build_package(package_info, dry_run, force, native)
def build_all(self, dry_run: bool = False, force: bool = False, parallel: int = 4, distro: str = 'el/9') -> bool:
def build_all(self, dry_run: bool = False, force: bool = False, parallel: int = 4, distro: str = 'el/9', native: bool = False) -> bool:
"""
Build all packages.
@@ -1201,29 +1199,29 @@ class Builder:
self.logger.info(f"Found {len(packages)} packages to process")
if parallel == 1:
return self._build_sequential(packages, dry_run, force)
return self._build_sequential(packages, dry_run, force, native)
else:
return self._build_parallel(packages, dry_run, force, parallel)
return self._build_parallel(packages, dry_run, force, parallel, native)
def _build_sequential(self, packages: List[PackageInfo], dry_run: bool, force: bool) -> bool:
def _build_sequential(self, packages: List[PackageInfo], dry_run: bool, force: bool, native: bool) -> bool:
"""Build packages sequentially."""
success_count = 0
for package_info in packages:
if self._build_package(package_info, dry_run, force):
if self._build_package(package_info, dry_run, force, native):
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) -> bool:
def _build_parallel(self, packages: List[PackageInfo], dry_run: bool, force: bool, parallel: int, native: 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): pkg
executor.submit(self._build_package, pkg, dry_run, force, native): pkg
for pkg in packages
}
@@ -1240,7 +1238,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) -> bool:
def _build_package(self, package_info: PackageInfo, dry_run: bool, force: bool, native: bool = False) -> bool:
"""
Build a single package.
@@ -1266,24 +1264,23 @@ class Builder:
return True
# Check build tool availability (unless dry run)
use_kaniko = check_kaniko_available()
use_docker = not use_kaniko and check_docker_available()
use_native = native or check_native_build_deps()
use_docker = not use_native and check_docker_available()
if not dry_run and not use_kaniko and not use_docker:
self.logger.error("Neither Kaniko nor Docker is available")
if not dry_run and not use_native and not use_docker:
self.logger.error("Neither native build dependencies nor Docker is available")
return False
# Build the package using available tool
if use_kaniko:
self.logger.debug(f"Using Kaniko to build {package_info.name}")
return build_package_kaniko(
if use_native:
self.logger.debug(f"Using native build for {package_info.name}")
return build_package_native(
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:
@@ -1559,6 +1556,7 @@ def build(
distro: str = typer.Option("almalinux/el9", help="Target distro (default: almalinux/el9)"),
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)"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging")
):
"""Build a specific package."""
@@ -1620,7 +1618,8 @@ def build(
release=str(release),
dry_run=dry_run,
force=force,
distro=distro
distro=distro,
native=native
)
if not success:
@@ -1636,6 +1635,7 @@ def build_all(
force: bool = typer.Option(False, "--force", help="Build even if packages exist in registry"),
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)"),
verbose: bool = typer.Option(False, "--verbose", "-v", help="Enable verbose logging")
):
"""Build all packages."""
@@ -1649,7 +1649,8 @@ def build_all(
dry_run=dry_run,
force=force,
parallel=parallel,
distro=distro
distro=distro,
native=native
)
if not success: