593 lines
18 KiB
Plaintext
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
|
|
}
|