2025-10-07 10:32:04 +01:00

593 lines
18 KiB
Plaintext

# Mode Management Commands
# Execution mode switching and configuration for provisioning system
#
# Modes:
# - solo: Single developer local development
# - multi-user: Team collaboration with shared services
# - cicd: CI/CD pipeline execution
# - enterprise: Production enterprise deployment
use ../utils/logging.nu *
# Get current active mode
export def "mode current" [] -> record {
let mode_file = get-mode-config-path
if not ($mode_file | path exists) {
return {
mode: "solo"
configured: false
config_file: null
message: "No mode configured, using default 'solo' mode"
}
}
let config = open $mode_file
{
mode: $config.mode
configured: true
config_file: $mode_file
description: $config.description?
}
}
# List all available modes
export def "mode list" [] -> table {
let modes_dir = get-modes-dir
if not ($modes_dir | path exists) {
return []
}
ls $modes_dir
| where name =~ ".yaml$"
| each {|it|
let config = open $it.name
let is_current = (is-current-mode $config.mode)
{
mode: $config.mode
description: $config.description
current: $is_current
config_file: $it.name
}
}
| sort-by mode
}
# Show mode information
export def "mode show" [
mode_name?: string # Mode to show (defaults to current)
] -> record {
let target_mode = if ($mode_name == null) {
(mode current).mode
} else {
$mode_name
}
let config_file = get-mode-file $target_mode
if not ($config_file | path exists) {
error make {
msg: $"Mode '($target_mode)' not found"
label: {
text: "Available modes: solo, multi-user, cicd, enterprise"
span: (metadata $mode_name).span
}
}
}
open $config_file
}
# Switch to a different mode
export def "mode switch" [
mode_name: string # Mode to switch to (solo, multi-user, cicd, enterprise)
--validate # Validate mode configuration before switching
--dry-run # Show what would change without applying
] -> nothing {
# Validate mode exists
let config_file = get-mode-file $mode_name
if not ($config_file | path exists) {
error make {
msg: $"Mode '($mode_name)' configuration not found"
label: {
text: $"Expected: ($config_file)"
span: (metadata $mode_name).span
}
}
}
# Load and validate configuration
let config = open $config_file
if $validate {
log-info $"Validating mode configuration: ($mode_name)"
mode validate $mode_name
}
# Get current mode
let current = (mode current).mode
if $dry_run {
print $"Would switch from '($current)' to '($mode_name)'"
print $"\nConfiguration changes:"
print $config
return
}
# Confirm switch (unless in non-interactive mode)
if (is-interactive) and ($current != $mode_name) {
let confirm = (input $"Switch from '($current)' to '($mode_name)' mode? \(y/N\) ")
if ($confirm | str downcase) != "y" {
log-info "Mode switch cancelled"
return
}
}
# Copy mode config to active config
let active_config = get-mode-config-path
# Backup current config if exists
if ($active_config | path exists) {
let backup = $"($active_config).backup"
cp $active_config $backup
log-debug $"Backed up current config to ($backup)"
}
# Copy new mode config
cp $config_file $active_config
log-info $"Switched to '($mode_name)' mode"
# Show next steps based on mode
show-mode-next-steps $mode_name
}
# Validate mode configuration
export def "mode validate" [
mode_name?: string # Mode to validate (defaults to current)
] -> record {
let target_mode = if ($mode_name == null) {
(mode current).mode
} else {
$mode_name
}
let config_file = get-mode-file $target_mode
if not ($config_file | path exists) {
error make {
msg: $"Mode configuration not found: ($config_file)"
}
}
log-info $"Validating mode: ($target_mode)"
let config = open $config_file
mut validation_results = []
# Validate required fields
let required_fields = ["mode", "description", "authentication", "services", "extensions", "workspaces", "security"]
for field in $required_fields {
if ($field in $config) {
$validation_results = ($validation_results | append {
check: $"Required field: ($field)"
status: "pass"
})
} else {
$validation_results = ($validation_results | append {
check: $"Required field: ($field)"
status: "fail"
message: $"Missing required field: ($field)"
})
}
}
# Validate service configurations
let service_checks = validate-services $config.services
$validation_results = ($validation_results | append $service_checks)
# Validate authentication
let auth_checks = validate-authentication $config.authentication
$validation_results = ($validation_results | append $auth_checks)
# Validate extensions
let ext_checks = validate-extensions $config.extensions
$validation_results = ($validation_results | append $ext_checks)
# Count results
let total = ($validation_results | length)
let passed = ($validation_results | where status == "pass" | length)
let failed = ($validation_results | where status == "fail" | length)
# Display results
print $"\nValidation Results for '($target_mode)' mode:"
print $" Total checks: ($total)"
print $" Passed: ($passed)"
print $" Failed: ($failed)"
if $failed > 0 {
print $"\nFailed checks:"
$validation_results
| where status == "fail"
| each {|it| print $" ✗ ($it.check): ($it.message)"}
}
{
mode: $target_mode
total_checks: $total
passed: $passed
failed: $failed
success: ($failed == 0)
checks: $validation_results
}
}
# Initialize mode system
export def "mode init" [] -> nothing {
log-info "Initializing mode system"
let modes_dir = get-modes-dir
# Create modes directory if not exists
if not ($modes_dir | path exists) {
mkdir $modes_dir
log-debug $"Created modes directory: ($modes_dir)"
}
# Check if mode configs exist
let mode_files = ["solo.yaml", "multi-user.yaml", "cicd.yaml", "enterprise.yaml"]
for mode_file in $mode_files {
let full_path = ($modes_dir | path join $mode_file)
if not ($full_path | path exists) {
log-warning $"Mode configuration missing: ($mode_file)"
}
}
# Initialize to solo mode if no active config
let active_config = get-mode-config-path
if not ($active_config | path exists) {
log-info "No active mode configuration, initializing to 'solo' mode"
mode switch solo --no-confirm
}
log-info "Mode system initialized"
}
# OCI registry management
export def "mode oci-registry" [
action: string # Action: start, stop, status, logs
] -> nothing {
let current_mode = (mode current)
let config = mode show
if ($config.services.oci-registry.deployment == "disabled") {
log-error "OCI registry is disabled in current mode"
return
}
if ($config.services.oci-registry.deployment != "local") {
log-error "OCI registry management only available for local deployment"
log-info $"Current deployment: ($config.services.oci-registry.deployment)"
return
}
match $action {
"start" => { start-oci-registry $config }
"stop" => { stop-oci-registry $config }
"status" => { oci-registry-status $config }
"logs" => { oci-registry-logs $config }
_ => {
error make {
msg: $"Unknown action: ($action)"
label: {
text: "Valid actions: start, stop, status, logs"
span: (metadata $action).span
}
}
}
}
}
# Compare modes
export def "mode compare" [
mode1: string # First mode to compare
mode2: string # Second mode to compare
] -> record {
let config1 = mode show $mode1
let config2 = mode show $mode2
print $"\nComparing '($mode1)' and '($mode2)' modes:\n"
# Compare key aspects
print "Authentication:"
print $" ($mode1): ($config1.authentication.type)"
print $" ($mode2): ($config2.authentication.type)"
print "\nService Deployments:"
print $" Orchestrator:"
print $" ($mode1): ($config1.services.orchestrator.deployment)"
print $" ($mode2): ($config2.services.orchestrator.deployment)"
print $" OCI Registry:"
print $" ($mode1): ($config1.services.oci-registry.deployment) \(($config1.services.oci-registry.type))"
print $" ($mode2): ($config2.services.oci-registry.deployment) \(($config2.services.oci-registry.type))"
print "\nExtension Source:"
print $" ($mode1): ($config1.extensions.source)"
print $" ($mode2): ($config2.extensions.source)"
print "\nWorkspace Locking:"
print $" ($mode1): ($config1.workspaces.locking)"
print $" ($mode2): ($config2.workspaces.locking)"
print "\nSecurity:"
print $" Encryption at rest:"
print $" ($mode1): ($config1.security.encryption_at_rest)"
print $" ($mode2): ($config2.security.encryption_at_rest)"
print $" Encryption in transit:"
print $" ($mode1): ($config1.security.encryption_in_transit)"
print $" ($mode2): ($config2.security.encryption_in_transit)"
{
mode1: $mode1
mode2: $mode2
comparison: {
authentication: {
mode1: $config1.authentication.type
mode2: $config2.authentication.type
different: ($config1.authentication.type != $config2.authentication.type)
}
deployment: {
mode1: $config1.services.orchestrator.deployment
mode2: $config2.services.orchestrator.deployment
different: ($config1.services.orchestrator.deployment != $config2.services.orchestrator.deployment)
}
}
}
}
# =============================================================================
# Helper Functions
# =============================================================================
def get-mode-config-path [] -> string {
let workspace_config = $env.PROVISIONING_WORKSPACE_CONFIG? | default (
$env.HOME | path join ".provisioning" "config"
)
$workspace_config | path join "active-mode.yaml"
}
def get-modes-dir [] -> string {
$env.PROVISIONING_PROJECT_ROOT? | default (pwd) | path join "workspace" "config" "modes"
}
def get-mode-file [mode: string] -> string {
let modes_dir = get-modes-dir
$modes_dir | path join $"($mode).yaml"
}
def is-current-mode [mode: string] -> bool {
let current = (mode current).mode
$current == $mode
}
def is-interactive [] -> bool {
(term size).columns > 0
}
def validate-services [services: record] -> list<record> {
mut results = []
# Check orchestrator
if ("orchestrator" in $services) {
$results = ($results | append {
check: "Service: orchestrator"
status: "pass"
})
if ($services.orchestrator.deployment in ["local", "remote", "k8s"]) {
$results = ($results | append {
check: "Orchestrator deployment valid"
status: "pass"
})
} else {
$results = ($results | append {
check: "Orchestrator deployment valid"
status: "fail"
message: $"Invalid deployment: ($services.orchestrator.deployment)"
})
}
} else {
$results = ($results | append {
check: "Service: orchestrator"
status: "fail"
message: "Orchestrator service not configured"
})
}
# Check OCI registry
if ("oci-registry" in $services) {
$results = ($results | append {
check: "Service: oci-registry"
status: "pass"
})
if ($services.oci-registry.deployment in ["local", "remote", "disabled"]) {
$results = ($results | append {
check: "OCI registry deployment valid"
status: "pass"
})
}
}
$results
}
def validate-authentication [auth: record] -> list<record> {
mut results = []
if ("type" in $auth) {
let valid_types = ["none", "token", "mtls", "oauth", "kms"]
if ($auth.type in $valid_types) {
$results = ($results | append {
check: "Authentication type valid"
status: "pass"
})
} else {
$results = ($results | append {
check: "Authentication type valid"
status: "fail"
message: $"Invalid auth type: ($auth.type)"
})
}
} else {
$results = ($results | append {
check: "Authentication type"
status: "fail"
message: "Authentication type not specified"
})
}
$results
}
def validate-extensions [extensions: record] -> list<record> {
mut results = []
if ("source" in $extensions) {
let valid_sources = ["local", "gitea", "oci", "mixed"]
if ($extensions.source in $valid_sources) {
$results = ($results | append {
check: "Extension source valid"
status: "pass"
})
} else {
$results = ($results | append {
check: "Extension source valid"
status: "fail"
message: $"Invalid source: ($extensions.source)"
})
}
}
$results
}
def show-mode-next-steps [mode: string] -> nothing {
print $"\n✓ Switched to '($mode)' mode\n"
match $mode {
"solo" => {
print "Next steps:"
print " 1. Start orchestrator: cd provisioning/platform/orchestrator && ./scripts/start-orchestrator.nu"
print " 2. (Optional) Start OCI registry: provisioning mode oci-registry start"
print " 3. Begin development: provisioning server create --check"
}
"multi-user" => {
print "Next steps:"
print " 1. Authenticate: provisioning auth login"
print " 2. Lock workspace: provisioning workspace lock"
print " 3. Pull extensions from OCI: provisioning extension pull"
print " 4. Check quota: provisioning quota show"
}
"cicd" => {
print "Next steps:"
print " 1. Ensure CI/CD environment variables are set"
print " 2. Token must be available at: /var/run/secrets/provisioning/token"
print " 3. Run validation: provisioning validate --all"
print " 4. Execute pipeline: provisioning server create --check"
}
"enterprise" => {
print "Next steps:"
print " 1. Ensure mTLS certificates are provisioned"
print " 2. Verify Kubernetes connectivity: kubectl get pods -n provisioning-system"
print " 3. Check Harbor access: docker login harbor.enterprise.local"
print " 4. Review compliance policies: provisioning policy list"
print " 5. Request workspace approval: provisioning workspace request"
}
}
}
def start-oci-registry [config: record] -> nothing {
let oci_config = $config.services.oci-registry
let data_dir = $oci_config.data_dir | str replace "~" $env.HOME
# Create data directory if not exists
if not ($data_dir | path exists) {
mkdir $data_dir
log-info $"Created OCI registry data directory: ($data_dir)"
}
# Check if already running
let status_check = (do {
http get $"http://($oci_config.endpoint):($oci_config.port)/v2/"
} | complete)
if $status_check.exit_code == 0 {
log-warning "OCI registry already running"
return
}
# Start registry (implementation depends on registry type)
match $oci_config.type {
"zot" => { start-zot-registry $oci_config }
"distribution" => { start-distribution-registry $oci_config }
_ => {
log-error $"Unsupported registry type for local deployment: ($oci_config.type)"
}
}
}
def stop-oci-registry [config: record] -> nothing {
log-info "Stopping OCI registry..."
# Implementation depends on how registry was started
# Could use PID file, systemd, or Docker
}
def oci-registry-status [config: record] -> nothing {
let oci_config = $config.services.oci-registry
let status_check = (do {
http get $"http://($oci_config.endpoint):($oci_config.port)/v2/"
} | complete)
if $status_check.exit_code == 0 {
print "OCI Registry: Running"
print $" Endpoint: ($oci_config.endpoint):($oci_config.port)"
print $" Type: ($oci_config.type)"
} else {
print "OCI Registry: Not running"
}
}
def oci-registry-logs [config: record] -> nothing {
let log_file = $config.services.oci-registry.log_file? | default (
$env.HOME | path join ".provisioning" "oci-registry" "registry.log"
)
if ($log_file | path exists) {
open $log_file
} else {
log-warning $"Log file not found: ($log_file)"
}
}
def start-zot-registry [config: record] -> nothing {
log-info "Starting Zot OCI registry..."
# Implementation would start Zot binary with config
}
def start-distribution-registry [config: record] -> nothing {
log-info "Starting Docker Distribution registry..."
# Implementation would start distribution binary
}