provisioning/tools/workspace-init.nu
2025-10-07 11:12:02 +01:00

962 lines
34 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# Workspace Initialization Tool
# Creates new workspace with proper directory structure for KCL package and module loader systems
use ../core/nulib/lib_provisioning/config/accessor.nu *
# Get dependency configuration (interactive or from flags)
def get-dependency-config [
workspace_path: string
dep_option?: string
dep_url?: string
]: nothing -> record {
# If dep_option provided, use it; otherwise prompt
let option = if ($dep_option | is-not-empty) {
$dep_option
} else {
print "\n📦 Provisioning Dependency Configuration"
print " Choose how to configure the provisioning package dependency:\n"
print " 1. workspace-home (default) - Install in workspace .kcl directory (self-contained)"
print " 2. home-package - Use ~/.kcl/packages/provisioning (shared across workspaces)"
print " 3. git-package - Use Git repository"
print " 4. publish-repo - Use KCL registry\n"
let choice = (input " Select option [1-4] (default: 1): ")
let normalized_choice = if ($choice | is-empty) { "1" } else { $choice }
match $normalized_choice {
"1" => "workspace-home",
"2" => "home-package",
"3" => "git-package",
"4" => "publish-repo",
"workspace-home" => "workspace-home",
"home-package" => "home-package",
"git-package" => "git-package",
"publish-repo" => "publish-repo",
_ => {
print $" ⚠️ Invalid choice '($normalized_choice)', using default: workspace-home"
"workspace-home"
}
}
}
# Handle each option
match $option {
"workspace-home" => {
print " ✓ Using workspace-local provisioning package"
# Always run install - it will check version and update if needed
install-workspace-provisioning $workspace_path
{
type: "workspace-home"
path: ".kcl/packages/provisioning"
version: "0.0.1"
}
},
"home-package" => {
print " ✓ Using home directory provisioning package"
# Always run install - it will check version and update if needed
install-home-provisioning
{
type: "home-package"
path: "~/.kcl/packages/provisioning"
version: "0.0.1"
}
},
"git-package" => {
let git_url = if ($dep_url | is-not-empty) {
$dep_url
} else {
input " Enter Git repository URL: "
}
print $" ✓ Using Git repository: ($git_url)"
{
type: "git-package"
git: $git_url
version: "0.0.1"
}
},
"publish-repo" => {
let repo_url = if ($dep_url | is-not-empty) {
$dep_url
} else {
input " Enter KCL registry URL (or leave empty for default): "
}
if ($repo_url | is-not-empty) {
print $" ✓ Using registry: ($repo_url)"
{
type: "publish-repo"
registry: $repo_url
version: "0.0.1"
}
} else {
print " ✓ Using default KCL registry"
{
type: "publish-repo"
version: "0.0.1"
}
}
},
_ => {
print $" ⚠️ Unknown option '($option)', using workspace-home"
{
type: "workspace-home"
path: ".kcl/packages/provisioning"
version: "0.0.1"
}
}
}
}
# Install provisioning package to workspace .kcl directory
def install-workspace-provisioning [workspace_path: string]: nothing -> nothing {
let workspace_abs = ($workspace_path | path expand)
let workspace_kcl_dir = ($workspace_abs | path join ".kcl" "packages")
let workspace_prov_dir = ($workspace_kcl_dir | path join "provisioning")
# Create .kcl/packages directory
mkdir $workspace_kcl_dir
# Determine source path for provisioning/kcl
# Try to find provisioning root
let script_dir = ($env.FILE_PWD? | default (pwd))
let provisioning_root = ($script_dir | path dirname | path dirname)
let kcl_source = ($provisioning_root | path join "kcl")
# Check if source exists
if not ($kcl_source | path exists) {
# Try alternative: check if PROVISIONING env var is set
let alt_source = try {
let config = (get-config)
let base_path = ($config.paths.base? | default "")
if ($base_path | is-not-empty) {
$base_path | path join "kcl"
} else {
""
}
} catch {
""
}
if ($alt_source | is-not-empty) and ($alt_source | path exists) {
# Check version and update if needed
if (check-and-update-package $workspace_prov_dir $alt_source) {
print $" 📋 Copying provisioning KCL package from: ($alt_source)"
cp -r $alt_source $workspace_prov_dir
}
} else {
error make {
msg: $"Cannot find provisioning KCL package. Tried: ($kcl_source), ($alt_source)"
}
}
} else {
# Check version and update if needed
if (check-and-update-package $workspace_prov_dir $kcl_source) {
print $" 📋 Copying provisioning KCL package from: ($kcl_source)"
cp -r $kcl_source $workspace_prov_dir
}
# Also create distribution package for sharing/reuse
build-distribution-package $provisioning_root $kcl_source
}
print $" ✅ Provisioning package ready at: ($workspace_prov_dir)"
}
# Install provisioning package to home .kcl directory
def install-home-provisioning []: nothing -> nothing {
let home_kcl_dir = ($env.HOME | path join ".kcl" "packages")
let home_prov_dir = ($home_kcl_dir | path join "provisioning")
# Create .kcl/packages directory
mkdir $home_kcl_dir
# Determine source path for provisioning/kcl
# Try to find provisioning root
let script_dir = ($env.FILE_PWD? | default (pwd))
let provisioning_root = ($script_dir | path dirname | path dirname)
let kcl_source = ($provisioning_root | path join "kcl")
# Check if source exists
if not ($kcl_source | path exists) {
# Try alternative: check if PROVISIONING env var is set
let alt_source = try {
let config = (get-config)
let base_path = ($config.paths.base? | default "")
if ($base_path | is-not-empty) {
$base_path | path join "kcl"
} else {
""
}
} catch {
""
}
if ($alt_source | is-not-empty) and ($alt_source | path exists) {
# Check version and update if needed
if (check-and-update-package $home_prov_dir $alt_source) {
print $" 📋 Copying provisioning KCL package from: ($alt_source)"
cp -r $alt_source $home_prov_dir
}
} else {
error make {
msg: $"Cannot find provisioning KCL package. Tried: ($kcl_source), ($alt_source)"
}
}
} else {
# Check version and update if needed
if (check-and-update-package $home_prov_dir $kcl_source) {
print $" 📋 Copying provisioning KCL package from: ($kcl_source)"
cp -r $kcl_source $home_prov_dir
}
# Also create distribution package for sharing/reuse
build-distribution-package $provisioning_root $kcl_source
}
print $" ✅ Provisioning package ready at: ($home_prov_dir)"
}
# Check package version and determine if update is needed
# Returns true if package should be copied/updated, false if already up to date
def check-and-update-package [
target_dir: string
source_dir: string
]: nothing -> bool {
# If target doesn't exist, needs installation
if not ($target_dir | path exists) {
print $" 📦 Installing new provisioning package..."
return true
}
# Get source version
let source_version = get-kcl-version $source_dir
# Get installed version
let installed_version = get-kcl-version $target_dir
# Compare versions
if $source_version != $installed_version {
print $" 🔄 Updating provisioning package: ($installed_version) → ($source_version)"
print $" 🗑️ Removing old version..."
rm -rf $target_dir
return true
} else {
print $" ✓ Provisioning package already up to date (v($installed_version))"
return false
}
}
# Get KCL package version from kcl.mod file
def get-kcl-version [kcl_path: string]: nothing -> string {
let kcl_mod = ($kcl_path | path join "kcl.mod")
if not ($kcl_mod | path exists) {
return "unknown"
}
try {
let mod_content = (open $kcl_mod | from toml)
$mod_content.package?.version? | default "unknown"
} catch {
"unknown"
}
}
# Build distribution package from KCL source
def build-distribution-package [provisioning_root: string, kcl_source: string]: nothing -> nothing {
let dist_dir = ($provisioning_root | path join "distribution" "packages")
let registry_dir = ($provisioning_root | path join "distribution" "registry")
# Create distribution directories
mkdir $dist_dir
mkdir $registry_dir
# Generate package info
let version = try {
let kcl_mod = ($kcl_source | path join "kcl.mod")
if ($kcl_mod | path exists) {
let mod_content = (open $kcl_mod | from toml)
$mod_content.package?.version? | default "0.0.1"
} else {
"0.0.1"
}
} catch {
"0.0.1"
}
let package_name = $"provisioning-kcl-($version).tar.gz"
let package_path = ($dist_dir | path join $package_name)
# Check if package already exists and is up to date
if ($package_path | path exists) {
let pkg_time = (ls $package_path | get 0.modified)
let src_time = (ls $kcl_source | sort-by modified -r | get 0.modified)
if $pkg_time > $src_time {
print $" Distribution package is up to date: ($package_name)"
return
}
}
print $" 📦 Building distribution package: ($package_name)"
# Create temporary staging directory
let staging_dir = ($dist_dir | path join ".staging")
mkdir $staging_dir
let staging_prov = ($staging_dir | path join "provisioning")
# Copy KCL files to staging
cp -r $kcl_source $staging_prov
# Create README for distribution
let readme_content = $"# Provisioning KCL Package
Version: ($version)
Generated: (date now | format date '%Y-%m-%d %H:%M:%S')
## Installation
### Option 1: Workspace-local installation
Copy this package to your workspace:
```bash
cp -r provisioning /path/to/workspace/.kcl/packages/
```
### Option 2: User-level installation
Install to your home directory:
```bash
mkdir -p ~/.kcl/packages
cp -r provisioning ~/.kcl/packages/
```
### Option 3: Using workspace init
When initializing a workspace, choose 'workspace-home' option:
```bash
provisioning ws init myworkspace
# Select option 1 \(workspace-home\)
```
## Usage
In your KCL files:
```kcl
import provisioning.settings as settings
import provisioning.server as server
import provisioning.defaults as defaults
```
## Contents
This package contains the core provisioning KCL schemas including:
- settings.k - Main settings schema
- server.k - Server configuration schema
- defaults.k - Default values and constants
- And other core schemas
For more information, visit the provisioning documentation.
"
$readme_content | save ($staging_dir | path join "README.md")
# Create package archive
try {
cd $staging_dir
tar czf $package_path provisioning README.md
cd $dist_dir # Go back before cleanup
print $" ✅ Created distribution package: ($package_path)"
# Update registry
update-package-registry $registry_dir $package_name $version $kcl_source
# Clean up staging
rm -rf $staging_dir
} catch {
print $" ⚠️ Warning: Could not create distribution package: ($in)"
# Try cleanup anyway
try { rm -rf $staging_dir } catch { }
}
}
# Update package registry with metadata
def update-package-registry [
registry_dir: string
package_name: string
version: string
source_path: string
]: nothing -> nothing {
let registry_file = ($registry_dir | path join "packages.json")
# Load existing registry or create new
let registry = if ($registry_file | path exists) {
open $registry_file | from json
} else {
{ packages: [] }
}
# Create package entry
let package_entry = {
name: "provisioning"
version: $version
package_file: $package_name
created_at: (date now | format date '%Y-%m-%d %H:%M:%S')
source_path: $source_path
type: "kcl-package"
description: "Core provisioning KCL schemas and modules"
}
# Update or add package entry
let updated_packages = (
$registry.packages
| where name != "provisioning" or version != $version
| append $package_entry
)
let updated_registry = { packages: $updated_packages }
# Save registry
$updated_registry | to json | save -f $registry_file
print $" 📋 Updated package registry: ($registry_file)"
}
# Load default modules to infrastructure layer
def load-default-modules [workspace_path: string, infra_path: string]: nothing -> nothing {
let workspace_abs = ($workspace_path | path expand)
let infra_abs = ($infra_path | path expand)
# Determine provisioning root and module loader script
let script_dir = ($env.FILE_PWD? | default (pwd))
let provisioning_root = ($script_dir | path dirname)
let module_loader = ($provisioning_root | path join "core" "cli" "module-loader")
# Verify module loader exists
if not ($module_loader | path exists) {
print $" ⚠️ Warning: Module loader not found at ($module_loader)"
print $" 💡 You can load modules manually: cd infra/<name> && provisioning mod load taskservs . os"
return
}
# Load os taskserv to infrastructure layer
try {
print $" 📦 Loading os taskserv to infrastructure layer..."
^nu $module_loader "load" "taskservs" $infra_abs "os"
print $" ✅ Loaded os taskserv"
} catch {
print $" ⚠️ Warning: Could not load os taskserv: ($in)"
print $" 💡 You can load it manually: cd ($infra_path) && provisioning mod load taskservs . os"
}
}
# Main workspace initialization command
def main [
workspace_path: string
--infra-name: string # Infrastructure name
--template: string # Template type: minimal, full, example
--workspace-type: string # Workspace type: development, staging, production
--user-name: string # User name for workspace
--taskservs: list<string> # Taskservs to load during init
--providers: list<string> # Providers to load during init
--clusters: list<string> # Clusters to load during init
--dep-option: string # Dependency option: home-package, git-package, publish-repo, workspace-home
--dep-url: string # Dependency URL for git-package or publish-repo
--overwrite # Overwrite existing workspace
--check # Check mode only (dry-run)
] {
# Validate workspace name - no hyphens allowed (causes KCL module resolution issues)
let workspace_name = ($workspace_path | path basename)
if ($workspace_name | str contains "-") {
print $"❌ Error: Workspace name cannot contain hyphens: '($workspace_name)'"
print ""
print " Hyphens in workspace names cause KCL module resolution issues."
print $" Use underscores instead: '($workspace_name | str replace -a '-' '_')'"
print ""
exit 1
}
# Validate infra name if provided - no hyphens allowed
if ($infra_name | is-not-empty) and ($infra_name | str contains "-") {
print $"❌ Error: Infrastructure name cannot contain hyphens: '($infra_name)'"
print ""
print " Hyphens in infrastructure names cause KCL module resolution issues."
print $" Use underscores instead: '($infra_name | str replace -a '-' '_')'"
print ""
exit 1
}
if $check {
let infra_dir_name = if ($infra_name | is-not-empty) { $infra_name } else { "main" }
let template_to_use = if ($template | is-not-empty) { $template } else { "minimal" }
print $"🔍 [CHECK MODE] Would initialize workspace: ($workspace_path)"
print $" Template: ($template_to_use)"
print $" Type: ($workspace_type | default 'development')"
print $" Infrastructure directory: infra/($infra_dir_name)"
print ""
print " Structure to create:"
print $" ($workspace_path)/"
print " ├── infra/"
print $" │ └── ($infra_dir_name)/"
print " │ ├── .taskservs/"
print " │ ├── .providers/"
print " │ ├── .clusters/"
print " │ ├── .manifest/"
print " │ ├── kcl.mod"
print " │ └── servers.k"
print " ├── data/"
print " ├── tmp/"
print " └── resources/"
if not ($taskservs | is-empty) {
print ""
print $" Taskservs to load: ($taskservs | str join ', ')"
}
if not ($providers | is-empty) {
print $" Providers to load: ($providers | str join ', ')"
}
if not ($clusters | is-empty) {
print $" Clusters to load: ($clusters | str join ', ')"
}
return
}
let workspace_abs = ($workspace_path | path expand)
# Check if workspace already exists
if ($workspace_abs | path exists) and not $overwrite {
print $"❌ Workspace already exists: ($workspace_path)"
print " Use --overwrite to overwrite existing workspace"
exit 1
}
print $"🚀 Initializing workspace: ($workspace_path)"
let infra_dir_name = if ($infra_name | is-not-empty) { $infra_name } else { "main" }
let template_to_use = if ($template | is-not-empty) { $template } else { "minimal" }
# Determine provisioning dependency configuration
let dep_config = get-dependency-config $workspace_abs $dep_option $dep_url
# Create workspace directory structure
let workspace_info = create-workspace-structure $workspace_path $infra_dir_name
# Create basic configuration files
create-workspace-config $workspace_path $workspace_info.infra_path $infra_dir_name $dep_config
# Create example files from template
create-workspace-examples $workspace_path $workspace_info.infra_path $infra_dir_name $template_to_use
# Load default os taskserv module to infrastructure
print ""
print "📦 Loading default modules..."
load-default-modules $workspace_path $workspace_info.infra_path
print ""
print $"✅ Workspace initialized successfully: ($workspace_path)"
print $"📁 Workspace structure created"
print $"📂 Infrastructure created: infra/($infra_dir_name)"
print $"📋 Template applied: ($template_to_use)"
print $"📄 Configuration files created"
print $"📦 Default modules loaded: os taskserv"
print ""
print "Next steps:"
print $" 1. cd ($workspace_path)/infra/($infra_dir_name)"
print " 2. Edit servers.k to customize your infrastructure"
print " 3. Load additional modules:"
print " - provisioning dt # Discover available taskservs"
print " - cd <workspace>/infra/<name> && provisioning mod load taskservs . <name>"
print $" 4. Deploy: provisioning s create --infra ($infra_dir_name)"
print ""
print "Quick start:"
print $" cd ($workspace_path)/infra/($infra_dir_name)"
print " kcl run servers.k # Validate configuration"
}
# Create workspace directory structure
def create-workspace-structure [
workspace_path: string
infra_name: string
]: nothing -> record {
let workspace_abs = ($workspace_path | path expand)
let infra_dir_name = if ($infra_name | is-not-empty) { $infra_name } else { "main" }
# Create workspace-level directories (non-infrastructure)
mkdir $workspace_abs
mkdir ($workspace_abs | path join "data")
mkdir ($workspace_abs | path join "tmp")
mkdir ($workspace_abs | path join "resources")
# Create Layer 2 (workspace-level) module directories
mkdir ($workspace_abs | path join ".taskservs")
mkdir ($workspace_abs | path join ".providers")
mkdir ($workspace_abs | path join ".clusters")
mkdir ($workspace_abs | path join ".manifest")
# Create infrastructure directory structure
let infra_path = ($workspace_abs | path join "infra" | path join $infra_dir_name)
mkdir $infra_path
# Create Layer 3 (infrastructure-level) module directories
mkdir ($infra_path | path join ".taskservs")
mkdir ($infra_path | path join ".providers")
mkdir ($infra_path | path join ".clusters")
mkdir ($infra_path | path join ".manifest")
# Create module-loader expected directories
mkdir ($infra_path | path join "taskservs")
mkdir ($infra_path | path join "overrides")
mkdir ($infra_path | path join "defs")
mkdir ($infra_path | path join "config")
{
path: $workspace_abs
infra_path: $infra_path
infra_name: $infra_dir_name
created: (date now | format date "%Y-%m-%d %H:%M:%S")
workspace_dirs: ["data", "tmp", "resources"]
workspace_modules: [".taskservs", ".providers", ".clusters", ".manifest"]
infra_dirs: [".taskservs", ".providers", ".clusters", ".manifest"]
}
}
# Create basic workspace configuration files
def create-workspace-config [
workspace_path: string
infra_path: string
infra_name: string
dep_config: record
]: nothing -> nothing {
let workspace_abs = ($workspace_path | path expand)
# Generate dependency line based on configuration
let dep_line = match $dep_config.type {
"workspace-home" => {
$"provisioning = \{ path = \"../../($dep_config.path)\", version = \"($dep_config.version)\" \}"
},
"home-package" => {
$"provisioning = \{ path = \"($dep_config.path)\", version = \"($dep_config.version)\" \}"
},
"git-package" => {
$"provisioning = \{ git = \"($dep_config.git)\", version = \"($dep_config.version)\" \}"
},
"publish-repo" => {
if ($dep_config.registry? | is-not-empty) {
$"provisioning = \{ registry = \"($dep_config.registry)\", version = \"($dep_config.version)\" \}"
} else {
$"provisioning = \{ version = \"($dep_config.version)\" \}"
}
},
_ => {
$"provisioning = \{ path = \"~/.kcl/packages/provisioning\", version = \"0.0.1\" \}"
}
}
# Create kcl.mod in infra directory
let kcl_mod_content = $"[package]
name = \"($infra_name)\"
edition = \"v0.11.3\"
version = \"0.0.1\"
[dependencies]
# Provisioning package dependency \(configured during workspace init\)
($dep_line)
"
$kcl_mod_content | save ($infra_path | path join "kcl.mod")
# Create .gitignore at workspace root
let base_gitignore = "# Workspace runtime data
data/
tmp/
*.log
# Infrastructure module directories (managed by module-loader)
infra/**/.taskservs/
infra/**/.providers/
infra/**/.clusters/
infra/**/.manifest/
# Generated files
infra/**/taskservs.k
infra/**/providers.k
infra/**/clusters.k
# Secrets and sensitive data
*.age
*.enc
*.key
"
# Add .kcl to gitignore if using workspace-home
let gitignore_content = if $dep_config.type == "workspace-home" {
$"($base_gitignore)
# Workspace-local KCL packages \(managed by workspace init\)
.kcl/
"
} else {
$base_gitignore
}
$gitignore_content | save ($workspace_abs | path join ".gitignore")
# Create empty manifest files in infra directory
let empty_manifest = {
loaded_taskservs: []
loaded_providers: []
loaded_clusters: []
last_updated: (date now | format date "%Y-%m-%d %H:%M:%S")
infra_path: $infra_path
}
$empty_manifest | to yaml | save ($infra_path | path join ".manifest" | path join "taskservs.yaml")
$empty_manifest | to yaml | save ($infra_path | path join ".manifest" | path join "providers.yaml")
$empty_manifest | to yaml | save ($infra_path | path join ".manifest" | path join "clusters.yaml")
}
# Load template file
def load-template [
template_name: string
file_name: string
]: nothing -> string {
# Try to find provisioning root
let provisioning_root = $env.PROVISIONING? | default "/usr/local/provisioning"
let template_path = ($provisioning_root | path join "templates" "workspace" $template_name $file_name)
if ($template_path | path exists) {
open $template_path
} else {
# Return empty string if template not found (will use fallback)
""
}
}
# Create example workspace files
def create-workspace-examples [
workspace_path: string
infra_path: string
infra_name: string
template_name: string = "minimal"
]: nothing -> nothing {
let workspace_abs = ($workspace_path | path expand)
# Load servers.k template
let servers_template = load-template $template_name "servers.k"
let servers_example = if ($servers_template | is-not-empty) {
$servers_template
} else {
# Fallback to minimal template if template not found
load-template "minimal" "servers.k"
}
$servers_example | save ($infra_path | path join "servers.k")
# Load template README if available
let template_readme = load-template $template_name "README.md"
# Create workspace README using simple string concatenation
let ws_name = ($workspace_path | path basename)
# Use simple string interpolation for README content
let readme_content = $"# Workspace: ($ws_name)
This workspace was initialized with the provisioning workspace system.
## Structure
```
($ws_name)/
├── .taskservs/ # Layer 2: Workspace-shared taskserv modules
├── .providers/ # Layer 2: Workspace-shared provider modules
├── .clusters/ # Layer 2: Workspace-shared cluster modules
├── .manifest/ # Layer 2: Workspace-level manifests
├── infra/ # Infrastructure definitions
│ └── ($infra_name)/ # Infrastructure: ($infra_name)
│ ├── .taskservs/ # Layer 3: Infrastructure-specific overrides
│ ├── .providers/ # Layer 3: Infrastructure-specific overrides
│ ├── .clusters/ # Layer 3: Infrastructure-specific overrides
│ ├── .manifest/ # Layer 3: Infrastructure-level manifests
│ ├── kcl.mod # KCL module configuration
│ └── servers.k # Server configuration
├── data/ # Workspace-level runtime data
├── tmp/ # Temporary files and deployment artifacts
└── resources/ # Workspace-level resources and templates
```
### 3-Layer Module Architecture
The workspace uses a 3-layer module resolution system:
1. **Layer 1 - System**: Global extensions in provisioning/extensions/
2. **Layer 2 - Workspace**: Shared modules in workspace .taskservs/, .providers/, .clusters/
3. **Layer 3 - Infrastructure**: Infrastructure-specific overrides in infra/<name>/.taskservs/, .providers/, .clusters/
**Resolution order**: Layer 3 → Layer 2 → Layer 1 - higher layers override lower
### Infrastructure Directory
Each infrastructure in infra/<name>/ can:
- Inherit shared modules from workspace layer -Layer 2-
- Override with infrastructure-specific versions -Layer 3-
- Define infrastructure-specific configuration -kcl.mod, servers.k-
## Usage
### 1. Navigate to Infrastructure Directory
Use cd to navigate to your infrastructure directory.
### 2. Load Required Modules
**Option A: Load at workspace level - shared across all infrastructures**
Load shared modules at workspace level using provisioning mod load with --level workspace flag.
**Option B: Load at infrastructure level - specific to one infrastructure**
Load infrastructure-specific modules using provisioning mod load with --level infra flag.
### 3. Verify Module Loading
Use provisioning mod list to verify loaded modules.
### 4. Configure Infrastructure
Edit servers.k in the infrastructure directory to define your requirements. The loaded modules will be available for import.
### 5. Deploy Infrastructure
Use kcl run to generate configuration and provisioning s create to deploy.
## Multiple Infrastructures
You can have multiple infrastructure configurations in one workspace by running workspace init with different --infra names.
## Module Management
- Use 'provisioning mod discover <type>' to find available modules
- Use 'provisioning mod load <type> <infra-path> <modules...>' to load modules
- Use 'provisioning mod unload <type> <infra-path> <module>' to remove modules
- Module manifests track all loaded modules in each infra/<name>/.manifest/
The module directories -.taskservs/, .providers/, .clusters/- are managed automatically and should not be edited manually.
"
# Combine workspace README with template README
let final_readme = if ($template_readme | is-not-empty) {
$"($readme_content)\n\n---\n\n($template_readme)"
} else {
$readme_content
}
$final_readme | save ($workspace_abs | path join "README.md")
# Also save template-specific README in infra directory if available
if ($template_readme | is-not-empty) {
$template_readme | save ($infra_path | path join "README.md")
}
}
# Initialize workspace with specific modules
export def "main init" [
workspace_path: string
--infra-name: string # Infrastructure name
--template: string # Template type: minimal, full, example
--workspace-type: string # Workspace type: development, staging, production
--user-name: string # User name for workspace
--taskservs: list<string> = [] # Taskservs to load during initialization
--providers: list<string> = [] # Providers to load during initialization
--clusters: list<string> = [] # Clusters to load during initialization
--dep-option: string # Dependency option: home-package, git-package, publish-repo, workspace-home
--dep-url: string # Dependency URL for git-package or publish-repo
--overwrite # Overwrite existing workspace
--check # Check mode only (dry-run)
]: nothing -> nothing {
# Create basic workspace first
main $workspace_path --infra-name $infra_name --template $template --workspace-type $workspace_type --user-name $user_name --taskservs $taskservs --providers $providers --clusters $clusters --dep-option $dep_option --dep-url $dep_url --overwrite=$overwrite --check=$check
# Skip module loading in check mode
if $check {
return
}
# Load specified modules
if not ($taskservs | is-empty) {
print $"Loading taskservs: ($taskservs | str join ', ')"
../core/cli/module-loader load taskservs $workspace_path $taskservs
}
if not ($providers | is-empty) {
print $"Loading providers: ($providers | str join ', ')"
../core/cli/module-loader load providers $workspace_path $providers
}
if not ($clusters | is-empty) {
print $"Loading clusters: ($clusters | str join ', ')"
../core/cli/module-loader load clusters $workspace_path $clusters
}
print $"✅ Workspace initialized with modules"
}
# Show workspace information
export def "main info" [workspace_path: string]: nothing -> record {
let workspace_abs = ($workspace_path | path expand)
if not ($workspace_abs | path exists) {
error make { msg: $"Workspace does not exist: ($workspace_abs)" }
}
let taskservs_manifest = try {
open ($workspace_abs | path join ".manifest" | path join "taskservs.yaml")
} catch { { loaded_taskservs: [] } }
let providers_manifest = try {
open ($workspace_abs | path join ".manifest" | path join "providers.yaml")
} catch { { loaded_providers: [] } }
let clusters_manifest = try {
open ($workspace_abs | path join ".manifest" | path join "clusters.yaml")
} catch { { loaded_clusters: [] } }
{
workspace: $workspace_abs
taskservs_count: ($taskservs_manifest.loaded_taskservs | length)
providers_count: ($providers_manifest.loaded_providers | length)
clusters_count: ($clusters_manifest.loaded_clusters | length)
taskservs: ($taskservs_manifest.loaded_taskservs | get name)
providers: ($providers_manifest.loaded_providers | get name)
clusters: ($clusters_manifest.loaded_clusters | get name)
}
}
# Validate workspace structure
export def "main validate" [workspace_path: string]: nothing -> record {
let workspace_abs = ($workspace_path | path expand)
let required_dirs = [".taskservs", ".providers", ".clusters", ".manifest"]
let required_files = ["kcl.mod", "servers.k"]
let missing_dirs = $required_dirs | where {|dir|
not (($workspace_abs | path join $dir) | path exists)
}
let missing_files = $required_files | where {|file|
not (($workspace_abs | path join $file) | path exists)
}
let is_valid = ($missing_dirs | is-empty) and ($missing_files | is-empty)
{
valid: $is_valid
workspace: $workspace_abs
missing_directories: $missing_dirs
missing_files: $missing_files
recommendations: (if not $is_valid {
["Run 'workspace-init.nu <path>' to fix workspace structure"]
} else {
[]
})
}
}