prvng_core/nulib/main_provisioning/ontoref-queries.nu
Jesús Pérez 894046ef5a
feat(core): three-layer DAG, unified component arch, commands-registry cache, Nushell 0.112.2 migration
- DAG architecture: `dag show/validate/export` (nulib/main_provisioning/dag.nu),
    config loader (lib_provisioning/config/loader/dag.nu), taskserv dag-executor.
    Backed by schemas/lib/dag/*.ncl; orchestrator emits NATS events via
    WorkspaceComposition::into_workflow. See ADR-020, ADR-021.
  - Unified Component Architecture: components/mod.nu, main_provisioning/
    {components,workflow,extensions,ontoref-queries}.nu. Full workflow engine with
    topological sort and NATS subject emission. Blocks A-H complete (libre-daoshi).
  - Commands-registry: nulib/commands-registry.ncl (Nickel source, 314 lines) +
    JSON cache at ~/.cache/provisioning/commands-registry.json rebuilt on source
    change. cli/provisioning fast-path alias expansion avoids cold Nu startup.
    ADDING_COMMANDS.md documents new-command workflow.
  - Platform service manager: service-manager.nu (+573), startup.nu (+611),
    service-check.nu (+255); autostart/bootstrap/health/target refactored.
  - Nushell 0.112.2 migration: removed all try/catch and bash redirections;
    external commands prefixed with ^; type signatures enforced. Driven by
    scripts/refactor-try-catch{,-simplified}.nu.
  - TTY stack: removed shlib/*-tty.sh; replaced by cli/tty-dispatch.sh,
    tty-filter.sh, tty-commands.conf.
  - New domain modules: images/ (golden image lifecycle), workspace/{state,sync}.nu,
    main_provisioning/{bootstrap,cluster-deploy,fip,state}.nu, commands/{state,
    build,integrations/auth,utilities/alias}.nu, platform.nu expanded (+874).
  - Config loader overhaul: loader/core.nu slimmed (-759), cache/core.nu
    refactored (-454), removed legacy loaders/file_loader.nu (-330).
  - Thirteen new provisioning-<domain>.nu top-level modules for bash dispatcher.
  - Tests: test_workspace_state.nu (+351); updates to test_oci_registry,
    test_services.
  - README + CHANGELOG updated.
2026-04-17 04:27:33 +01:00

325 lines
13 KiB
Text

use ../lib_provisioning/user/config.nu [get-workspace-path, get-active-workspace-details]
use ../lib_provisioning/utils/nickel_processor.nu [ncl-eval, ncl-eval-soft, default-ncl-paths]
# Resolve provisioning root from env with default fallback.
def oq-prov-root []: nothing -> string {
$env.PROVISIONING? | default "/usr/local/provisioning"
}
# Export a Nickel file as parsed JSON using workspace + provisioning import paths.
def oq-ncl-export [ws_root: string, full_path: string]: nothing -> record {
ncl-eval $full_path (default-ncl-paths $ws_root)
}
# Resolve workspace name from optional arg or active workspace.
def oq-resolve-ws [workspace: string]: nothing -> record {
let ws_name = if ($workspace | is-not-empty) {
$workspace
} else {
let details = (get-active-workspace-details)
if ($details == null) {
error make { msg: "No active workspace. Pass --workspace or activate one first." }
}
$details.name
}
let ws_root = (get-workspace-path $ws_name)
if ($ws_root | is-empty) or ($ws_root == null) {
error make { msg: $"Workspace '($ws_name)' not found in registry." }
}
{ name: $ws_name, root: $ws_root }
}
# Detect infra subdirectory: first dir under infra/ that contains settings.ncl.
def oq-detect-infra [ws_root: string]: nothing -> string {
let result = (do { ^bash -c $"ls -1d ($ws_root)/infra/*/settings.ncl 2>/dev/null | head -1" } | complete)
if $result.exit_code != 0 or ($result.stdout | str trim | is-empty) {
error make { msg: $"No infra/*/settings.ncl found under ($ws_root)" }
}
let parts = ($result.stdout | str trim | path split)
# path: ws_root/infra/<name>/settings.ncl — index -2 is infra name.
$parts | get ($parts | length | $in - 2)
}
# Collect all workflow *.ncl files under infra/{infra}/workflows/.
def oq-collect-workflows [ws_root: string]: nothing -> list {
let result = (do { ^bash -c $"ls ($ws_root)/infra/*/workflows/*.ncl 2>/dev/null" } | complete)
if $result.exit_code != 0 or ($result.stdout | str trim | is-empty) {
return []
}
$result.stdout | lines | where { $in | str trim | is-not-empty }
}
# Load settings.ncl components for the auto-detected infra.
def oq-load-components [ws_root: string]: nothing -> record {
let infra = (oq-detect-infra $ws_root)
let path = ($ws_root | path join "infra" $infra "settings.ncl")
if not ($path | path exists) {
return {}
}
let exported = (oq-ncl-export $ws_root $path)
$exported | get -o components | default {}
}
# Show the unified view of a component: config, FSM dimension state, and ontology consumers.
#
# Reads infra/{infra}/components/{name}.ncl for config, .ontology/state.ncl for dimension
# state, and .ontology/core.ncl for edges referencing this component.
export def "main describe component" [
name: string # Component name (e.g. postgresql, forgejo)
--workspace (-w): string # Workspace name (default: active)
] : nothing -> record {
let ws = (oq-resolve-ws $workspace)
let ws_root = $ws.root
let infra = (oq-detect-infra $ws_root)
let comp_path = ($ws_root | path join "infra" $infra "components" $"($name).ncl")
let settings_path = ($ws_root | path join "infra" $infra "settings.ncl")
# Component source config from its own NCL file.
let source_cfg = if ($comp_path | path exists) {
(oq-ncl-export $ws_root $comp_path) | get -o $name | default {}
} else if ($settings_path | path exists) {
let settings = (oq-ncl-export $ws_root $settings_path)
$settings | get -o components | default {} | get -o $name | default {}
} else {
{}
}
let mode = ($source_cfg | get -o mode | default "unknown" | into string | str replace "'" "")
let requires = ($source_cfg | get -o requires | default {})
let provides = ($source_cfg | get -o provides | default {})
let operations = ($source_cfg | get -o operations | default {})
# Extension path.
let prov_root = (oq-prov-root)
let ext_path = ($prov_root | path join "extensions/components" $name)
# FSM dimension: look for dimension id matching "{name}-status".
let state_path = ($ws_root | path join ".ontology" "state.ncl")
let fsm_state = if ($state_path | path exists) {
let state_data = (ncl-eval-soft $state_path (default-ncl-paths $ws_root) null)
if ($state_data | is-not-empty) {
let dims = ($state_data | get -o dimensions | default [])
let dim_id = $"($name)-status"
let dim = ($dims | where {|d| $d.id == $dim_id})
if ($dim | is-empty) {
{ dimension: null, current_state: "unknown", desired_state: "unknown" }
} else {
let d = ($dim | first)
{
dimension: $dim_id,
current_state: ($d | get -o current_state | default "unknown"),
desired_state: ($d | get -o desired_state | default "unknown"),
}
}
} else {
{ dimension: null, current_state: "unknown", desired_state: "unknown" }
}
} else {
{ dimension: null, current_state: "unknown", desired_state: "unknown" }
}
# Ontology consumers: edges in core.ncl that reference this component name.
let core_path = ($ws_root | path join ".ontology" "core.ncl")
let consumers = if ($core_path | path exists) {
let core_data = (ncl-eval-soft $core_path (default-ncl-paths $ws_root) null)
if ($core_data | is-not-empty) {
let edges = ($core_data | get -o edges | default [])
$edges | where {|e|
($e | get -o from | default "") == $name or ($e | get -o to | default "") == $name
} | each {|e| { from: ($e | get -o from | default ""), to: ($e | get -o to | default ""), kind: ($e | get -o kind | default "") }}
} else { [] }
} else { [] }
{
name: $name,
mode: $mode,
requires: $requires,
provides: $provides,
operations: $operations,
state: $fsm_state,
consumers: $consumers,
extension_path: $ext_path,
}
}
# List all components that expose database services.
#
# Filters components where provides.databases is non-empty, returning a flat table
# with one row per component.
export def "main describe databases" [
--workspace (-w): string # Workspace name (default: active)
] : nothing -> table {
let ws = (oq-resolve-ws $workspace)
let ws_root = $ws.root
let components = (oq-load-components $ws_root)
$components | columns | each {|comp_name|
let comp = ($components | get $comp_name)
let provides = ($comp | get -o provides | default {})
let databases = ($provides | get -o databases | default [])
if ($databases | is-not-empty) {
let port = ($provides | get -o port | default ($comp | get -o port | default 0))
let requires = ($comp | get -o requires | default {})
let ns_raw = ($comp | get -o namespace | default "default")
{
component: $comp_name,
databases: ($databases | str join ", "),
port: $port,
namespace: $ns_raw,
}
} else {
null
}
} | where { $in != null }
}
# List all components deployed to a specific Kubernetes namespace.
export def "main describe namespace" [
namespace: string # Kubernetes namespace to filter on
--workspace (-w): string # Workspace name (default: active)
] : nothing -> table {
let ws = (oq-resolve-ws $workspace)
let ws_root = $ws.root
let components = (oq-load-components $ws_root)
$components | columns | each {|comp_name|
let comp = ($components | get $comp_name)
let comp_ns = ($comp | get -o namespace | default "")
if $comp_ns == $namespace {
let mode_raw = ($comp | get -o mode | default "unknown" | into string | str replace "'" "")
let port = ($comp | get -o port | default ($comp | get -o requires | default {} | get -o ports | default [] | first | default {} | get -o port | default 0))
let image = ($comp | get -o image | default "")
{
component: $comp_name,
mode: $mode_raw,
port: $port,
image: $image,
}
} else {
null
}
} | where { $in != null }
}
# Show storage topology: available classes from capabilities.ncl and per-component requirements.
export def "main describe storage" [
--workspace (-w): string # Workspace name (default: active)
] : nothing -> record {
let ws = (oq-resolve-ws $workspace)
let ws_root = $ws.root
let infra = (oq-detect-infra $ws_root)
let prov_root = (oq-prov-root)
# Available storage classes from capabilities.ncl.
let caps_path = ($ws_root | path join "infra" $infra "capabilities.ncl")
let available_classes = if ($caps_path | path exists) {
ncl-eval-soft $caps_path [$ws_root $prov_root] {} | get -o provides | default {} | get -o storage_classes | default []
} else { [] }
# Per-component storage requirements.
let components = (oq-load-components $ws_root)
let component_requirements = ($components | columns | each {|comp_name|
let comp = ($components | get $comp_name)
let requires = ($comp | get -o requires | default {})
let storage = ($requires | get -o storage | default null)
if $storage != null {
{
component: $comp_name,
size: ($storage | get -o size | default ""),
storage_class: ($storage | get -o storage_class | default ($comp | get -o storage_class | default "")),
persistent: ($storage | get -o persistent | default false),
}
} else {
null
}
} | where { $in != null })
{
available_classes: $available_classes,
component_requirements: $component_requirements,
}
}
# Show a full workflow definition with FSM state and backlog references.
#
# Finds the workflow by id across all infra/*/workflows/*.ncl files and returns
# its steps, FSM dimension state, and any backlog_refs declared in metadata.
export def "main describe workflow" [
workflow_id: string # Workflow id to describe
--workspace (-w): string # Workspace name (default: active)
] : nothing -> record {
let ws = (oq-resolve-ws $workspace)
let ws_root = $ws.root
let prov_root = (oq-prov-root)
let wf_files = (oq-collect-workflows $ws_root)
if ($wf_files | is-empty) {
error make { msg: $"No workflow files found under ($ws_root)/infra/*/workflows/" }
}
mut wf_def = null
mut wf_meta = null
for wf_file in $wf_files {
let exported = (oq-ncl-export $ws_root $wf_file)
for key in ($exported | columns) {
let entry = ($exported | get $key)
if ($key | str ends-with "metadata") {
if ($entry | get -o id | default "") == $workflow_id {
$wf_meta = $entry
}
} else {
if ($entry | get -o id | default "") == $workflow_id {
$wf_def = $entry
}
}
}
if $wf_def != null { break }
}
if $wf_def == null {
error make { msg: $"Workflow '($workflow_id)' not found in any infra/*/workflows/*.ncl under ($ws_root)" }
}
# FSM dimension state.
let fsm_dim = if $wf_meta != null {
$wf_meta | get -o fsm_dimension | default ""
} else { "" }
let fsm_state = if ($fsm_dim | is-not-empty) {
let state_path = ($ws_root | path join ".ontology" "state.ncl")
if ($state_path | path exists) {
let state_data2 = (ncl-eval-soft $state_path (default-ncl-paths $ws_root) null)
if ($state_data2 | is-not-empty) {
let dims = ($state_data2 | get -o dimensions | default [])
let dim = ($dims | where {|d| $d.id == $fsm_dim})
if ($dim | is-empty) {
{ dimension: $fsm_dim, current_state: "unknown", desired_state: "unknown" }
} else {
let d = ($dim | first)
{
dimension: $fsm_dim,
current_state: ($d | get -o current_state | default "unknown"),
desired_state: ($d | get -o desired_state | default "unknown"),
}
}
} else {
{ dimension: $fsm_dim, current_state: "unknown", desired_state: "unknown" }
}
} else {
{ dimension: $fsm_dim, current_state: "unknown", desired_state: "unknown" }
}
} else {
{ dimension: null, current_state: "unknown", desired_state: "unknown" }
}
{
id: $workflow_id,
name: (if $wf_meta != null { $wf_meta | get -o name | default $workflow_id } else { $workflow_id }),
description: (if $wf_meta != null { $wf_meta | get -o description | default "" } else { $wf_def | get -o description | default "" }),
steps: ($wf_def | get -o steps | default []),
fsm_state: $fsm_state,
backlog_refs: (if $wf_meta != null { $wf_meta | get -o backlog_refs | default [] } else { [] }),
}
}