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//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 { [] }), } }