#!/usr/bin/env nu # Container build tool - builds Docker containers for platform services # # Builds: # - Orchestrator service container # - Control center container # - Web UI container # - All-in-one development container # - Platform-specific containers use std log def main [ --dist-dir: string = "dist" # Distribution directory with built components --output-registry: string = "local" # Container registry: local, docker.io, ghcr.io, custom --tag-prefix: string = "provisioning" # Container tag prefix --version: string = "" # Version tag (auto-detected if empty) --build-args: string = "" # Comma-separated build arguments --platforms: string = "linux/amd64" # Target platforms for multi-arch builds --push: bool = false # Push containers to registry --cache: bool = true # Use build cache --verbose: bool = false # Enable verbose logging --parallel: bool = true # Build containers in parallel ] -> record { let repo_root = ($env.PWD | path dirname | path dirname | path dirname) let dist_root = ($dist_dir | path expand) # Detect version if not provided let detected_version = if $version == "" { detect_version $repo_root } else { $version } let container_config = { dist_dir: $dist_root output_registry: $output_registry tag_prefix: $tag_prefix version: $detected_version build_args: ($build_args | if $in == "" { [] } else { $in | split row "," | each { str trim } }) platforms: ($platforms | split row "," | each { str trim }) push: $push cache: $cache verbose: $verbose parallel: $parallel repo_root: $repo_root } log info $"Starting container builds with config: ($container_config)" # Validate distribution directory if not ($dist_root | path exists) { log error $"Distribution directory does not exist: ($dist_root)" exit 1 } # Check Docker availability let docker_available = check_docker_availability if not $docker_available { log error "Docker is not available or not running" exit 1 } # Define container configurations let container_definitions = [ { name: "orchestrator" dockerfile: "Dockerfile.orchestrator" context: "." binary_path: ($dist_root | path join "platform") dependencies: ["orchestrator"] }, { name: "control-center" dockerfile: "Dockerfile.control-center" context: "." binary_path: ($dist_root | path join "platform") dependencies: ["control-center"] }, { name: "web-ui" dockerfile: "Dockerfile.web-ui" context: "." binary_path: ($dist_root | path join "platform") dependencies: ["control-center-ui"] }, { name: "all-in-one" dockerfile: "Dockerfile.all-in-one" context: "." binary_path: ($dist_root | path join "platform") dependencies: ["orchestrator", "control-center", "control-center-ui"] } ] # Create Dockerfiles if they don't exist ensure_dockerfiles_exist $container_definitions $container_config # Build containers let build_results = if $container_config.parallel { build_containers_parallel $container_definitions $container_config } else { build_containers_sequential $container_definitions $container_config } # Push containers if requested let push_results = if $container_config.push { push_containers $build_results $container_config } else { { status: "skipped", pushed: [] } } let summary = { total_containers: ($container_definitions | length) successful_builds: ($build_results | where status == "success" | length) failed_builds: ($build_results | where status == "failed" | length) push_results: $push_results container_config: $container_config build_results: $build_results } if $summary.failed_builds > 0 { log error $"Container build completed with ($summary.failed_builds) failures" exit 1 } else { log info $"Container build completed successfully - ($summary.successful_builds) containers built" } return $summary } # Detect version from git or other sources def detect_version [repo_root: string] -> string { try { cd $repo_root let git_version = (git describe --tags --always --dirty 2>/dev/null | complete) if $git_version.exit_code == 0 and ($git_version.stdout | str trim) != "" { return ($git_version.stdout | str trim) } return $"dev-(date now | format date "%Y%m%d")" } catch { return "dev-unknown" } } # Check if Docker is available def check_docker_availability [] -> bool { try { let docker_check = (docker --version | complete) return ($docker_check.exit_code == 0) } catch { return false } } # Ensure Dockerfiles exist, create them if they don't def ensure_dockerfiles_exist [ container_definitions: list container_config: record ] { let dockerfile_dir = ($container_config.repo_root | path join "docker") # Ensure docker directory exists mkdir $dockerfile_dir for container in $container_definitions { let dockerfile_path = ($dockerfile_dir | path join $container.dockerfile) if not ($dockerfile_path | path exists) { log info $"Creating Dockerfile for ($container.name): ($dockerfile_path)" create_dockerfile $container $container_config $dockerfile_path } } } # Create a Dockerfile for a container def create_dockerfile [ container: record container_config: record dockerfile_path: string ] { let dockerfile_content = match $container.name { "orchestrator" => { create_orchestrator_dockerfile $container_config } "control-center" => { create_control_center_dockerfile $container_config } "web-ui" => { create_web_ui_dockerfile $container_config } "all-in-one" => { create_all_in_one_dockerfile $container_config } _ => { create_generic_dockerfile $container $container_config } } $dockerfile_content | save $dockerfile_path log info $"Created Dockerfile: ($dockerfile_path)" } # Create orchestrator Dockerfile def create_orchestrator_dockerfile [config: record] -> string { $"# Provisioning Orchestrator Container # Version: ($config.version) FROM debian:bookworm-slim # Install runtime dependencies RUN apt-get update && apt-get install -y \\ ca-certificates \\ curl \\ && rm -rf /var/lib/apt/lists/* # Create application user RUN useradd -r -s /bin/false -m -d /app provisioning # Copy binary COPY dist/platform/orchestrator-* /usr/local/bin/provisioning-orchestrator RUN chmod +x /usr/local/bin/provisioning-orchestrator # Copy core libraries COPY dist/core /app/core COPY dist/config /app/config # Set ownership RUN chown -R provisioning:provisioning /app # Switch to application user USER provisioning WORKDIR /app # Expose ports EXPOSE 8080 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost:8080/health || exit 1 # Run orchestrator CMD [\"/usr/local/bin/provisioning-orchestrator\", \"--host\", \"0.0.0.0\", \"--port\", \"8080\"] " } # Create control center Dockerfile def create_control_center_dockerfile [config: record] -> string { $"# Provisioning Control Center Container # Version: ($config.version) FROM debian:bookworm-slim # Install runtime dependencies RUN apt-get update && apt-get install -y \\ ca-certificates \\ curl \\ && rm -rf /var/lib/apt/lists/* # Create application user RUN useradd -r -s /bin/false -m -d /app provisioning # Copy binary COPY dist/platform/control-center-* /usr/local/bin/control-center RUN chmod +x /usr/local/bin/control-center # Copy core libraries COPY dist/core /app/core COPY dist/config /app/config # Set ownership RUN chown -R provisioning:provisioning /app # Switch to application user USER provisioning WORKDIR /app # Expose ports EXPOSE 9080 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost:9080/health || exit 1 # Run control center CMD [\"/usr/local/bin/control-center\", \"--host\", \"0.0.0.0\", \"--port\", \"9080\"] " } # Create web UI Dockerfile def create_web_ui_dockerfile [config: record] -> string { $"# Provisioning Web UI Container # Version: ($config.version) FROM nginx:alpine # Copy built UI assets COPY dist/platform/control-center-ui /usr/share/nginx/html # Copy nginx configuration COPY docker/nginx.conf /etc/nginx/conf.d/default.conf # Expose port EXPOSE 80 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost/health || exit 1 # Run nginx CMD [\"nginx\", \"-g\", \"daemon off;\"] " } # Create all-in-one Dockerfile def create_all_in_one_dockerfile [config: record] -> string { $"# Provisioning All-in-One Container # Version: ($config.version) FROM debian:bookworm-slim # Install runtime dependencies RUN apt-get update && apt-get install -y \\ ca-certificates \\ curl \\ nginx \\ supervisor \\ && rm -rf /var/lib/apt/lists/* # Create application user RUN useradd -r -s /bin/false -m -d /app provisioning # Copy binaries COPY dist/platform/orchestrator-* /usr/local/bin/provisioning-orchestrator COPY dist/platform/control-center-* /usr/local/bin/control-center RUN chmod +x /usr/local/bin/provisioning-orchestrator /usr/local/bin/control-center # Copy core libraries and configuration COPY dist/core /app/core COPY dist/config /app/config # Copy web UI COPY dist/platform/control-center-ui /usr/share/nginx/html # Copy supervisor configuration COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf COPY docker/nginx-all-in-one.conf /etc/nginx/sites-available/default # Set ownership RUN chown -R provisioning:provisioning /app # Expose ports EXPOSE 80 8080 9080 # Health check HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \\ CMD curl -f http://localhost/health && curl -f http://localhost:8080/health || exit 1 # Run supervisor CMD [\"/usr/bin/supervisord\", \"-c\", \"/etc/supervisor/conf.d/supervisord.conf\"] " } # Create generic Dockerfile def create_generic_dockerfile [container: record, config: record] -> string { $"# Generic Provisioning Service Container # Service: ($container.name) # Version: ($config.version) FROM debian:bookworm-slim # Install runtime dependencies RUN apt-get update && apt-get install -y \\ ca-certificates \\ curl \\ && rm -rf /var/lib/apt/lists/* # Create application user RUN useradd -r -s /bin/false -m -d /app provisioning # Copy binaries COPY dist/platform/* /usr/local/bin/ RUN chmod +x /usr/local/bin/* # Copy core libraries COPY dist/core /app/core COPY dist/config /app/config # Set ownership RUN chown -R provisioning:provisioning /app # Switch to application user USER provisioning WORKDIR /app # Expose default port EXPOSE 8080 # Run service CMD [\"sh\", \"-c\", \"echo 'Container for ($container.name) - configure as needed'\"] " } # Build containers sequentially def build_containers_sequential [ container_definitions: list container_config: record ] -> list { $container_definitions | each {|container| build_single_container $container $container_config } } # Build containers in parallel def build_containers_parallel [ container_definitions: list container_config: record ] -> list { # For simplicity, using sequential for now # In a real implementation, you might use background processes build_containers_sequential $container_definitions $container_config } # Build a single container def build_single_container [ container: record container_config: record ] -> record { log info $"Building container: ($container.name)" let start_time = (date now) let dockerfile_path = ($container_config.repo_root | path join "docker" $container.dockerfile) let image_tag = $"($container_config.tag_prefix)/($container.name):($container_config.version)" try { # Check if required binaries exist let missing_deps = check_container_dependencies $container $container_config if ($missing_deps | length) > 0 { return { container: $container.name status: "failed" reason: $"Missing dependencies: ($missing_deps | str join ', ')" duration: ((date now) - $start_time) } } # Build Docker command let mut docker_cmd = ["docker", "build"] # Add build arguments for arg in $container_config.build_args { $docker_cmd = ($docker_cmd | append ["--build-arg", $arg]) } # Add cache options if not $container_config.cache { $docker_cmd = ($docker_cmd | append "--no-cache") } # Add platform support for multi-arch if ($container_config.platforms | length) > 1 { $docker_cmd = ($docker_cmd | append ["--platform", ($container_config.platforms | str join ",")]) } else { $docker_cmd = ($docker_cmd | append ["--platform", ($container_config.platforms | get 0)]) } # Add tags $docker_cmd = ($docker_cmd | append ["-t", $image_tag]) # Add dockerfile and context $docker_cmd = ($docker_cmd | append ["-f", $dockerfile_path, $container.context]) # Execute build cd ($container_config.repo_root) if $container_config.verbose { log info $"Running: ($docker_cmd | str join ' ')" } let build_result = (run-external --redirect-combine $docker_cmd.0 ...$docker_cmd.1.. | complete) if $build_result.exit_code == 0 { # Get image size let image_info = get_image_info $image_tag log info $"Successfully built container: ($container.name) -> ($image_tag)" { container: $container.name status: "success" image_tag: $image_tag image_size: $image_info.size duration: ((date now) - $start_time) } } else { log error $"Failed to build container ($container.name): ($build_result.stderr)" { container: $container.name status: "failed" reason: $build_result.stderr duration: ((date now) - $start_time) } } } catch {|err| log error $"Failed to build container ($container.name): ($err.msg)" { container: $container.name status: "failed" reason: $err.msg duration: ((date now) - $start_time) } } } # Check container dependencies def check_container_dependencies [ container: record container_config: record ] -> list { let mut missing_deps = [] for dep in $container.dependencies { let binary_pattern = $"($dep)*" let found_binaries = (find $container.binary_path -name $binary_pattern -type f) if ($found_binaries | length) == 0 { $missing_deps = ($missing_deps | append $dep) } } return $missing_deps } # Get image information def get_image_info [image_tag: string] -> record { try { let inspect_result = (docker inspect $image_tag | from json | get 0) { size: $inspect_result.Size created: $inspect_result.Created architecture: $inspect_result.Architecture } } catch { { size: 0, created: "", architecture: "unknown" } } } # Push containers to registry def push_containers [ build_results: list container_config: record ] -> record { log info $"Pushing containers to registry: ($container_config.output_registry)" let successful_builds = ($build_results | where status == "success") let mut push_results = [] for build in $successful_builds { try { log info $"Pushing: ($build.image_tag)" let push_result = (docker push $build.image_tag | complete) if $push_result.exit_code == 0 { log info $"Successfully pushed: ($build.image_tag)" $push_results = ($push_results | append { image: $build.image_tag status: "success" }) } else { log error $"Failed to push ($build.image_tag): ($push_result.stderr)" $push_results = ($push_results | append { image: $build.image_tag status: "failed" reason: $push_result.stderr }) } } catch {|err| log error $"Failed to push ($build.image_tag): ($err.msg)" $push_results = ($push_results | append { image: $build.image_tag status: "failed" reason: $err.msg }) } } { status: (if ($push_results | where status == "failed" | length) > 0 { "partial" } else { "success" }) total_pushed: ($push_results | where status == "success" | length) failed_pushes: ($push_results | where status == "failed" | length) pushed: $push_results } } # Show container information def "main info" [] { let docker_available = check_docker_availability let info = { docker_available: $docker_available } if $docker_available { let docker_info = try { docker version --format json | from json } catch { {} } let images = try { docker images --filter "reference=provisioning/*" --format "table {{.Repository}}:{{.Tag}}\t{{.Size}}\t{{.CreatedAt}}" | lines | skip 1 } catch { [] } $info | insert docker_info $docker_info | insert provisioning_images $images } else { $info } } # List built containers def "main list" [--all: bool = false] { let filter = if $all { "" } else { "reference=provisioning/*" } let docker_available = check_docker_availability if not $docker_available { return { error: "Docker not available" } } try { let images = if $all { docker images --format json | lines | each { from json } } else { docker images --filter "reference=provisioning/*" --format json | lines | each { from json } } $images | select Repository Tag Size CreatedAt } catch { [] } }