639 lines
19 KiB
Plaintext
639 lines
19 KiB
Plaintext
#!/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 {
|
|
[]
|
|
}
|
|
} |