provisioning/tools/create-taskserv-helper.nu

816 lines
23 KiB
Plaintext
Raw Normal View History

2025-10-07 11:12:02 +01:00
#!/usr/bin/env nu
# Taskserv Creation Helper Tool
# Modern Nushell-compatible tool for creating taskservs with guided setup
use ../core/nulib/taskservs/discover.nu *
# Main command dispatcher
def main [
command?: string # Command: create, interactive, template, discover, help
name?: string # Taskserv name (for create command)
--category: string # Taskserv category
--port: int = 8080 # Default port
--description: string = "" # Service description
--author: string = "Developer" # Author name
--template: string = "basic" # Template type
--output: string = "provisioning/extensions" # Output directory
] {
match $command {
"create" => {
if ($name | is-empty) {
error make { msg: "Taskserv name is required for create command" }
}
create_taskserv $name $category $port $description $author $output
}
"interactive" | null => {
interactive_create
}
"template" => {
if ($name | is-empty) {
create_workspace_template
} else {
create_workspace_template $name
}
}
"discover" => {
show_discovery_info
}
"help" => {
show_help
}
_ => {
print $"Unknown command: ($command)"
show_help
}
}
}
# Interactive taskserv creation
def interactive_create [] {
print "🚀 Interactive Taskserv Creator"
print ""
# Get basic information
let name = input "📝 Enter taskserv name (kebab-case, e.g., 'my-api'):"
if ($name | is-empty) or ($name | str contains " ") {
error make { msg: "Taskserv name must be kebab-case without spaces" }
}
# Select category
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" }
}
let category = ($categories | get $category_index)
# Get additional info
let description = input $"📄 Enter description for ($name) (optional):"
let author = input "👤 Enter author name (optional):" --default "Developer"
# Service type specific questions
let port = match $category {
"databases" => {
let db_ports = {
"postgres": 5432,
"redis": 6379,
"mysql": 3306,
"mongodb": 27017
}
if ($name | str contains "postgres") { 5432 }
else if ($name | str contains "redis") { 6379 }
else if ($name | str contains "mysql") { 3306 }
else if ($name | str contains "mongo") { 27017 }
else { 5432 }
}
"development" => {
if ($name | str contains "api") or ($name | str contains "web") { 8080 }
else { 3000 }
}
_ => 8080
}
let port_input = input $"🔌 Enter port number (default: ($port)):"
let final_port = if ($port_input | is-empty) { $port } else { $port_input | into int }
print ""
print "📋 Creating taskserv with:"
print $" Name: ($name)"
print $" Category: ($category)"
print $" Port: ($final_port)"
print $" Description: ($description)"
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
}
create_taskserv $name $category $final_port $description $author "provisioning/extensions"
}
# Create taskserv with parameters
def create_taskserv [
name: string
category: string
port: int
description: string
author: string
output_dir: string
] {
print $"🛠️ Creating taskserv: ($name)"
# Validate name format
if ($name | str contains " ") or ($name | str contains "_") or ($name != ($name | str downcase)) {
error make { msg: "Name must be kebab-case (lowercase with hyphens)" }
}
# Create directory structure
let taskserv_path = ($output_dir | path join "taskservs" $category $name)
let kcl_path = ($taskserv_path | path join "kcl")
let default_path = ($taskserv_path | path join "default")
print $"📁 Creating directories..."
mkdir $kcl_path
mkdir $default_path
# Generate variables for templates
let variables = generate_variables $name $category $port $description $author
# Create files
create_kcl_mod $kcl_path $variables
create_main_schema $kcl_path $variables
create_version_file $kcl_path $variables
create_defaults_toml $default_path $variables
create_install_script $default_path $variables
create_readme $taskserv_path $variables
print $"✅ Taskserv created successfully at: ($taskserv_path)"
print ""
print "🔄 Next steps:"
print "1. Customize the configuration in the .k files"
print "2. Update the installation script"
print "3. Test discovery:"
print $" nu -c \"use provisioning/core/nulib/taskservs/discover.nu *; get-taskserv-info ($name)\""
print "4. Create a workspace template (optional):"
print $" nu provisioning/tools/create-taskserv-helper.nu template ($name)"
# Show layer resolution test
print ""
print "🧪 Test layer resolution:"
print $" nu -c \"use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution ($name) wuji upcloud\""
}
# Generate template variables
def generate_variables [
name: string
category: string
port: int
description: string
author: string
]: 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
}
{
name: $name,
name_pascal: $name_pascal,
name_snake: $name_snake,
display_name: $display_name,
category: $category,
port: $port,
description: $final_description,
author: $author,
date: (date now | format date "%Y-%m-%d"),
version: "1.0.0"
}
}
# Create kcl.mod file
def create_kcl_mod [kcl_path: string, vars: record] {
let content = $"[package]
name = \"($vars.name)\"
version = \"($vars.version)\"
description = \"($vars.description)\"
authors = [\"($vars.author)\"]
[dependencies]
k8s = { oci = \"oci://ghcr.io/kcl-lang/k8s\", tag = \"1.30\" }
"
$content | save ($kcl_path | path join "kcl.mod")
print $" ✓ Created kcl.mod"
}
# Create main KCL schema
def create_main_schema [kcl_path: string, vars: record] {
let content = $"# ($vars.display_name) Taskserv Configuration
# ($vars.description)
schema ($vars.name_pascal) {
# Service metadata
name: str = \"($vars.name)\"
version: str = \"latest\"
namespace: str = \"default\"
# Service configuration
replicas: int = 1
port: int = ($vars.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
($vars.name_snake)_config: ($vars.name_pascal) = ($vars.name_pascal) {
name = \"($vars.name)\"
version = \"latest\"
replicas = 1
port = ($vars.port)
}
"
$content | save ($kcl_path | path join $"($vars.name).k")
print $" ✓ Created ($vars.name).k"
}
# Create version file
def create_version_file [kcl_path: string, vars: record] {
let content = $"# Version information for ($vars.name) taskserv
schema ($vars.name_pascal)Version {
current: str = \"($vars.version)\"
compatible: [str] = [\"($vars.version)\"]
deprecated?: [str] = []
changelog?: {str: str} = {}
}
($vars.name_snake)_version: ($vars.name_pascal)Version = ($vars.name_pascal)Version {
current = \"($vars.version)\"
changelog = {
\"($vars.version)\" = \"Initial release\"
}
}
"
$content | save ($kcl_path | path join "version.k")
print $" ✓ Created version.k"
}
# Create defaults TOML
def create_defaults_toml [default_path: string, vars: record] {
let content = $"# Default configuration for ($vars.name)
# Generated on ($vars.date)
[service]
name = \"($vars.name)\"
version = \"latest\"
port = ($vars.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
"
$content | save ($default_path | path join "defs.toml")
print $" ✓ Created defs.toml"
}
# Create installation script
def create_install_script [default_path: string, vars: record] {
let content = $"#!/bin/bash
set -euo pipefail
# ($vars.display_name) Installation Script
# Generated on ($vars.date)
echo \"🚀 Installing ($vars.name)...\"
# Configuration
SERVICE_NAME=\"${SERVICE_NAME:-($vars.name)}\"
SERVICE_VERSION=\"${SERVICE_VERSION:-latest}\"
NAMESPACE=\"${NAMESPACE:-default}\"
REPLICAS=\"${REPLICAS:-1}\"
PORT=\"${PORT:-($vars.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 if it doesn't exist
echo \"🏗️ Creating namespace...\"
kubectl create namespace \"$NAMESPACE\" --dry-run=client -o yaml | kubectl apply -f -
# Apply deployment
echo \"🚢 Applying deployment...\"
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 \"✅ ($vars.display_name) installed successfully\"
echo \"🔍 Check status with: kubectl get pods -n $NAMESPACE -l app=$SERVICE_NAME\"
"
$content | save ($default_path | path join $"install-($vars.name).sh")
# Make script executable
chmod +x ($default_path | path join $"install-($vars.name).sh")
print $" ✓ Created install-($vars.name).sh"
}
# Create README
def create_readme [taskserv_path: string, vars: record] {
let content = $"# ($vars.display_name) Taskserv
($vars.description)
## Overview
This taskserv provides deployment and management capabilities for ($vars.display_name) in Kubernetes environments.
## Configuration
### Basic Configuration
```kcl
import taskservs.($vars.category).($vars.name).kcl.($vars.name) as ($vars.name_snake)
($vars.name_snake)_config: ($vars.name_snake).($vars.name_pascal) = ($vars.name_snake).($vars.name_snake)_config {
# Customize configuration
version = \"1.2.3\"
replicas = 2
port = ($vars.port)
resources = {
cpu = \"200m\"
memory = \"256Mi\"
limits = {
cpu = \"1000m\"
memory = \"1Gi\"
}
}
}
```
### Advanced Configuration
```kcl
($vars.name_snake)_production: ($vars.name_snake).($vars.name_pascal) = ($vars.name_snake).($vars.name_snake)_config {
# Production settings
replicas = 3
resources = {
cpu = \"500m\"
memory = \"512Mi\"
limits = {
cpu = \"2000m\"
memory = \"2Gi\"
}
}
health = {
enabled = true
path = \"/healthz\"
initial_delay = 60
period = 15
}
config = {
\"log_level\" = \"info\"
\"metrics_enabled\" = \"true\"
}
}
```
## Usage
### Deploy with Provisioning System
```bash
# Deploy to infrastructure
provisioning/core/cli/provisioning taskserv create ($vars.name) --infra my-infra --check
# Generate configuration
provisioning/core/cli/provisioning taskserv generate ($vars.name) --infra my-infra
```
### Direct Installation
```bash
# Set environment variables
export SERVICE_VERSION=\"1.2.3\"
export NAMESPACE=\"production\"
export REPLICAS=\"3\"
# Run installation script
cd provisioning/extensions/taskservs/($vars.category)/($vars.name)/default
./install-($vars.name).sh
```
## Configuration Options
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| `name` | str | `($vars.name)` | Service name |
| `version` | str | `latest` | Container image version |
| `namespace` | str | `default` | Kubernetes namespace |
| `replicas` | int | `1` | Number of replicas |
| `port` | int | `($vars.port)` | Service port |
| `resources.cpu` | str | `100m` | CPU request |
| `resources.memory` | str | `128Mi` | Memory request |
| `health.enabled` | bool | `true` | Enable health checks |
| `health.path` | str | `/health` | Health check endpoint |
| `config` | object | `{}` | Additional configuration |
## Dependencies
- Kubernetes cluster
- kubectl configured
- Container image: `($vars.name):latest`
## Troubleshooting
### Check Pod Status
```bash
kubectl get pods -n <namespace> -l app=($vars.name)
kubectl describe pod <pod-name> -n <namespace>
```
### View Logs
```bash
kubectl logs -f deployment/($vars.name) -n <namespace>
```
### Debug Configuration
```bash
# Test KCL configuration
kcl run provisioning/extensions/taskservs/($vars.category)/($vars.name)/kcl/($vars.name).k
# Validate deployment
provisioning/core/cli/provisioning taskserv create ($vars.name) --infra <infra> --check
```
### Common Issues
1. **Pod not starting**: Check resource requests and image availability
2. **Health check failures**: Verify health check endpoint and timing
3. **Service not accessible**: Check service configuration and network policies
## Development
### Layer Resolution
This taskserv supports the 3-layer architecture:
1. **Core Layer**: Base configuration in this directory
2. **Workspace Layer**: Templates in `provisioning/workspace/templates/taskservs/($vars.category)/($vars.name).k`
3. **Infrastructure Layer**: Overrides in `workspace/infra/<infra>/task-servs/($vars.name).k`
### Testing
```bash
# Test layer resolution
nu -c \"use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution ($vars.name) <infra> <provider>\"
# Discover taskserv
nu -c \"use provisioning/core/nulib/taskservs/discover.nu *; get-taskserv-info ($vars.name)\"
```
## Contributing
1. Follow the taskserv development guidelines
2. Update version information when making changes
3. Test across different environments
4. Update this README with any new configuration options
## License
MIT License
## Author
($vars.author)
## Generated
Created on ($vars.date) using the Taskserv Helper Tool.
"
$content | save ($taskserv_path | path join "README.md")
print $" ✓ Created README.md"
}
# Create workspace template
def create_workspace_template [name?: string] {
if ($name == null) or ($name | is-empty) {
print "📋 Create workspace template for existing taskserv"
let taskservs = (discover-taskservs | select name category)
print "Available taskservs:"
for i in 0..($taskservs | length | $in - 1) {
let ts = ($taskservs | get $i)
print $" ($i + 1). ($ts.name) (($ts.category))"
}
let choice = input "Select taskserv number:"
let index = (($choice | into int) - 1)
if $index < 0 or $index >= ($taskservs | length) {
error make { msg: "Invalid selection" }
}
let selected = ($taskservs | get $index)
create_template_for_taskserv $selected.name $selected.category
} else {
let taskserv_info = get-taskserv-info $name
create_template_for_taskserv $taskserv_info.name $taskserv_info.group
}
}
# Create template for specific taskserv
def create_template_for_taskserv [name: string, category: string] {
print $"📝 Creating workspace template for ($name)"
let template_dir = ("provisioning/workspace/templates/taskservs" | path join $category)
let template_file = ($template_dir | path join $"($name).k")
mkdir $template_dir
let name_snake = ($name | str replace -a "-" "_")
let name_pascal = ($name | str replace -a "-" " " | str title-case | str replace -a " " "")
let content = $"# Workspace template for ($name) taskserv
import taskservs.($category).($name).kcl.($name) as base
# Template configuration extending base
($name_snake)_template: base.($name_pascal) = base.($name_snake)_config {
# Template customizations for workspace layer
# These can be overridden at the infrastructure layer
# Common template settings
version = \"stable\"
# Adjust for template environment
replicas = 2 # Default for multi-replica setup
# Template resource defaults
resources = base.($name_snake)_config.resources {
cpu = \"200m\"
memory = \"256Mi\"
limits = {
cpu = \"1000m\"
memory = \"1Gi\"
}
}
# Template-level configuration
config = {
\"environment\" = \"template\"
\"log_level\" = \"info\"
}
}
"
$content | save $template_file
print $"✅ Created workspace template: ($template_file)"
print ""
print "🧪 Test the template:"
print $" nu -c \"use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution ($name) <infra> <provider>\""
}
# Show discovery information
def show_discovery_info [] {
print "🔍 Taskserv Discovery Information"
print ""
let taskservs = discover-taskservs
let total = ($taskservs | length)
let by_group = ($taskservs | group-by group | transpose group taskservs | each { |row| {group: $row.group, count: ($row.taskservs | length)} })
print $"📊 Total taskservs discovered: ($total)"
print ""
print "📂 By category:"
for group_info in $by_group {
print $" ($group_info.group): ($group_info.count) taskservs"
let group_taskservs = ($taskservs | where group == $group_info.group | get name)
for taskserv in $group_taskservs {
print $" • ($taskserv)"
}
}
print ""
print "🛠️ Available commands:"
print " nu -c \"use provisioning/core/nulib/taskservs/discover.nu *; discover-taskservs\""
print " nu -c \"use provisioning/core/nulib/taskservs/discover.nu *; search-taskservs <query>\""
print " nu -c \"use provisioning/workspace/tools/layer-utils.nu *; show_layer_stats\""
}
# Show help information
def show_help [] {
print "🚀 Taskserv Helper Tool"
print ""
print "USAGE:"
print " nu provisioning/tools/create-taskserv-helper.nu <command> [options]"
print ""
print "COMMANDS:"
print " interactive Launch interactive taskserv creator"
print " create <name> Create taskserv with specified name"
print " template [name] Create workspace template for taskserv"
print " discover Show discovery information"
print " help Show this help message"
print ""
print "CREATE OPTIONS:"
print " --category <cat> Taskserv category (required for create)"
print " --port <port> Service port (default: 8080)"
print " --description <desc> Service description"
print " --author <author> Author name (default: Developer)"
print " --output <dir> Output directory (default: provisioning/extensions)"
print ""
print "CATEGORIES:"
print " container-runtime Container runtime engines"
print " databases Database services"
print " development Development tools"
print " infrastructure System infrastructure"
print " kubernetes Kubernetes orchestration"
print " networking Network services"
print " storage Storage solutions"
print ""
print "EXAMPLES:"
print " # Interactive creation"
print " nu provisioning/tools/create-taskserv-helper.nu interactive"
print ""
print " # Create API service"
print " nu provisioning/tools/create-taskserv-helper.nu create my-api \\"
print " --category development \\"
print " --port 8080 \\"
print " --description \"My REST API service\""
print ""
print " # Create database service"
print " nu provisioning/tools/create-taskserv-helper.nu create my-db \\"
print " --category databases \\"
print " --port 5432 \\"
print " --author \"Database Team\""
print ""
print " # Create workspace template"
print " nu provisioning/tools/create-taskserv-helper.nu template my-api"
print ""
print "📖 For more information, see:"
print " docs/development/TASKSERV_DEVELOPER_GUIDE.md"
print " docs/development/TASKSERV_QUICK_GUIDE.md"
}
# String utility functions
def "str title-case" [] {
let text = $in
$text | split row " " | each { |word|
if ($word | is-empty) {
$word
} else {
($word | str substring 0..1 | str upcase) + ($word | str substring 1..)
}
} | str join " "
}