982 lines
30 KiB
Plaintext
982 lines
30 KiB
Plaintext
|
|
#!/usr/bin/env nu
|
||
|
|
|
||
|
|
# Extension Creation Tool (Updated for Modern Nushell & Grouped Structure)
|
||
|
|
# Creates new extensions from templates with variable substitution
|
||
|
|
|
||
|
|
use ../core/nulib/taskservs/discover.nu *
|
||
|
|
|
||
|
|
# Main extension creation command
|
||
|
|
def main [
|
||
|
|
type: string, # Extension type: taskserv, provider, cluster
|
||
|
|
name: string, # Extension name (kebab-case)
|
||
|
|
--template: string = "basic", # Template to use
|
||
|
|
--author: string = "Unknown", # Author name
|
||
|
|
--description: string = "", # Extension description
|
||
|
|
--category: string = "", # Category for taskservs (required for taskservs)
|
||
|
|
--port: int = 8080, # Default port for services
|
||
|
|
--output: string = "provisioning/extensions" # Output directory
|
||
|
|
] {
|
||
|
|
print $"🚀 Creating ($type) extension: ($name)"
|
||
|
|
|
||
|
|
# Validate extension type
|
||
|
|
if $type not-in ["taskserv", "provider", "cluster"] {
|
||
|
|
error make { msg: "Extension type must be one of: taskserv, provider, cluster" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate name format
|
||
|
|
if ($name | str contains " ") or ($name | str contains "_") or ($name != ($name | str downcase)) {
|
||
|
|
error make { msg: "Extension name must be in kebab-case format (e.g., my-service)" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# For taskservs, require category
|
||
|
|
if $type == "taskserv" and ($category | is-empty) {
|
||
|
|
error make { msg: "Category is required for taskservs. Use --category <category>" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create extension from template
|
||
|
|
let extension_info = create_extension_from_template $type $name $template $author $description $category $port $output
|
||
|
|
|
||
|
|
print $"✅ Extension created successfully:"
|
||
|
|
print $"📁 Location: ($extension_info.path)"
|
||
|
|
print $"📄 Files created: ($extension_info.files | length)"
|
||
|
|
print ""
|
||
|
|
print "🔄 Next steps:"
|
||
|
|
print $" 1. cd ($extension_info.path)"
|
||
|
|
print " 2. Customize the configuration in the main .k file"
|
||
|
|
print " 3. Update README.md with specific details"
|
||
|
|
|
||
|
|
if $type == "taskserv" {
|
||
|
|
print " 4. Test discovery:"
|
||
|
|
print $" nu -c \"use provisioning/core/nulib/taskservs/discover.nu *; get-taskserv-info ($name)\""
|
||
|
|
print " 5. Test layer resolution:"
|
||
|
|
print $" nu -c \"use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution ($name) wuji upcloud\""
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create extension from template
|
||
|
|
def create_extension_from_template [
|
||
|
|
type: string,
|
||
|
|
name: string,
|
||
|
|
template: string,
|
||
|
|
author: string,
|
||
|
|
description: string,
|
||
|
|
category: string,
|
||
|
|
port: int,
|
||
|
|
output_dir: string
|
||
|
|
]: nothing -> record {
|
||
|
|
# Determine template path based on type
|
||
|
|
let template_base = "provisioning/templates/extensions"
|
||
|
|
let template_path = match $type {
|
||
|
|
"taskserv" => ($template_base | path join "taskservs" "basic-taskserv")
|
||
|
|
"provider" => ($template_base | path join "providers" "basic-provider")
|
||
|
|
"cluster" => ($template_base | path join "clusters" "basic-cluster")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create extension path (with category for taskservs)
|
||
|
|
let extension_path = if $type == "taskserv" {
|
||
|
|
($output_dir | path join "taskservs" $category $name "kcl")
|
||
|
|
} else {
|
||
|
|
($output_dir | path join ($type + "s") $name "kcl")
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create output directory
|
||
|
|
print $"📁 Creating directories..."
|
||
|
|
mkdir $extension_path
|
||
|
|
|
||
|
|
# Also create default directory for taskservs
|
||
|
|
if $type == "taskserv" {
|
||
|
|
let default_path = ([$extension_path, "..", "default"] | path join)
|
||
|
|
mkdir $default_path
|
||
|
|
}
|
||
|
|
|
||
|
|
# Generate variables for templates
|
||
|
|
let variables = generate_template_variables $type $name $author $description $category $port
|
||
|
|
|
||
|
|
# Check if template exists, if not create basic structure
|
||
|
|
if ($template_path | path exists) {
|
||
|
|
# Copy and process template files
|
||
|
|
let template_files = try { ls $template_path | get name } catch { [] }
|
||
|
|
let created_files = ($template_files | each { |file|
|
||
|
|
process_template_file $file $extension_path $variables
|
||
|
|
})
|
||
|
|
|
||
|
|
# Create additional files for taskservs
|
||
|
|
if $type == "taskserv" {
|
||
|
|
create_taskserv_extras $extension_path $variables
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
path: ([$extension_path, ".."] | path join),
|
||
|
|
files: $created_files,
|
||
|
|
variables: $variables
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
# Create basic structure without templates
|
||
|
|
print $"⚠️ Template not found: ($template_path)"
|
||
|
|
print "📄 Creating basic structure..."
|
||
|
|
|
||
|
|
let created_files = create_basic_structure $type $extension_path $variables
|
||
|
|
|
||
|
|
# Create additional files for taskservs
|
||
|
|
if $type == "taskserv" {
|
||
|
|
create_taskserv_extras $extension_path $variables
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
path: ([$extension_path, ".."] | path join),
|
||
|
|
files: $created_files,
|
||
|
|
variables: $variables
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Generate template variables for substitution
|
||
|
|
def generate_template_variables [
|
||
|
|
type: string,
|
||
|
|
name: string,
|
||
|
|
author: string,
|
||
|
|
description: string,
|
||
|
|
category: string,
|
||
|
|
port: int
|
||
|
|
]: nothing -> record {
|
||
|
|
let name_pascal = ($name | split row "-" | each { |word| $word | str capitalize } | str join "")
|
||
|
|
let name_snake = ($name | str replace -a "-" "_")
|
||
|
|
let display_name = ($name | split row "-" | each { |word| $word | str capitalize } | str join " ")
|
||
|
|
|
||
|
|
let final_description = if ($description | is-empty) {
|
||
|
|
$"Deployment and management for ($display_name)"
|
||
|
|
} else {
|
||
|
|
$description
|
||
|
|
}
|
||
|
|
|
||
|
|
# Set defaults based on type and name
|
||
|
|
let default_port = if $port != 8080 {
|
||
|
|
$port
|
||
|
|
} else {
|
||
|
|
match $name {
|
||
|
|
$name if ($name | str contains "redis") => 6379
|
||
|
|
$name if ($name | str contains "postgres") => 5432
|
||
|
|
$name if ($name | str contains "mysql") => 3306
|
||
|
|
$name if ($name | str contains "http") => 8080
|
||
|
|
$name if ($name | str contains "api") => 8080
|
||
|
|
_ => 8080
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let final_category = if ($category | is-empty) {
|
||
|
|
match $name {
|
||
|
|
$name if ($name | str contains "database" or $name | str contains "db" or $name | str contains "postgres" or $name | str contains "mysql" or $name | str contains "redis") => "databases"
|
||
|
|
$name if ($name | str contains "web" or $name | str contains "http" or $name | str contains "nginx" or $name | str contains "proxy") => "networking"
|
||
|
|
$name if ($name | str contains "monitor" or $name | str contains "metric" or $name | str contains "log") => "infrastructure"
|
||
|
|
$name if ($name | str contains "security" or $name | str contains "auth" or $name | str contains "cert") => "infrastructure"
|
||
|
|
_ => "development"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
$category
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
# Old template compatibility
|
||
|
|
TASKSERV_NAME: $name,
|
||
|
|
TASKSERV_NAME_PASCAL: $name_pascal,
|
||
|
|
TASKSERV_NAME_SNAKE: $name_snake,
|
||
|
|
TASKSERV_DISPLAY_NAME: $display_name,
|
||
|
|
TASKSERV_DESCRIPTION: $final_description,
|
||
|
|
|
||
|
|
# New extension variables
|
||
|
|
EXTENSION_TYPE: $type,
|
||
|
|
EXTENSION_NAME: $name,
|
||
|
|
EXTENSION_NAME_PASCAL: $name_pascal,
|
||
|
|
EXTENSION_NAME_SNAKE: $name_snake,
|
||
|
|
EXTENSION_DISPLAY_NAME: $display_name,
|
||
|
|
EXTENSION_DESCRIPTION: $final_description,
|
||
|
|
EXTENSION_CATEGORY: $final_category,
|
||
|
|
DEFAULT_PORT: $default_port,
|
||
|
|
AUTHOR_NAME: $author,
|
||
|
|
LICENSE: "MIT",
|
||
|
|
REPOSITORY_URL: $"https://github.com/($author | str downcase)/($name)-($type)",
|
||
|
|
SERVICE_VERSION: "latest",
|
||
|
|
TAGS: $"\"($final_category)\", \"kubernetes\", \"($name)\"",
|
||
|
|
DOCS_URL: $"https://docs.example.com/($name)",
|
||
|
|
SUPPORT_URL: $"https://github.com/($author | str downcase)/($name)-($type)/issues",
|
||
|
|
DATE: (date now | format date "%Y-%m-%d"),
|
||
|
|
VERSION: "1.0.0"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Process a single template file
|
||
|
|
def process_template_file [
|
||
|
|
file: string,
|
||
|
|
extension_path: string,
|
||
|
|
variables: record
|
||
|
|
]: nothing -> string {
|
||
|
|
let filename = ($file | path basename)
|
||
|
|
let target_file = process_filename $filename $variables
|
||
|
|
let target_path = ($extension_path | path join $target_file)
|
||
|
|
|
||
|
|
# Read template content
|
||
|
|
let content = try {
|
||
|
|
open $file
|
||
|
|
} catch {
|
||
|
|
""
|
||
|
|
}
|
||
|
|
|
||
|
|
# Process template variables
|
||
|
|
let processed_content = process_template_content $content $variables
|
||
|
|
|
||
|
|
# Save processed file
|
||
|
|
$processed_content | save $target_path
|
||
|
|
|
||
|
|
print $" ✓ Created ($target_file)"
|
||
|
|
$target_path
|
||
|
|
}
|
||
|
|
|
||
|
|
# Process template filename with variables
|
||
|
|
def process_filename [filename: string, variables: record]: nothing -> string {
|
||
|
|
mut processed = $filename
|
||
|
|
|
||
|
|
# Use fold instead of reduce for modern Nushell
|
||
|
|
for key_value in ($variables | transpose key value) {
|
||
|
|
$processed = ($processed | str replace -a $"{{($key_value.key)}}" $key_value.value)
|
||
|
|
}
|
||
|
|
|
||
|
|
$processed
|
||
|
|
}
|
||
|
|
|
||
|
|
# Process template content with variables
|
||
|
|
def process_template_content [content: string, variables: record]: nothing -> string {
|
||
|
|
mut processed = $content
|
||
|
|
|
||
|
|
# Use fold instead of reduce for modern Nushell
|
||
|
|
for key_value in ($variables | transpose key value) {
|
||
|
|
$processed = ($processed | str replace -a $"{{($key_value.key)}}" ($key_value.value | into string))
|
||
|
|
}
|
||
|
|
|
||
|
|
$processed
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create basic structure without templates
|
||
|
|
def create_basic_structure [
|
||
|
|
type: string,
|
||
|
|
extension_path: string,
|
||
|
|
variables: record
|
||
|
|
]: nothing -> list<string> {
|
||
|
|
mut created_files = []
|
||
|
|
|
||
|
|
match $type {
|
||
|
|
"taskserv" => {
|
||
|
|
# Create kcl.mod
|
||
|
|
let kcl_mod_content = create_basic_kcl_mod $variables
|
||
|
|
($kcl_mod_content | save ($extension_path | path join "kcl.mod"))
|
||
|
|
$created_files = ($created_files | append "kcl.mod")
|
||
|
|
|
||
|
|
# Create main KCL file
|
||
|
|
let main_kcl_content = create_basic_taskserv_kcl $variables
|
||
|
|
($main_kcl_content | save ($extension_path | path join $"($variables.EXTENSION_NAME).k"))
|
||
|
|
$created_files = ($created_files | append $"($variables.EXTENSION_NAME).k")
|
||
|
|
|
||
|
|
# Create version file
|
||
|
|
let version_content = create_basic_version_kcl $variables
|
||
|
|
($version_content | save ($extension_path | path join "version.k"))
|
||
|
|
$created_files = ($created_files | append "version.k")
|
||
|
|
|
||
|
|
# Create README
|
||
|
|
let readme_content = create_basic_readme $variables
|
||
|
|
($readme_content | save ([$extension_path, "..", "README.md"] | path join))
|
||
|
|
$created_files = ($created_files | append "README.md")
|
||
|
|
|
||
|
|
print $" ✓ Created kcl.mod"
|
||
|
|
print $" ✓ Created ($variables.EXTENSION_NAME).k"
|
||
|
|
print $" ✓ Created version.k"
|
||
|
|
print $" ✓ Created README.md"
|
||
|
|
}
|
||
|
|
"provider" => {
|
||
|
|
# Create basic provider structure
|
||
|
|
let provider_content = create_basic_provider_kcl $variables
|
||
|
|
($provider_content | save ($extension_path | path join $"($variables.EXTENSION_NAME).k"))
|
||
|
|
$created_files = ($created_files | append $"($variables.EXTENSION_NAME).k")
|
||
|
|
|
||
|
|
print $" ✓ Created ($variables.EXTENSION_NAME).k"
|
||
|
|
}
|
||
|
|
"cluster" => {
|
||
|
|
# Create basic cluster structure
|
||
|
|
let cluster_content = create_basic_cluster_kcl $variables
|
||
|
|
($cluster_content | save ($extension_path | path join $"($variables.EXTENSION_NAME).k"))
|
||
|
|
$created_files = ($created_files | append $"($variables.EXTENSION_NAME).k")
|
||
|
|
|
||
|
|
print $" ✓ Created ($variables.EXTENSION_NAME).k"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$created_files
|
||
|
|
}
|
||
|
|
|
||
|
|
# Create extra files for taskservs
|
||
|
|
def create_taskserv_extras [
|
||
|
|
extension_path: string,
|
||
|
|
variables: record
|
||
|
|
] {
|
||
|
|
let default_path = ([$extension_path, "..", "default"] | path join)
|
||
|
|
|
||
|
|
# Create defs.toml
|
||
|
|
let defs_content = create_basic_defs_toml $variables
|
||
|
|
($defs_content | save ($default_path | path join "defs.toml"))
|
||
|
|
|
||
|
|
# Create install script
|
||
|
|
let install_content = create_basic_install_script $variables
|
||
|
|
($install_content | save ($default_path | path join $"install-($variables.EXTENSION_NAME).sh"))
|
||
|
|
chmod +x ($default_path | path join $"install-($variables.EXTENSION_NAME).sh")
|
||
|
|
|
||
|
|
print $" ✓ Created defs.toml"
|
||
|
|
print $" ✓ Created install-($variables.EXTENSION_NAME).sh"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Template content generators
|
||
|
|
def create_basic_kcl_mod [variables: record]: nothing -> string {
|
||
|
|
$"[package]
|
||
|
|
name = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version = \"($variables.VERSION)\"
|
||
|
|
description = \"($variables.EXTENSION_DESCRIPTION)\"
|
||
|
|
authors = [\"($variables.AUTHOR_NAME)\"]
|
||
|
|
|
||
|
|
[dependencies]
|
||
|
|
k8s = { oci = \"oci://ghcr.io/kcl-lang/k8s\", tag = \"1.30\" }
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_basic_taskserv_kcl [variables: record]: nothing -> string {
|
||
|
|
$"# ($variables.EXTENSION_DISPLAY_NAME) Taskserv Configuration
|
||
|
|
# ($variables.EXTENSION_DESCRIPTION)
|
||
|
|
|
||
|
|
schema ($variables.EXTENSION_NAME_PASCAL) {
|
||
|
|
# Service metadata
|
||
|
|
name: str = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version: str = \"latest\"
|
||
|
|
namespace: str = \"default\"
|
||
|
|
|
||
|
|
# Service configuration
|
||
|
|
replicas: int = 1
|
||
|
|
port: int = ($variables.DEFAULT_PORT)
|
||
|
|
|
||
|
|
# Resource requirements
|
||
|
|
resources: {
|
||
|
|
cpu: str = \"100m\"
|
||
|
|
memory: str = \"128Mi\"
|
||
|
|
limits?: {
|
||
|
|
cpu?: str = \"500m\"
|
||
|
|
memory?: str = \"512Mi\"
|
||
|
|
}
|
||
|
|
} = {
|
||
|
|
cpu = \"100m\"
|
||
|
|
memory = \"128Mi\"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Service specific configuration
|
||
|
|
config?: {str: any} = {}
|
||
|
|
|
||
|
|
# Health checks
|
||
|
|
health?: {
|
||
|
|
enabled: bool = true
|
||
|
|
path: str = \"/health\"
|
||
|
|
initial_delay: int = 30
|
||
|
|
period: int = 10
|
||
|
|
} = {
|
||
|
|
enabled = true
|
||
|
|
path = \"/health\"
|
||
|
|
initial_delay = 30
|
||
|
|
period = 10
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Default configuration
|
||
|
|
($variables.EXTENSION_NAME_SNAKE)_config: ($variables.EXTENSION_NAME_PASCAL) = ($variables.EXTENSION_NAME_PASCAL) {
|
||
|
|
name = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version = \"latest\"
|
||
|
|
replicas = 1
|
||
|
|
port = ($variables.DEFAULT_PORT)
|
||
|
|
}
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_basic_version_kcl [variables: record]: nothing -> string {
|
||
|
|
$"# Version information for ($variables.EXTENSION_NAME) taskserv
|
||
|
|
|
||
|
|
schema ($variables.EXTENSION_NAME_PASCAL)Version {
|
||
|
|
current: str = \"($variables.VERSION)\"
|
||
|
|
compatible: [str] = [\"($variables.VERSION)\"]
|
||
|
|
deprecated?: [str] = []
|
||
|
|
changelog?: {str: str} = {}
|
||
|
|
}
|
||
|
|
|
||
|
|
($variables.EXTENSION_NAME_SNAKE)_version: ($variables.EXTENSION_NAME_PASCAL)Version = ($variables.EXTENSION_NAME_PASCAL)Version {
|
||
|
|
current = \"($variables.VERSION)\"
|
||
|
|
changelog = {
|
||
|
|
\"($variables.VERSION)\" = \"Initial release\"
|
||
|
|
}
|
||
|
|
}
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_basic_defs_toml [variables: record]: nothing -> string {
|
||
|
|
$"# Default configuration for ($variables.EXTENSION_NAME)
|
||
|
|
# Generated on ($variables.DATE)
|
||
|
|
|
||
|
|
[service]
|
||
|
|
name = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version = \"latest\"
|
||
|
|
port = ($variables.DEFAULT_PORT)
|
||
|
|
replicas = 1
|
||
|
|
|
||
|
|
[deployment]
|
||
|
|
strategy = \"RollingUpdate\"
|
||
|
|
max_unavailable = 1
|
||
|
|
max_surge = 1
|
||
|
|
|
||
|
|
[resources]
|
||
|
|
cpu_request = \"100m\"
|
||
|
|
cpu_limit = \"500m\"
|
||
|
|
memory_request = \"128Mi\"
|
||
|
|
memory_limit = \"512Mi\"
|
||
|
|
|
||
|
|
[health]
|
||
|
|
enabled = true
|
||
|
|
path = \"/health\"
|
||
|
|
initial_delay_seconds = 30
|
||
|
|
period_seconds = 10
|
||
|
|
timeout_seconds = 5
|
||
|
|
|
||
|
|
[networking]
|
||
|
|
service_type = \"ClusterIP\"
|
||
|
|
expose_metrics = false
|
||
|
|
metrics_port = 9090
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_basic_install_script [variables: record]: nothing -> string {
|
||
|
|
$"#!/bin/bash
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
# ($variables.EXTENSION_DISPLAY_NAME) Installation Script
|
||
|
|
# Generated on ($variables.DATE)
|
||
|
|
|
||
|
|
echo \"🚀 Installing ($variables.EXTENSION_NAME)...\"
|
||
|
|
|
||
|
|
# Configuration
|
||
|
|
SERVICE_NAME=\"${SERVICE_NAME:-($variables.EXTENSION_NAME)}\"
|
||
|
|
SERVICE_VERSION=\"${SERVICE_VERSION:-latest}\"
|
||
|
|
NAMESPACE=\"${NAMESPACE:-default}\"
|
||
|
|
REPLICAS=\"${REPLICAS:-1}\"
|
||
|
|
PORT=\"${PORT:-($variables.DEFAULT_PORT)}\"
|
||
|
|
|
||
|
|
# Validation
|
||
|
|
if [[ -z \"$SERVICE_NAME\" ]]; then
|
||
|
|
echo \"❌ SERVICE_NAME is required\"
|
||
|
|
exit 1
|
||
|
|
fi
|
||
|
|
|
||
|
|
echo \"📋 Configuration:\"
|
||
|
|
echo \" Service: $SERVICE_NAME\"
|
||
|
|
echo \" Version: $SERVICE_VERSION\"
|
||
|
|
echo \" Namespace: $NAMESPACE\"
|
||
|
|
echo \" Port: $PORT\"
|
||
|
|
echo \" Replicas: $REPLICAS\"
|
||
|
|
|
||
|
|
# Create namespace
|
||
|
|
echo \"🏗️ Creating namespace...\"
|
||
|
|
kubectl create namespace \"$NAMESPACE\" --dry-run=client -o yaml | kubectl apply -f -
|
||
|
|
|
||
|
|
# Apply configuration
|
||
|
|
echo \"🚢 Deploying service...\"
|
||
|
|
cat <<EOF | envsubst | kubectl apply -f -
|
||
|
|
apiVersion: apps/v1
|
||
|
|
kind: Deployment
|
||
|
|
metadata:
|
||
|
|
name: $SERVICE_NAME
|
||
|
|
namespace: $NAMESPACE
|
||
|
|
labels:
|
||
|
|
app: $SERVICE_NAME
|
||
|
|
spec:
|
||
|
|
replicas: $REPLICAS
|
||
|
|
selector:
|
||
|
|
matchLabels:
|
||
|
|
app: $SERVICE_NAME
|
||
|
|
template:
|
||
|
|
metadata:
|
||
|
|
labels:
|
||
|
|
app: $SERVICE_NAME
|
||
|
|
spec:
|
||
|
|
containers:
|
||
|
|
- name: $SERVICE_NAME
|
||
|
|
image: $SERVICE_NAME:$SERVICE_VERSION
|
||
|
|
ports:
|
||
|
|
- containerPort: $PORT
|
||
|
|
resources:
|
||
|
|
requests:
|
||
|
|
cpu: 100m
|
||
|
|
memory: 128Mi
|
||
|
|
limits:
|
||
|
|
cpu: 500m
|
||
|
|
memory: 512Mi
|
||
|
|
livenessProbe:
|
||
|
|
httpGet:
|
||
|
|
path: /health
|
||
|
|
port: $PORT
|
||
|
|
initialDelaySeconds: 30
|
||
|
|
periodSeconds: 10
|
||
|
|
readinessProbe:
|
||
|
|
httpGet:
|
||
|
|
path: /health
|
||
|
|
port: $PORT
|
||
|
|
initialDelaySeconds: 5
|
||
|
|
periodSeconds: 5
|
||
|
|
---
|
||
|
|
apiVersion: v1
|
||
|
|
kind: Service
|
||
|
|
metadata:
|
||
|
|
name: $SERVICE_NAME
|
||
|
|
namespace: $NAMESPACE
|
||
|
|
labels:
|
||
|
|
app: $SERVICE_NAME
|
||
|
|
spec:
|
||
|
|
selector:
|
||
|
|
app: $SERVICE_NAME
|
||
|
|
ports:
|
||
|
|
- port: $PORT
|
||
|
|
targetPort: $PORT
|
||
|
|
type: ClusterIP
|
||
|
|
EOF
|
||
|
|
|
||
|
|
# Wait for deployment
|
||
|
|
echo \"⏳ Waiting for deployment to be ready...\"
|
||
|
|
kubectl wait --for=condition=available --timeout=300s deployment/$SERVICE_NAME -n $NAMESPACE
|
||
|
|
|
||
|
|
echo \"✅ ($variables.EXTENSION_DISPLAY_NAME) installed successfully\"
|
||
|
|
echo \"🔍 Check status with: kubectl get pods -n $NAMESPACE -l app=$SERVICE_NAME\"
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_basic_readme [variables: record]: nothing -> string {
|
||
|
|
let readme_template = '# {{EXTENSION_DISPLAY_NAME}} Extension
|
||
|
|
|
||
|
|
{{EXTENSION_DESCRIPTION}}
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
This extension provides {{EXTENSION_TYPE}} capabilities for {{EXTENSION_DISPLAY_NAME}}.
|
||
|
|
|
||
|
|
## Configuration
|
||
|
|
|
||
|
|
### Basic Usage
|
||
|
|
|
||
|
|
```kcl
|
||
|
|
import {{EXTENSION_TYPE}}s.{{EXTENSION_CATEGORY}}.{{EXTENSION_NAME}}.kcl.{{EXTENSION_NAME}} as {{EXTENSION_NAME_SNAKE}}
|
||
|
|
|
||
|
|
{{EXTENSION_NAME_SNAKE}}_config: {{EXTENSION_NAME_SNAKE}}.{{EXTENSION_NAME_PASCAL}} = {{EXTENSION_NAME_SNAKE}}.{{EXTENSION_NAME_SNAKE}}_config {
|
||
|
|
# Customize configuration
|
||
|
|
version = "1.2.3"
|
||
|
|
port = {{DEFAULT_PORT}}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Installation
|
||
|
|
|
||
|
|
### Using Provisioning System
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Deploy with provisioning CLI
|
||
|
|
provisioning/core/cli/provisioning {{EXTENSION_TYPE}} create {{EXTENSION_NAME}} --infra my-infra --check
|
||
|
|
```
|
||
|
|
|
||
|
|
### Direct Installation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Set environment variables
|
||
|
|
export SERVICE_VERSION="1.0.0"
|
||
|
|
export NAMESPACE="production"
|
||
|
|
|
||
|
|
# Run installation script
|
||
|
|
./default/install-{{EXTENSION_NAME}}.sh
|
||
|
|
```
|
||
|
|
|
||
|
|
## Development
|
||
|
|
|
||
|
|
### Testing
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Test discovery (for taskservs)
|
||
|
|
nu -c "use provisioning/core/nulib/taskservs/discover.nu *; get-taskserv-info {{EXTENSION_NAME}}"
|
||
|
|
|
||
|
|
# Test layer resolution (for taskservs)
|
||
|
|
nu -c "use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution {{EXTENSION_NAME}} wuji upcloud"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Validation
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Check KCL syntax
|
||
|
|
kcl check kcl/{{EXTENSION_NAME}}.k
|
||
|
|
|
||
|
|
# Validate extension structure
|
||
|
|
nu provisioning/tools/create-extension.nu validate .
|
||
|
|
```
|
||
|
|
|
||
|
|
## License
|
||
|
|
|
||
|
|
{{LICENSE}}
|
||
|
|
|
||
|
|
## Author
|
||
|
|
|
||
|
|
{{AUTHOR_NAME}}
|
||
|
|
|
||
|
|
## Generated
|
||
|
|
|
||
|
|
Created on {{DATE}} using the Extension Creation Tool.
|
||
|
|
'
|
||
|
|
|
||
|
|
process_template_content $readme_template $variables
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_basic_provider_kcl [variables: record]: nothing -> string {
|
||
|
|
$"# ($variables.EXTENSION_DISPLAY_NAME) Provider Configuration
|
||
|
|
# ($variables.EXTENSION_DESCRIPTION)
|
||
|
|
|
||
|
|
schema ($variables.EXTENSION_NAME_PASCAL)Provider {
|
||
|
|
# Provider metadata
|
||
|
|
name: str = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version: str = \"latest\"
|
||
|
|
region?: str = \"default\"
|
||
|
|
|
||
|
|
# Provider-specific configuration
|
||
|
|
config?: {str: any} = {}
|
||
|
|
|
||
|
|
# Authentication
|
||
|
|
auth?: {
|
||
|
|
type: str = \"default\"
|
||
|
|
credentials?: {str: any} = {}
|
||
|
|
} = {}
|
||
|
|
|
||
|
|
# Resource limits
|
||
|
|
limits?: {
|
||
|
|
instances?: int = 10
|
||
|
|
storage?: str = \"100GB\"
|
||
|
|
} = {}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Default configuration
|
||
|
|
($variables.EXTENSION_NAME_SNAKE)_provider: ($variables.EXTENSION_NAME_PASCAL)Provider = ($variables.EXTENSION_NAME_PASCAL)Provider {
|
||
|
|
name = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version = \"latest\"
|
||
|
|
}
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
def create_basic_cluster_kcl [variables: record]: nothing -> string {
|
||
|
|
$"# ($variables.EXTENSION_DISPLAY_NAME) Cluster Configuration
|
||
|
|
# ($variables.EXTENSION_DESCRIPTION)
|
||
|
|
|
||
|
|
schema ($variables.EXTENSION_NAME_PASCAL)Cluster {
|
||
|
|
# Cluster metadata
|
||
|
|
name: str = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version: str = \"latest\"
|
||
|
|
namespace: str = \"default\"
|
||
|
|
|
||
|
|
# Cluster configuration
|
||
|
|
nodes: int = 3
|
||
|
|
node_type: str = \"standard\"
|
||
|
|
|
||
|
|
# Cluster components
|
||
|
|
components: [str] = []
|
||
|
|
|
||
|
|
# Networking
|
||
|
|
networking?: {
|
||
|
|
cidr?: str = \"10.0.0.0/16\"
|
||
|
|
service_cidr?: str = \"10.1.0.0/16\"
|
||
|
|
} = {}
|
||
|
|
|
||
|
|
# Storage
|
||
|
|
storage?: {
|
||
|
|
type: str = \"persistent\"
|
||
|
|
size: str = \"10Gi\"
|
||
|
|
} = {}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Default configuration
|
||
|
|
($variables.EXTENSION_NAME_SNAKE)_cluster: ($variables.EXTENSION_NAME_PASCAL)Cluster = ($variables.EXTENSION_NAME_PASCAL)Cluster {
|
||
|
|
name = \"($variables.EXTENSION_NAME)\"
|
||
|
|
version = \"latest\"
|
||
|
|
nodes = 3
|
||
|
|
}
|
||
|
|
"
|
||
|
|
}
|
||
|
|
|
||
|
|
# List available templates
|
||
|
|
export def "main list-templates" [type?: string]: nothing -> table {
|
||
|
|
let templates_base = "provisioning/templates/extensions"
|
||
|
|
|
||
|
|
if ($type | is-empty) {
|
||
|
|
# List all templates
|
||
|
|
mut all_templates = []
|
||
|
|
|
||
|
|
for ext_type in ["taskservs", "providers", "clusters"] {
|
||
|
|
let type_dir = ($templates_base | path join $ext_type)
|
||
|
|
if ($type_dir | path exists) {
|
||
|
|
let templates = try {
|
||
|
|
ls $type_dir | get name | each { |path|
|
||
|
|
{ type: ($ext_type | str replace "s$" ""), name: ($path | path basename) }
|
||
|
|
}
|
||
|
|
} catch { [] }
|
||
|
|
$all_templates = ($all_templates | append $templates)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$all_templates
|
||
|
|
} else {
|
||
|
|
# List templates for specific type
|
||
|
|
let type_plural = $type + "s"
|
||
|
|
let template_dir = ($templates_base | path join $type_plural)
|
||
|
|
|
||
|
|
if ($template_dir | path exists) {
|
||
|
|
ls $template_dir | get name | each { |path|
|
||
|
|
{ type: $type, name: ($path | path basename) }
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
[]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show template information
|
||
|
|
export def "main template-info" [type: string, template: string]: nothing -> record {
|
||
|
|
let template_path = $"provisioning/templates/extensions/($type)s/($template)"
|
||
|
|
|
||
|
|
if not ($template_path | path exists) {
|
||
|
|
error make { msg: $"Template not found: ($template_path)" }
|
||
|
|
}
|
||
|
|
|
||
|
|
let template_files = try {
|
||
|
|
ls $template_path | get name | each { |file| $file | path basename }
|
||
|
|
} catch { [] }
|
||
|
|
|
||
|
|
let readme_path = ($template_path | path join "README.md")
|
||
|
|
let readme_content = if ($readme_path | path exists) {
|
||
|
|
try {
|
||
|
|
open $readme_path | lines | take 10 | str join "\n"
|
||
|
|
} catch {
|
||
|
|
"No README content available"
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
"No README available"
|
||
|
|
}
|
||
|
|
|
||
|
|
{
|
||
|
|
type: $type,
|
||
|
|
template: $template,
|
||
|
|
path: $template_path,
|
||
|
|
files: $template_files,
|
||
|
|
readme_preview: $readme_content
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Interactive extension creation
|
||
|
|
export def "main interactive" []: nothing -> nothing {
|
||
|
|
print "🚀 Interactive Extension Creator"
|
||
|
|
print ""
|
||
|
|
|
||
|
|
# Get extension type
|
||
|
|
let type = (["taskserv", "provider", "cluster"] | input list "Select extension type:")
|
||
|
|
|
||
|
|
# Get extension name
|
||
|
|
let name = (input "Enter extension name (kebab-case):")
|
||
|
|
|
||
|
|
if ($name | is-empty) or ($name | str contains " ") or ($name | str contains "_") {
|
||
|
|
error make { msg: "Extension name must be kebab-case format (e.g., my-service)" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get category for taskservs
|
||
|
|
let category = if $type == "taskserv" {
|
||
|
|
let categories = [
|
||
|
|
"container-runtime"
|
||
|
|
"databases"
|
||
|
|
"development"
|
||
|
|
"infrastructure"
|
||
|
|
"kubernetes"
|
||
|
|
"networking"
|
||
|
|
"storage"
|
||
|
|
]
|
||
|
|
|
||
|
|
print ""
|
||
|
|
print "📂 Available categories:"
|
||
|
|
for i in 0..($categories | length) {
|
||
|
|
let cat = ($categories | get $i)
|
||
|
|
print $" ($i + 1). ($cat)"
|
||
|
|
}
|
||
|
|
|
||
|
|
let category_choice = input "Select category (1-7):"
|
||
|
|
let category_index = (($category_choice | into int) - 1)
|
||
|
|
|
||
|
|
if $category_index < 0 or $category_index >= ($categories | length) {
|
||
|
|
error make { msg: "Invalid category selection" }
|
||
|
|
}
|
||
|
|
|
||
|
|
($categories | get $category_index)
|
||
|
|
} else {
|
||
|
|
""
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get port for taskservs
|
||
|
|
let port = if $type == "taskserv" {
|
||
|
|
let port_input = input "Enter port number (default: 8080):"
|
||
|
|
if ($port_input | is-empty) { 8080 } else { $port_input | into int }
|
||
|
|
} else {
|
||
|
|
8080
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get optional information
|
||
|
|
let author = input "Enter author name:" --default "Developer"
|
||
|
|
let description = input $"Enter description for ($name):" --default ""
|
||
|
|
|
||
|
|
# Show available templates
|
||
|
|
let available_templates = (main list-templates $type)
|
||
|
|
let template = if ($available_templates | length) > 0 {
|
||
|
|
let template_names = ($available_templates | get name)
|
||
|
|
if ($template_names | length) > 1 {
|
||
|
|
$template_names | input list "Select template:"
|
||
|
|
} else {
|
||
|
|
$template_names | first
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
"basic"
|
||
|
|
}
|
||
|
|
|
||
|
|
# Get output directory
|
||
|
|
let output = input "Enter output directory:" --default "provisioning/extensions"
|
||
|
|
|
||
|
|
# Create the extension
|
||
|
|
print ""
|
||
|
|
print "📋 Creating extension with:"
|
||
|
|
print $" Type: ($type)"
|
||
|
|
print $" Name: ($name)"
|
||
|
|
if $type == "taskserv" {
|
||
|
|
print $" Category: ($category)"
|
||
|
|
print $" Port: ($port)"
|
||
|
|
}
|
||
|
|
print $" Template: ($template)"
|
||
|
|
print $" Author: ($author)"
|
||
|
|
print ""
|
||
|
|
|
||
|
|
let confirm = input "✅ Proceed with creation? (y/N):"
|
||
|
|
if not ($confirm | str downcase | str starts-with "y") {
|
||
|
|
print "❌ Creation cancelled"
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
if $type == "taskserv" {
|
||
|
|
main $type $name --template $template --author $author --description $description --category $category --port $port --output $output
|
||
|
|
} else {
|
||
|
|
main $type $name --template $template --author $author --description $description --output $output
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Validate extension structure
|
||
|
|
export def "main validate" [extension_path: string]: nothing -> record {
|
||
|
|
print $"🔍 Validating extension: ($extension_path)"
|
||
|
|
|
||
|
|
let kcl_path = ($extension_path | path join "kcl")
|
||
|
|
if not ($kcl_path | path exists) {
|
||
|
|
error make { msg: $"KCL directory not found: ($kcl_path)" }
|
||
|
|
}
|
||
|
|
|
||
|
|
# Check required files
|
||
|
|
let required_files = ["kcl.mod"]
|
||
|
|
let optional_files = ["version.k", "dependencies.k", "README.md"]
|
||
|
|
|
||
|
|
let missing_required = ($required_files | where { |file|
|
||
|
|
not (($kcl_path | path join $file) | path exists)
|
||
|
|
})
|
||
|
|
|
||
|
|
let missing_optional = ($optional_files | where { |file|
|
||
|
|
not (($kcl_path | path join $file) | path exists)
|
||
|
|
})
|
||
|
|
|
||
|
|
# Check KCL syntax
|
||
|
|
let kcl_files = try {
|
||
|
|
glob ($kcl_path | path join "*.k") | get name
|
||
|
|
} catch {
|
||
|
|
[]
|
||
|
|
}
|
||
|
|
|
||
|
|
let syntax_errors = ($kcl_files | each { |file|
|
||
|
|
try {
|
||
|
|
^kcl check $file
|
||
|
|
null
|
||
|
|
} catch { |err|
|
||
|
|
{ file: $file, error: $err.msg }
|
||
|
|
}
|
||
|
|
} | compact)
|
||
|
|
|
||
|
|
let is_valid = ($missing_required | is-empty) and ($syntax_errors | is-empty)
|
||
|
|
|
||
|
|
{
|
||
|
|
valid: $is_valid,
|
||
|
|
extension_path: $extension_path,
|
||
|
|
missing_required_files: $missing_required,
|
||
|
|
missing_optional_files: $missing_optional,
|
||
|
|
syntax_errors: $syntax_errors,
|
||
|
|
recommendations: (
|
||
|
|
if not $is_valid {
|
||
|
|
[
|
||
|
|
"Fix missing required files",
|
||
|
|
"Resolve KCL syntax errors",
|
||
|
|
"Add optional files for better documentation"
|
||
|
|
]
|
||
|
|
} else {
|
||
|
|
["Extension structure is valid"]
|
||
|
|
}
|
||
|
|
)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
# Show help
|
||
|
|
export def "main help" [] {
|
||
|
|
print "🛠️ Extension Creation Tool (Updated)"
|
||
|
|
print ""
|
||
|
|
print "USAGE:"
|
||
|
|
print " nu provisioning/tools/create-extension.nu <command> [options]"
|
||
|
|
print ""
|
||
|
|
print "COMMANDS:"
|
||
|
|
print " create <type> <name> Create new extension"
|
||
|
|
print " interactive Interactive extension creator"
|
||
|
|
print " list-templates [type] List available templates"
|
||
|
|
print " template-info <type> <template> Show template information"
|
||
|
|
print " validate <path> Validate extension structure"
|
||
|
|
print " help Show this help"
|
||
|
|
print ""
|
||
|
|
print "EXTENSION TYPES:"
|
||
|
|
print " taskserv Infrastructure task services"
|
||
|
|
print " provider Cloud providers"
|
||
|
|
print " cluster Cluster configurations"
|
||
|
|
print ""
|
||
|
|
print "CREATE OPTIONS:"
|
||
|
|
print " --template <template> Template to use (default: basic)"
|
||
|
|
print " --author <author> Author name (default: Unknown)"
|
||
|
|
print " --description <desc> Extension description"
|
||
|
|
print " --category <category> Category (required for taskservs)"
|
||
|
|
print " --port <port> Port number (default: 8080, for taskservs)"
|
||
|
|
print " --output <dir> Output directory (default: provisioning/extensions)"
|
||
|
|
print ""
|
||
|
|
print "EXAMPLES:"
|
||
|
|
print " # Interactive creation"
|
||
|
|
print " nu provisioning/tools/create-extension.nu interactive"
|
||
|
|
print ""
|
||
|
|
print " # Create taskserv"
|
||
|
|
print " nu provisioning/tools/create-extension.nu taskserv my-api \\"
|
||
|
|
print " --category development \\"
|
||
|
|
print " --port 8080 \\"
|
||
|
|
print " --description \"My API service\""
|
||
|
|
print ""
|
||
|
|
print " # List templates"
|
||
|
|
print " nu provisioning/tools/create-extension.nu list-templates taskserv"
|
||
|
|
print ""
|
||
|
|
print " # Validate extension"
|
||
|
|
print " nu provisioning/tools/create-extension.nu validate /path/to/extension"
|
||
|
|
}
|