chore: add current provisioning state before migration
This commit is contained in:
parent
a9703b4748
commit
50745b0f22
660 changed files with 88126 additions and 0 deletions
135
core/nulib/lib_provisioning/extensions/loader.nu
Normal file
135
core/nulib/lib_provisioning/extensions/loader.nu
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
# Extension Loader
|
||||
# Discovers and loads extensions from multiple sources
|
||||
|
||||
# Extension discovery paths in priority order
|
||||
export def get-extension-paths []: nothing -> list<string> {
|
||||
[
|
||||
# Project-specific extensions (highest priority)
|
||||
($env.PWD | path join ".provisioning" "extensions")
|
||||
# User extensions
|
||||
($env.HOME | path join ".provisioning-extensions")
|
||||
# System-wide extensions
|
||||
"/opt/provisioning-extensions"
|
||||
# Environment variable override
|
||||
($env.PROVISIONING_EXTENSIONS_PATH? | default "")
|
||||
] | where ($it | is-not-empty) | where ($it | path exists)
|
||||
}
|
||||
|
||||
# Load extension manifest
|
||||
export def load-manifest [extension_path: string]: nothing -> record {
|
||||
let manifest_file = ($extension_path | path join "manifest.yaml")
|
||||
if ($manifest_file | path exists) {
|
||||
open $manifest_file
|
||||
} else {
|
||||
{
|
||||
name: ($extension_path | path basename)
|
||||
version: "1.0.0"
|
||||
type: "unknown"
|
||||
requires: []
|
||||
permissions: []
|
||||
hooks: {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if extension is allowed
|
||||
export def is-extension-allowed [manifest: record]: nothing -> bool {
|
||||
let mode = ($env.PROVISIONING_EXTENSION_MODE? | default "full")
|
||||
let allowed = ($env.PROVISIONING_ALLOWED_EXTENSIONS? | default "" | split row "," | each { str trim })
|
||||
let blocked = ($env.PROVISIONING_BLOCKED_EXTENSIONS? | default "" | split row "," | each { str trim })
|
||||
|
||||
match $mode {
|
||||
"disabled" => false,
|
||||
"restricted" => {
|
||||
if ($blocked | any {|x| $x == $manifest.name}) {
|
||||
false
|
||||
} else if ($allowed | is-empty) {
|
||||
true
|
||||
} else {
|
||||
($allowed | any {|x| $x == $manifest.name})
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
not ($blocked | any {|x| $x == $manifest.name})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Discover providers in extension paths
|
||||
export def discover-providers []: nothing -> table {
|
||||
get-extension-paths | each {|ext_path|
|
||||
let providers_path = ($ext_path | path join "providers")
|
||||
if ($providers_path | path exists) {
|
||||
glob ($providers_path | path join "*")
|
||||
| where ($it | path type) == "dir"
|
||||
| each {|provider_path|
|
||||
let manifest = (load-manifest $provider_path)
|
||||
if (is-extension-allowed $manifest) and $manifest.type == "provider" {
|
||||
{
|
||||
name: ($provider_path | path basename)
|
||||
path: $provider_path
|
||||
manifest: $manifest
|
||||
source: $ext_path
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
| where ($it != null)
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
} | flatten
|
||||
}
|
||||
|
||||
# Discover taskservs in extension paths
|
||||
export def discover-taskservs []: nothing -> table {
|
||||
get-extension-paths | each {|ext_path|
|
||||
let taskservs_path = ($ext_path | path join "taskservs")
|
||||
if ($taskservs_path | path exists) {
|
||||
glob ($taskservs_path | path join "*")
|
||||
| where ($it | path type) == "dir"
|
||||
| each {|taskserv_path|
|
||||
let manifest = (load-manifest $taskserv_path)
|
||||
if (is-extension-allowed $manifest) and $manifest.type == "taskserv" {
|
||||
{
|
||||
name: ($taskserv_path | path basename)
|
||||
path: $taskserv_path
|
||||
manifest: $manifest
|
||||
source: $ext_path
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
| where ($it != null)
|
||||
} else {
|
||||
[]
|
||||
}
|
||||
} | flatten
|
||||
}
|
||||
|
||||
# Check extension requirements
|
||||
export def check-requirements [manifest: record]: nothing -> bool {
|
||||
if ($manifest.requires | is-empty) {
|
||||
true
|
||||
} else {
|
||||
$manifest.requires | all {|req|
|
||||
(which $req | length) > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Load extension hooks
|
||||
export def load-hooks [extension_path: string, manifest: record]: nothing -> record {
|
||||
if ($manifest.hooks | is-not-empty) {
|
||||
$manifest.hooks | items {|key, value|
|
||||
let hook_file = ($extension_path | path join $value)
|
||||
if ($hook_file | path exists) {
|
||||
{key: $key, value: $hook_file}
|
||||
}
|
||||
} | reduce --fold {} {|it, acc| $acc | insert $it.key $it.value}
|
||||
} else {
|
||||
{}
|
||||
}
|
||||
}
|
||||
6
core/nulib/lib_provisioning/extensions/mod.nu
Normal file
6
core/nulib/lib_provisioning/extensions/mod.nu
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
# Extensions Module
|
||||
# Provides extension system functionality
|
||||
|
||||
export use loader.nu *
|
||||
export use registry.nu *
|
||||
export use profiles.nu *
|
||||
223
core/nulib/lib_provisioning/extensions/profiles.nu
Normal file
223
core/nulib/lib_provisioning/extensions/profiles.nu
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
# Profile-based Access Control
|
||||
# Implements permission system for restricted environments like CI/CD
|
||||
|
||||
# Load profile configuration
|
||||
export def load-profile [profile_name?: string]: nothing -> record {
|
||||
let active_profile = if ($profile_name | is-not-empty) {
|
||||
$profile_name
|
||||
} else {
|
||||
$env.PROVISIONING_PROFILE? | default ""
|
||||
}
|
||||
|
||||
if ($active_profile | is-empty) {
|
||||
return {
|
||||
name: "default"
|
||||
allowed: {
|
||||
commands: []
|
||||
providers: []
|
||||
taskservs: []
|
||||
}
|
||||
blocked: {
|
||||
commands: []
|
||||
providers: []
|
||||
taskservs: []
|
||||
}
|
||||
restricted: false
|
||||
}
|
||||
}
|
||||
|
||||
# Check user profile first
|
||||
let user_profile_path = ($env.HOME | path join ".provisioning-extensions" "profiles" $"($active_profile).yaml")
|
||||
let system_profile_path = ("/opt/provisioning-extensions/profiles" | path join $"($active_profile).yaml")
|
||||
let project_profile_path = ($env.PWD | path join ".provisioning" "profiles" $"($active_profile).yaml")
|
||||
|
||||
# Load in priority order: project > user > system
|
||||
let available_files = [
|
||||
$project_profile_path
|
||||
$user_profile_path
|
||||
$system_profile_path
|
||||
] | where ($it | path exists)
|
||||
|
||||
if ($available_files | length) > 0 {
|
||||
open ($available_files | first)
|
||||
} else {
|
||||
# Default restricted profile
|
||||
{
|
||||
name: $active_profile
|
||||
allowed: {
|
||||
commands: ["list", "status", "show", "query", "help", "version"]
|
||||
providers: ["local"]
|
||||
taskservs: []
|
||||
}
|
||||
blocked: {
|
||||
commands: ["delete", "create", "sops", "secrets"]
|
||||
providers: ["aws", "upcloud"]
|
||||
taskservs: []
|
||||
}
|
||||
restricted: true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if command is allowed
|
||||
export def is-command-allowed [command: string, subcommand?: string]: nothing -> bool {
|
||||
let profile = (load-profile)
|
||||
|
||||
if not $profile.restricted {
|
||||
return true
|
||||
}
|
||||
|
||||
let full_command = if ($subcommand | is-not-empty) {
|
||||
$"($command) ($subcommand)"
|
||||
} else {
|
||||
$command
|
||||
}
|
||||
|
||||
# Check blocked first
|
||||
if ($profile.blocked.commands | any {|cmd| $full_command =~ $cmd}) {
|
||||
return false
|
||||
}
|
||||
|
||||
# If allowed list is empty, allow everything not blocked
|
||||
if ($profile.allowed.commands | is-empty) {
|
||||
return true
|
||||
}
|
||||
|
||||
# Check if explicitly allowed
|
||||
($profile.allowed.commands | any {|cmd| $full_command =~ $cmd})
|
||||
}
|
||||
|
||||
# Check if provider is allowed
|
||||
export def is-provider-allowed [provider: string]: nothing -> bool {
|
||||
let profile = (load-profile)
|
||||
|
||||
if not $profile.restricted {
|
||||
return true
|
||||
}
|
||||
|
||||
# Check blocked first
|
||||
if ($profile.blocked.providers | any {|prov| $provider == $prov}) {
|
||||
return false
|
||||
}
|
||||
|
||||
# If allowed list is empty, allow everything not blocked
|
||||
if ($profile.allowed.providers | is-empty) {
|
||||
return true
|
||||
}
|
||||
|
||||
# Check if explicitly allowed
|
||||
($profile.allowed.providers | any {|prov| $provider == $prov})
|
||||
}
|
||||
|
||||
# Check if taskserv is allowed
|
||||
export def is-taskserv-allowed [taskserv: string]: nothing -> bool {
|
||||
let profile = (load-profile)
|
||||
|
||||
if not $profile.restricted {
|
||||
return true
|
||||
}
|
||||
|
||||
# Check blocked first
|
||||
if ($profile.blocked.taskservs | any {|ts| $taskserv == $ts}) {
|
||||
return false
|
||||
}
|
||||
|
||||
# If allowed list is empty, allow everything not blocked
|
||||
if ($profile.allowed.taskservs | is-empty) {
|
||||
return true
|
||||
}
|
||||
|
||||
# Check if explicitly allowed
|
||||
($profile.allowed.taskservs | any {|ts| $taskserv == $ts})
|
||||
}
|
||||
|
||||
# Enforce profile restrictions on command execution
|
||||
export def enforce-profile [command: string, subcommand?: string, target?: string]: nothing -> bool {
|
||||
if not (is-command-allowed $command $subcommand) {
|
||||
print $"🛑 Command '($command) ($subcommand | default "")' is not allowed by profile ($env.PROVISIONING_PROFILE)"
|
||||
return false
|
||||
}
|
||||
|
||||
# Additional checks based on target type
|
||||
if ($target | is-not-empty) {
|
||||
match $command {
|
||||
"server" => {
|
||||
if ($subcommand | default "") in ["create", "delete"] {
|
||||
let settings = (find_get_settings)
|
||||
let server = ($settings.data.servers | where hostname == $target | first?)
|
||||
if ($server | is-not-empty) {
|
||||
if not (is-provider-allowed $server.provider) {
|
||||
print $"🛑 Provider '($server.provider)' is not allowed by profile"
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"taskserv" => {
|
||||
if not (is-taskserv-allowed $target) {
|
||||
print $"🛑 TaskServ '($target)' is not allowed by profile"
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
# Show current profile information
|
||||
export def show-profile []: nothing -> record {
|
||||
let profile = (load-profile)
|
||||
{
|
||||
active_profile: ($env.PROVISIONING_PROFILE? | default "default")
|
||||
extension_mode: ($env.PROVISIONING_EXTENSION_MODE? | default "full")
|
||||
profile_config: $profile
|
||||
status: (if $profile.restricted { "restricted" } else { "unrestricted" })
|
||||
}
|
||||
}
|
||||
|
||||
# Create example profile files
|
||||
export def create-example-profiles []: nothing -> nothing {
|
||||
let user_profiles_dir = ($env.HOME | path join ".provisioning-extensions" "profiles")
|
||||
mkdir $user_profiles_dir
|
||||
|
||||
# CI/CD profile
|
||||
let cicd_profile = {
|
||||
profile: "cicd"
|
||||
description: "Restricted profile for CI/CD agents"
|
||||
restricted: true
|
||||
allowed: {
|
||||
commands: ["server list", "server status", "taskserv list", "taskserv status", "query", "show", "help", "version"]
|
||||
providers: ["local"]
|
||||
taskservs: ["kubernetes", "containerd", "kubectl"]
|
||||
}
|
||||
blocked: {
|
||||
commands: ["server create", "server delete", "taskserv create", "taskserv delete", "sops", "secrets"]
|
||||
providers: ["aws", "upcloud"]
|
||||
taskservs: ["postgres", "gitea"]
|
||||
}
|
||||
}
|
||||
|
||||
# Developer profile
|
||||
let developer_profile = {
|
||||
profile: "developer"
|
||||
description: "Profile for developers with limited production access"
|
||||
restricted: true
|
||||
allowed: {
|
||||
commands: ["server list", "server create", "taskserv list", "taskserv create", "query", "show"]
|
||||
providers: ["local", "aws"]
|
||||
taskservs: []
|
||||
}
|
||||
blocked: {
|
||||
commands: ["server delete", "sops"]
|
||||
providers: ["upcloud"]
|
||||
taskservs: ["postgres"]
|
||||
}
|
||||
}
|
||||
|
||||
# Save example profiles
|
||||
$cicd_profile | to yaml | save ($user_profiles_dir | path join "cicd.yaml")
|
||||
$developer_profile | to yaml | save ($user_profiles_dir | path join "developer.yaml")
|
||||
|
||||
print $"Created example profiles in ($user_profiles_dir)"
|
||||
}
|
||||
237
core/nulib/lib_provisioning/extensions/registry.nu
Normal file
237
core/nulib/lib_provisioning/extensions/registry.nu
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
# Extension Registry
|
||||
# Manages registration and lookup of providers, taskservs, and hooks
|
||||
|
||||
use loader.nu *
|
||||
|
||||
# Get default extension registry
|
||||
export def get-default-registry []: nothing -> record {
|
||||
{
|
||||
providers: {},
|
||||
taskservs: {},
|
||||
hooks: {
|
||||
pre_server_create: [],
|
||||
post_server_create: [],
|
||||
pre_server_delete: [],
|
||||
post_server_delete: [],
|
||||
pre_taskserv_install: [],
|
||||
post_taskserv_install: [],
|
||||
pre_taskserv_delete: [],
|
||||
post_taskserv_delete: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Get registry cache file path
|
||||
def get-registry-cache-file []: nothing -> string {
|
||||
let cache_dir = ($env.HOME | path join ".cache" "provisioning")
|
||||
if not ($cache_dir | path exists) {
|
||||
mkdir $cache_dir
|
||||
}
|
||||
$cache_dir | path join "extension-registry.json"
|
||||
}
|
||||
|
||||
# Load registry from cache or initialize
|
||||
export def load-registry []: nothing -> record {
|
||||
let cache_file = (get-registry-cache-file)
|
||||
if ($cache_file | path exists) {
|
||||
open $cache_file
|
||||
} else {
|
||||
get-default-registry
|
||||
}
|
||||
}
|
||||
|
||||
# Save registry to cache
|
||||
export def save-registry [registry: record]: nothing -> nothing {
|
||||
let cache_file = (get-registry-cache-file)
|
||||
$registry | to json | save -f $cache_file
|
||||
}
|
||||
|
||||
# Initialize extension registry
|
||||
export def init-registry []: nothing -> nothing {
|
||||
# Load all discovered extensions
|
||||
let providers = (discover-providers)
|
||||
let taskservs = (discover-taskservs)
|
||||
|
||||
# Build provider entries
|
||||
let provider_entries = ($providers | reduce -f {} {|provider, acc|
|
||||
let provider_entry = {
|
||||
name: $provider.name
|
||||
path: $provider.path
|
||||
manifest: $provider.manifest
|
||||
entry_point: ($provider.path | path join "nulib" $provider.name)
|
||||
available: ($provider.path | path join "nulib" $provider.name | path exists)
|
||||
}
|
||||
|
||||
if $provider_entry.available {
|
||||
$acc | insert $provider.name $provider_entry
|
||||
} else {
|
||||
$acc
|
||||
}
|
||||
})
|
||||
|
||||
# Build taskserv entries
|
||||
let taskserv_entries = ($taskservs | reduce -f {} {|taskserv, acc|
|
||||
let taskserv_entry = {
|
||||
name: $taskserv.name
|
||||
path: $taskserv.path
|
||||
manifest: $taskserv.manifest
|
||||
profiles: (glob ($taskserv.path | path join "*") | where ($it | path type) == "dir" | each { path basename })
|
||||
available: true
|
||||
}
|
||||
|
||||
$acc | insert $taskserv.name $taskserv_entry
|
||||
})
|
||||
|
||||
# Build hooks (simplified for now)
|
||||
let hook_entries = (get-default-registry).hooks
|
||||
|
||||
# Build final registry
|
||||
let registry = {
|
||||
providers: $provider_entries
|
||||
taskservs: $taskserv_entries
|
||||
hooks: $hook_entries
|
||||
}
|
||||
|
||||
# Save registry to cache
|
||||
save-registry $registry
|
||||
}
|
||||
|
||||
# Register a provider
|
||||
export def --env register-provider [name: string, path: string, manifest: record]: nothing -> nothing {
|
||||
let provider_entry = {
|
||||
name: $name
|
||||
path: $path
|
||||
manifest: $manifest
|
||||
entry_point: ($path | path join "nulib" $name)
|
||||
available: ($path | path join "nulib" $name | path exists)
|
||||
}
|
||||
|
||||
if $provider_entry.available {
|
||||
let current_registry = ($env.EXTENSION_REGISTRY? | default (get-default-registry))
|
||||
$env.EXTENSION_REGISTRY = ($current_registry
|
||||
| update providers ($current_registry.providers | insert $name $provider_entry))
|
||||
}
|
||||
}
|
||||
|
||||
# Register a taskserv
|
||||
export def --env register-taskserv [name: string, path: string, manifest: record]: nothing -> nothing {
|
||||
let taskserv_entry = {
|
||||
name: $name
|
||||
path: $path
|
||||
manifest: $manifest
|
||||
profiles: (glob ($path | path join "*") | where ($it | path type) == "dir" | each { path basename })
|
||||
available: true
|
||||
}
|
||||
|
||||
let current_registry = ($env.EXTENSION_REGISTRY? | default (get-default-registry))
|
||||
$env.EXTENSION_REGISTRY = ($current_registry
|
||||
| update taskservs ($current_registry.taskservs | insert $name $taskserv_entry))
|
||||
}
|
||||
|
||||
# Register a hook
|
||||
export def --env register-hook [hook_type: string, hook_path: string, extension_name: string]: nothing -> nothing {
|
||||
let hook_entry = {
|
||||
path: $hook_path
|
||||
extension: $extension_name
|
||||
enabled: true
|
||||
}
|
||||
|
||||
let current_registry = ($env.EXTENSION_REGISTRY? | default (get-default-registry))
|
||||
let current_hooks = ($current_registry.hooks? | get -o $hook_type | default [])
|
||||
$env.EXTENSION_REGISTRY = ($current_registry
|
||||
| update hooks ($current_registry.hooks? | default (get-default-registry).hooks
|
||||
| update $hook_type ($current_hooks | append $hook_entry)))
|
||||
}
|
||||
|
||||
# Get registered provider
|
||||
export def get-provider [name: string]: nothing -> record {
|
||||
let registry = (load-registry)
|
||||
$registry.providers | get -o $name | default {}
|
||||
}
|
||||
|
||||
# List all registered providers
|
||||
export def list-providers []: nothing -> table {
|
||||
let registry = (load-registry)
|
||||
$registry.providers | items {|name, provider|
|
||||
{
|
||||
name: $name
|
||||
path: $provider.path
|
||||
version: $provider.manifest.version
|
||||
available: $provider.available
|
||||
source: ($provider.path | str replace $env.HOME "~")
|
||||
}
|
||||
} | flatten
|
||||
}
|
||||
|
||||
# Get registered taskserv
|
||||
export def get-taskserv [name: string]: nothing -> record {
|
||||
let registry = (load-registry)
|
||||
$registry.taskservs | get -o $name | default {}
|
||||
}
|
||||
|
||||
# List all registered taskservs
|
||||
export def list-taskservs []: nothing -> table {
|
||||
let registry = (load-registry)
|
||||
$registry.taskservs | items {|name, taskserv|
|
||||
{
|
||||
name: $name
|
||||
path: $taskserv.path
|
||||
version: $taskserv.manifest.version
|
||||
profiles: ($taskserv.profiles | str join ", ")
|
||||
source: ($taskserv.path | str replace $env.HOME "~")
|
||||
}
|
||||
} | flatten
|
||||
}
|
||||
|
||||
# Execute hooks
|
||||
export def execute-hooks [hook_type: string, context: record]: nothing -> list {
|
||||
let registry = (load-registry)
|
||||
let hooks = ($registry.hooks? | get -o $hook_type | default [])
|
||||
$hooks | where enabled | each {|hook|
|
||||
let result = (do { nu $hook.path ($context | to json) } | complete)
|
||||
if $result.exit_code == 0 {
|
||||
{
|
||||
hook: $hook.path
|
||||
extension: $hook.extension
|
||||
output: $result.stdout
|
||||
success: true
|
||||
}
|
||||
} else {
|
||||
{
|
||||
hook: $hook.path
|
||||
extension: $hook.extension
|
||||
error: $result.stderr
|
||||
success: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check if provider exists (core or extension)
|
||||
export def provider-exists [name: string]: nothing -> bool {
|
||||
let core_providers = ["aws", "local", "upcloud"]
|
||||
($name in $core_providers) or ((get-provider $name) | is-not-empty)
|
||||
}
|
||||
|
||||
# Check if taskserv exists (core or extension)
|
||||
export def taskserv-exists [name: string]: nothing -> bool {
|
||||
let core_path = ($env.PROVISIONING_TASKSERVS_PATH | path join $name)
|
||||
let extension_taskserv = (get-taskserv $name)
|
||||
|
||||
($core_path | path exists) or ($extension_taskserv | is-not-empty)
|
||||
}
|
||||
|
||||
# Get taskserv path (core or extension)
|
||||
export def get-taskserv-path [name: string]: nothing -> string {
|
||||
let core_path = ($env.PROVISIONING_TASKSERVS_PATH | path join $name)
|
||||
if ($core_path | path exists) {
|
||||
$core_path
|
||||
} else {
|
||||
let extension_taskserv = (get-taskserv $name)
|
||||
if ($extension_taskserv | is-not-empty) {
|
||||
$extension_taskserv.path
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue