provisioning/tools/workspace-init.nu

962 lines
34 KiB
Plaintext
Raw Normal View History

2025-10-07 11:12:02 +01:00
#!/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 {
[]
})
}
}