419 lines
12 KiB
Text
419 lines
12 KiB
Text
|
|
use tools/nickel/process.nu [ncl-eval-soft]
|
||
|
|
|
||
|
|
# Resolve the per-OS directory that holds provisioning's user-level state.
|
||
|
|
# Precedence:
|
||
|
|
# 1. $PROVISIONING_USER_CONFIG_DIR — explicit override (any OS)
|
||
|
|
# 2. macOS : $HOME/Library/Application Support/provisioning
|
||
|
|
# 3. Linux : $XDG_CONFIG_HOME/provisioning (falls back to $HOME/.config/provisioning)
|
||
|
|
# 4. Windows : $APPDATA/provisioning (falls back to $HOME/AppData/Roaming/provisioning)
|
||
|
|
# 5. Other : $HOME/.config/provisioning
|
||
|
|
export def get-user-config-dir []: nothing -> string {
|
||
|
|
let override = ($env.PROVISIONING_USER_CONFIG_DIR? | default "")
|
||
|
|
if ($override | is-not-empty) { return $override }
|
||
|
|
|
||
|
|
let home = ($env.HOME? | default "")
|
||
|
|
match $nu.os-info.name {
|
||
|
|
"macos" => ([$home "Library" "Application Support" "provisioning"] | path join)
|
||
|
|
"linux" => {
|
||
|
|
let xdg = ($env.XDG_CONFIG_HOME? | default "")
|
||
|
|
if ($xdg | is-not-empty) {
|
||
|
|
($xdg | path join "provisioning")
|
||
|
|
} else {
|
||
|
|
([$home ".config" "provisioning"] | path join)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
"windows" => {
|
||
|
|
let appdata = ($env.APPDATA? | default "")
|
||
|
|
if ($appdata | is-not-empty) {
|
||
|
|
($appdata | path join "provisioning")
|
||
|
|
} else {
|
||
|
|
([$home "AppData" "Roaming" "provisioning"] | path join)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_ => ([$home ".config" "provisioning"] | path join)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export def get-user-config-path []: nothing -> string {
|
||
|
|
let user_config_dir = (get-user-config-dir)
|
||
|
|
|
||
|
|
if not ($user_config_dir | path exists) {
|
||
|
|
mkdir $user_config_dir
|
||
|
|
}
|
||
|
|
|
||
|
|
$user_config_dir | path join "user_config.yaml"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Persist the provisioning repo root so Rust binaries can resolve Nickel schema
|
||
|
|
# imports without requiring $PROVISIONING in their environment. The file is read
|
||
|
|
# by platform-config::nickel::read_stored_provisioning_root as a fallback.
|
||
|
|
export def write-provisioning-root []: nothing -> nothing {
|
||
|
|
let provisioning = ($env.PROVISIONING? | default "")
|
||
|
|
if ($provisioning | is-empty) { return }
|
||
|
|
let data_dir = (get-user-config-dir)
|
||
|
|
if not ($data_dir | path exists) { ^mkdir -p $data_dir }
|
||
|
|
$provisioning | save --force ($data_dir | path join "project-root")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Resolve the per-OS directory that holds platform service configs
|
||
|
|
# (control-center, orchestrator, vault-service, mcp-server, etc.).
|
||
|
|
# Always a `platform` subdirectory under the user config dir.
|
||
|
|
export def get-platform-config-dir []: nothing -> string {
|
||
|
|
(get-user-config-dir) | path join "platform"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create-default-user-config-content [] {
|
||
|
|
{
|
||
|
|
active_workspace: null,
|
||
|
|
workspaces: [],
|
||
|
|
preferences: {
|
||
|
|
editor: "vim"
|
||
|
|
output_format: "yaml"
|
||
|
|
confirm_delete: true
|
||
|
|
confirm_deploy: true
|
||
|
|
default_log_level: "info"
|
||
|
|
preferred_provider: "local"
|
||
|
|
},
|
||
|
|
metadata: {
|
||
|
|
created: (date now | format date "%Y-%m-%dT%H:%M:%SZ"),
|
||
|
|
last_updated: (date now | format date "%Y-%m-%dT%H:%M:%SZ"),
|
||
|
|
version: "1.0.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export def load-user-config [] {
|
||
|
|
let config_dir = (get-user-config-dir)
|
||
|
|
let config_path_str = ($config_dir | path join "user_config.yaml")
|
||
|
|
|
||
|
|
let file_exists = ($config_path_str | path exists)
|
||
|
|
|
||
|
|
if not $file_exists {
|
||
|
|
if not ($config_dir | path exists) {
|
||
|
|
mkdir $config_dir
|
||
|
|
}
|
||
|
|
(create-default-user-config-content)
|
||
|
|
| to yaml
|
||
|
|
| save -f $config_path_str
|
||
|
|
return (create-default-user-config-content)
|
||
|
|
}
|
||
|
|
|
||
|
|
let file_type_result = ($config_path_str | path type)
|
||
|
|
if $file_type_result != "file" {
|
||
|
|
return-default-config "Config path is not a file"
|
||
|
|
}
|
||
|
|
|
||
|
|
let raw_content = (open -r $config_path_str)
|
||
|
|
if ($raw_content | is-empty) {
|
||
|
|
return (return-default-config "Empty config file")
|
||
|
|
}
|
||
|
|
let config = (do --ignore-errors { $raw_content | from yaml })
|
||
|
|
if ($config | describe) == "nothing" {
|
||
|
|
return (return-default-config "Malformed YAML in config file")
|
||
|
|
}
|
||
|
|
|
||
|
|
let defaults = (create-default-user-config-content)
|
||
|
|
|
||
|
|
return {
|
||
|
|
active_workspace: ($config.active_workspace? | default $defaults.active_workspace)
|
||
|
|
workspaces: ($config.workspaces? | default $defaults.workspaces)
|
||
|
|
preferences: (
|
||
|
|
($config.preferences? | default {})
|
||
|
|
| merge $defaults.preferences
|
||
|
|
)
|
||
|
|
metadata: ($config.metadata? | default $defaults.metadata)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
def return-default-config [reason: string] {
|
||
|
|
if ($env.PROVISIONING_DEBUG? | default false) {
|
||
|
|
print $"(ansi yellow)⚠ Using default config: ($reason)(ansi reset)" | debug
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
active_workspace: null,
|
||
|
|
workspaces: [],
|
||
|
|
preferences: {
|
||
|
|
editor: "vim"
|
||
|
|
output_format: "yaml"
|
||
|
|
confirm_delete: true
|
||
|
|
confirm_deploy: true
|
||
|
|
default_log_level: "info"
|
||
|
|
preferred_provider: "local"
|
||
|
|
},
|
||
|
|
metadata: {
|
||
|
|
created: (date now | format date "%Y-%m-%dT%H:%M:%SZ"),
|
||
|
|
last_updated: (date now | format date "%Y-%m-%dT%H:%M:%SZ"),
|
||
|
|
version: "1.0.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export def create-default-user-config [] {
|
||
|
|
let config_path = (get-user-config-path)
|
||
|
|
|
||
|
|
let default_config = {
|
||
|
|
active_workspace: null,
|
||
|
|
workspaces: [],
|
||
|
|
preferences: {
|
||
|
|
editor: "vim",
|
||
|
|
output_format: "yaml",
|
||
|
|
confirm_delete: true,
|
||
|
|
confirm_deploy: true,
|
||
|
|
default_log_level: "info",
|
||
|
|
preferred_provider: "local"
|
||
|
|
},
|
||
|
|
metadata: {
|
||
|
|
created: (date now | format date "%Y-%m-%dT%H:%M:%SZ"),
|
||
|
|
last_updated: (date now | format date "%Y-%m-%dT%H:%M:%SZ"),
|
||
|
|
version: "1.0.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$default_config | to yaml | save -f $config_path
|
||
|
|
}
|
||
|
|
|
||
|
|
export def save-user-config [config: record] {
|
||
|
|
let config_path = (get-user-config-path)
|
||
|
|
|
||
|
|
let updated_config = (
|
||
|
|
$config | update metadata.last_updated (date now | format date "%Y-%m-%dT%H:%M:%SZ")
|
||
|
|
)
|
||
|
|
|
||
|
|
$updated_config | to yaml | save -f $config_path
|
||
|
|
}
|
||
|
|
|
||
|
|
export def get-active-workspace [] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
if ($config.active_workspace == null) {
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
$config.active_workspace
|
||
|
|
}
|
||
|
|
|
||
|
|
export def get-active-workspace-details [] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
if ($config.active_workspace == null) {
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
let workspace_name = $config.active_workspace
|
||
|
|
|
||
|
|
let workspace = ($config.workspaces | where name == $workspace_name | first)
|
||
|
|
|
||
|
|
if ($workspace | is-empty) {
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
$workspace
|
||
|
|
}
|
||
|
|
|
||
|
|
export def set-active-workspace [
|
||
|
|
workspace_name: string
|
||
|
|
workspace_path: string
|
||
|
|
] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
let workspace_exists = ($config.workspaces | where name == $workspace_name | length) > 0
|
||
|
|
|
||
|
|
let updated_config = if $workspace_exists {
|
||
|
|
$config
|
||
|
|
| update workspaces ($config.workspaces | each { |ws|
|
||
|
|
if $ws.name == $workspace_name {
|
||
|
|
$ws | update last_used (date now | format date "%Y-%m-%dT%H:%M:%SZ")
|
||
|
|
} else {
|
||
|
|
$ws
|
||
|
|
}
|
||
|
|
})
|
||
|
|
| update active_workspace $workspace_name
|
||
|
|
} else {
|
||
|
|
let new_workspace = {
|
||
|
|
name: $workspace_name,
|
||
|
|
path: $workspace_path,
|
||
|
|
last_used: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
|
||
|
|
}
|
||
|
|
|
||
|
|
$config
|
||
|
|
| update workspaces ($config.workspaces | append $new_workspace)
|
||
|
|
| update active_workspace $workspace_name
|
||
|
|
}
|
||
|
|
|
||
|
|
save-user-config $updated_config
|
||
|
|
|
||
|
|
print $"(ansi green)✓(ansi reset) Workspace '($workspace_name)' activated"
|
||
|
|
}
|
||
|
|
|
||
|
|
export def list-workspaces [] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
if ($config.workspaces | is-empty) {
|
||
|
|
return []
|
||
|
|
}
|
||
|
|
|
||
|
|
let active_workspace = $config.active_workspace
|
||
|
|
|
||
|
|
mut workspace_list = []
|
||
|
|
for ws in $config.workspaces {
|
||
|
|
let is_active = ($ws.name == $active_workspace)
|
||
|
|
let ws_record = {
|
||
|
|
name: $ws.name
|
||
|
|
path: $ws.path
|
||
|
|
active: $is_active
|
||
|
|
last_used: $ws.last_used
|
||
|
|
}
|
||
|
|
$workspace_list = ($workspace_list | append $ws_record)
|
||
|
|
}
|
||
|
|
|
||
|
|
$workspace_list
|
||
|
|
}
|
||
|
|
|
||
|
|
export def remove-workspace [workspace_name: string] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
let updated_workspaces = ($config.workspaces | where name != $workspace_name)
|
||
|
|
|
||
|
|
let updated_config = $config | update workspaces $updated_workspaces
|
||
|
|
|
||
|
|
let final_config = if $config.active_workspace == $workspace_name {
|
||
|
|
$updated_config | update active_workspace null
|
||
|
|
} else {
|
||
|
|
$updated_config
|
||
|
|
}
|
||
|
|
|
||
|
|
save-user-config $final_config
|
||
|
|
|
||
|
|
print $"(ansi green)✓(ansi reset) Workspace '($workspace_name)' removed from known list"
|
||
|
|
}
|
||
|
|
|
||
|
|
export def register-workspace [
|
||
|
|
workspace_name: string
|
||
|
|
workspace_path: string
|
||
|
|
] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
let workspace_exists = ($config.workspaces | where name == $workspace_name | length) > 0
|
||
|
|
|
||
|
|
if $workspace_exists {
|
||
|
|
print $"(ansi yellow)⚠(ansi reset) Workspace '($workspace_name)' already registered"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
let new_workspace = {
|
||
|
|
name: $workspace_name,
|
||
|
|
path: $workspace_path,
|
||
|
|
last_used: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
|
||
|
|
}
|
||
|
|
|
||
|
|
let updated_config = $config | update workspaces ($config.workspaces | append $new_workspace)
|
||
|
|
|
||
|
|
save-user-config $updated_config
|
||
|
|
|
||
|
|
print $"(ansi green)✓(ansi reset) Workspace '($workspace_name)' registered"
|
||
|
|
}
|
||
|
|
|
||
|
|
export def get-user-preference [preference_key: string] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
if ($preference_key in $config.preferences) {
|
||
|
|
$config.preferences | get $preference_key
|
||
|
|
} else {
|
||
|
|
null
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export def set-user-preference [
|
||
|
|
preference_key: string
|
||
|
|
value: any
|
||
|
|
] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
let updated_config = $config | update preferences {
|
||
|
|
$config.preferences | upsert $preference_key $value
|
||
|
|
}
|
||
|
|
|
||
|
|
save-user-config $updated_config
|
||
|
|
|
||
|
|
print $"(ansi green)✓(ansi reset) Preference '($preference_key)' updated to '($value)'"
|
||
|
|
}
|
||
|
|
|
||
|
|
export def validate-workspace-exists [workspace_name: string] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
($config.workspaces | where name == $workspace_name | length) > 0
|
||
|
|
}
|
||
|
|
|
||
|
|
export def get-workspace-path [workspace_name: string] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
let workspace = ($config.workspaces | where name == $workspace_name | first)
|
||
|
|
|
||
|
|
if ($workspace | is-empty) {
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
$workspace.path
|
||
|
|
}
|
||
|
|
|
||
|
|
export def get-workspace-default-infra [workspace_name: string] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
let workspace = ($config.workspaces | where name == $workspace_name | first)
|
||
|
|
|
||
|
|
if ($workspace | is-empty) {
|
||
|
|
return null
|
||
|
|
}
|
||
|
|
|
||
|
|
let user_infra = ($workspace.default_infra? | default null)
|
||
|
|
if ($user_infra | is-not-empty) {
|
||
|
|
return $user_infra
|
||
|
|
}
|
||
|
|
|
||
|
|
let ws_path = (get-workspace-path $workspace_name)
|
||
|
|
let ws_config_file = ([$ws_path "config" "provisioning.ncl"] | path join)
|
||
|
|
if ($ws_config_file | path exists) {
|
||
|
|
let result = (ncl-eval-soft $ws_config_file [] null)
|
||
|
|
if ($result | is-not-empty) {
|
||
|
|
let current_infra = ($result.current_infra? | default null)
|
||
|
|
if ($current_infra | is-not-empty) {
|
||
|
|
return $current_infra
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
null
|
||
|
|
}
|
||
|
|
|
||
|
|
export def set-workspace-default-infra [
|
||
|
|
workspace_name: string
|
||
|
|
infra_name: string
|
||
|
|
] {
|
||
|
|
let config = (load-user-config)
|
||
|
|
|
||
|
|
mut updated_workspaces = []
|
||
|
|
for ws in $config.workspaces {
|
||
|
|
if $ws.name == $workspace_name {
|
||
|
|
let updated_ws = {
|
||
|
|
name: $ws.name
|
||
|
|
path: $ws.path
|
||
|
|
last_used: (date now | format date "%Y-%m-%dT%H:%M:%SZ")
|
||
|
|
default_infra: $infra_name
|
||
|
|
}
|
||
|
|
$updated_workspaces = ($updated_workspaces | append $updated_ws)
|
||
|
|
} else {
|
||
|
|
$updated_workspaces = ($updated_workspaces | append $ws)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let updated_config = {
|
||
|
|
active_workspace: $config.active_workspace
|
||
|
|
workspaces: $updated_workspaces
|
||
|
|
preferences: $config.preferences
|
||
|
|
metadata: $config.metadata
|
||
|
|
}
|
||
|
|
|
||
|
|
save-user-config $updated_config
|
||
|
|
}
|