First commit of golang CLI.

This commit is contained in:
2025-08-31 11:51:11 -07:00
parent 4ca06aecb6
commit f0a2098f11
51 changed files with 8840 additions and 0 deletions

View File

@@ -0,0 +1,265 @@
package setup
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/wild-cloud/wild-cli/internal/config"
"github.com/wild-cloud/wild-cli/internal/environment"
"github.com/wild-cloud/wild-cli/internal/external"
"github.com/wild-cloud/wild-cli/internal/output"
)
var (
skipInstaller bool
skipHardware bool
)
func newClusterCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "cluster",
Short: "Set up Kubernetes cluster",
Long: `Set up the Kubernetes cluster infrastructure using Talos Linux.
This command configures Talos Linux nodes and bootstraps the Kubernetes cluster.
Examples:
wild setup cluster
wild setup cluster --skip-installer
wild setup cluster --skip-hardware`,
RunE: runCluster,
}
cmd.Flags().BoolVar(&skipInstaller, "skip-installer", false, "skip installer image generation")
cmd.Flags().BoolVar(&skipHardware, "skip-hardware", false, "skip node hardware detection")
return cmd
}
func runCluster(cmd *cobra.Command, args []string) error {
output.Header("Wild Cloud Cluster Setup")
// Initialize environment
env := environment.New()
if err := env.RequiresProject(); err != nil {
return err
}
// Check external tools
toolManager := external.NewManager()
if err := toolManager.CheckTools(cmd.Context(), []string{"talosctl"}); err != nil {
return fmt.Errorf("required tools not available: %w", err)
}
// Load configuration
configMgr := config.NewManager(env.ConfigPath(), env.SecretsPath())
// Get cluster configuration
clusterName, err := getConfigString(configMgr, "cluster.name")
if err != nil {
return fmt.Errorf("cluster name not configured: %w", err)
}
vip, err := getConfigString(configMgr, "cluster.vip")
if err != nil {
return fmt.Errorf("cluster VIP not configured: %w", err)
}
output.Info("Cluster: " + clusterName)
output.Info("VIP: " + vip)
// Phase 1: Generate Talos configuration
output.Info("\n=== Phase 1: Generating Talos Configuration ===")
configDir := filepath.Join(env.WildCloudDir(), "talos")
if err := os.MkdirAll(configDir, 0755); err != nil {
return fmt.Errorf("creating config directory: %w", err)
}
talosctl := toolManager.Talosctl()
clusterEndpoint := "https://" + vip + ":6443"
if err := talosctl.GenerateConfig(cmd.Context(), clusterName, clusterEndpoint, configDir); err != nil {
return fmt.Errorf("generating talos config: %w", err)
}
output.Success("Talos configuration generated")
// Phase 2: Node configuration
if !skipHardware {
output.Info("\n=== Phase 2: Detecting Nodes ===")
if err := detectAndConfigureNodes(cmd.Context(), configMgr, talosctl, configDir); err != nil {
return fmt.Errorf("configuring nodes: %w", err)
}
} else {
output.Info("Skipping node hardware detection")
}
// Phase 3: Bootstrap cluster
output.Info("\n=== Phase 3: Bootstrapping Cluster ===")
if err := bootstrapCluster(cmd.Context(), configMgr, talosctl, configDir); err != nil {
return fmt.Errorf("bootstrapping cluster: %w", err)
}
output.Success("Cluster setup completed successfully!")
output.Info("")
output.Info("Next steps:")
output.Info(" wild setup services # Install cluster services")
output.Info(" kubectl get nodes # Verify cluster")
return nil
}
// detectAndConfigureNodes detects and configures cluster nodes
func detectAndConfigureNodes(ctx context.Context, configMgr *config.Manager, talosctl *external.TalosctlTool, configDir string) error {
// Get nodes from configuration
nodesConfig, err := configMgr.Get("cluster.nodes")
if err != nil || nodesConfig == nil {
output.Warning("No nodes configured")
output.Info("Add nodes to config: wild config set cluster.nodes '[{\"ip\": \"192.168.1.10\", \"role\": \"controlplane\"}]'")
return nil
}
nodes, ok := nodesConfig.([]interface{})
if !ok {
return fmt.Errorf("invalid nodes configuration")
}
if len(nodes) == 0 {
output.Warning("No nodes configured")
return nil
}
output.Info(fmt.Sprintf("Found %d nodes in configuration", len(nodes)))
// Configure each node
for i, nodeConfig := range nodes {
nodeMap, ok := nodeConfig.(map[string]interface{})
if !ok {
output.Warning(fmt.Sprintf("Invalid node %d configuration", i))
continue
}
nodeIP, exists := nodeMap["ip"]
if !exists {
output.Warning(fmt.Sprintf("Node %d missing IP address", i))
continue
}
nodeRole, exists := nodeMap["role"]
if !exists {
nodeRole = "worker"
}
output.Info(fmt.Sprintf("Configuring node %s (%s)", nodeIP, nodeRole))
// Apply configuration to node
var configFile string
if nodeRole == "controlplane" {
configFile = filepath.Join(configDir, "controlplane.yaml")
} else {
configFile = filepath.Join(configDir, "worker.yaml")
}
talosctl.SetEndpoints([]string{fmt.Sprintf("%v", nodeIP)})
if err := talosctl.ApplyConfig(ctx, configFile, true); err != nil {
output.Warning(fmt.Sprintf("Failed to configure node %s: %v", nodeIP, err))
} else {
output.Success(fmt.Sprintf("Node %s configured", nodeIP))
}
}
return nil
}
// bootstrapCluster bootstraps the Kubernetes cluster
func bootstrapCluster(ctx context.Context, configMgr *config.Manager, talosctl *external.TalosctlTool, configDir string) error {
// Get first controlplane node
nodesConfig, err := configMgr.Get("cluster.nodes")
if err != nil || nodesConfig == nil {
return fmt.Errorf("no nodes configured")
}
nodes, ok := nodesConfig.([]interface{})
if !ok || len(nodes) == 0 {
return fmt.Errorf("invalid nodes configuration")
}
// Find first controlplane node
var bootstrapNode string
for _, nodeConfig := range nodes {
nodeMap, ok := nodeConfig.(map[string]interface{})
if !ok {
continue
}
nodeIP, exists := nodeMap["ip"]
if !exists {
continue
}
nodeRole, exists := nodeMap["role"]
if exists && nodeRole == "controlplane" {
bootstrapNode = fmt.Sprintf("%v", nodeIP)
break
}
}
if bootstrapNode == "" {
return fmt.Errorf("no controlplane node found")
}
output.Info("Bootstrap node: " + bootstrapNode)
// Set talosconfig
talosconfig := filepath.Join(configDir, "talosconfig")
talosctl.SetTalosconfig(talosconfig)
talosctl.SetEndpoints([]string{bootstrapNode})
talosctl.SetNodes([]string{bootstrapNode})
// Bootstrap cluster
if err := talosctl.Bootstrap(ctx); err != nil {
return fmt.Errorf("bootstrapping cluster: %w", err)
}
output.Success("Cluster bootstrapped")
// Generate kubeconfig
output.Info("Generating kubeconfig...")
kubeconfigPath := filepath.Join(configDir, "kubeconfig")
if err := talosctl.Kubeconfig(ctx, kubeconfigPath, true); err != nil {
output.Warning("Failed to generate kubeconfig: " + err.Error())
} else {
output.Success("Kubeconfig generated: " + kubeconfigPath)
output.Info("Set KUBECONFIG=" + kubeconfigPath)
}
return nil
}
// getConfigString gets a string value from config with validation
func getConfigString(configMgr *config.Manager, path string) (string, error) {
value, err := configMgr.Get(path)
if err != nil {
return "", err
}
if value == nil {
return "", fmt.Errorf("config value '%s' not set", path)
}
strValue, ok := value.(string)
if !ok {
return "", fmt.Errorf("config value '%s' is not a string", path)
}
if strValue == "" {
return "", fmt.Errorf("config value '%s' is empty", path)
}
return strValue, nil
}

View File

@@ -0,0 +1,190 @@
package setup
import (
"fmt"
"os"
"path/filepath"
"github.com/spf13/cobra"
"github.com/wild-cloud/wild-cli/internal/output"
)
func newScaffoldCommand() *cobra.Command {
return &cobra.Command{
Use: "scaffold",
Short: "Initialize a new Wild Cloud project",
Long: `Initialize a new Wild Cloud project directory with configuration templates.`,
RunE: runScaffold,
}
}
func runScaffold(cmd *cobra.Command, args []string) error {
output.Header("Wild Cloud Project Initialization")
// Get current directory
cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("getting current directory: %w", err)
}
// Check if already a Wild Cloud project
if _, err := os.Stat(filepath.Join(cwd, ".wildcloud")); err == nil {
return fmt.Errorf("current directory is already a Wild Cloud project")
}
output.Info("Initializing Wild Cloud project in: " + cwd)
// Create .wildcloud directory
wildcloudDir := filepath.Join(cwd, ".wildcloud")
if err := os.MkdirAll(wildcloudDir, 0755); err != nil {
return fmt.Errorf("creating .wildcloud directory: %w", err)
}
// Create cache directory
cacheDir := filepath.Join(wildcloudDir, "cache")
if err := os.MkdirAll(cacheDir, 0755); err != nil {
return fmt.Errorf("creating cache directory: %w", err)
}
// Create apps directory
appsDir := filepath.Join(cwd, "apps")
if err := os.MkdirAll(appsDir, 0755); err != nil {
return fmt.Errorf("creating apps directory: %w", err)
}
// Create config.yaml with basic structure
configPath := filepath.Join(cwd, "config.yaml")
configContent := `# Wild Cloud Configuration
cluster:
name: ""
domain: ""
vip: ""
nodes: []
apps: {}
services: {}
`
if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil {
return fmt.Errorf("creating config.yaml: %w", err)
}
// Create secrets.yaml with basic structure
secretsPath := filepath.Join(cwd, "secrets.yaml")
secretsContent := `# Wild Cloud Secrets
# This file contains sensitive information and should not be committed to version control
cluster:
secrets: {}
apps: {}
`
if err := os.WriteFile(secretsPath, []byte(secretsContent), 0600); err != nil {
return fmt.Errorf("creating secrets.yaml: %w", err)
}
// Create .gitignore to exclude secrets
gitignorePath := filepath.Join(cwd, ".gitignore")
gitignoreContent := `# Wild Cloud secrets and sensitive data
secrets.yaml
*.key
*.crt
*.pem
# Talos configuration files
*.talosconfig
controlplane.yaml
worker.yaml
# Kubernetes config
kubeconfig
# Backup files
*.bak
*.backup
# Cache and temporary files
.wildcloud/cache/
*.tmp
*.temp
`
if err := os.WriteFile(gitignorePath, []byte(gitignoreContent), 0644); err != nil {
output.Warning("Failed to create .gitignore: " + err.Error())
}
// Create README.md with basic information
readmePath := filepath.Join(cwd, "README.md")
readmeContent := `# Wild Cloud Project
This is a Wild Cloud personal infrastructure project.
## Getting Started
1. Configure your cluster settings:
` + "```bash" + `
wild config set cluster.name my-cluster
wild config set cluster.domain example.com
wild config set cluster.vip 192.168.1.100
` + "```" + `
2. Set up your cluster:
` + "```bash" + `
wild setup cluster
` + "```" + `
3. Install cluster services:
` + "```bash" + `
wild setup services
` + "```" + `
4. Deploy applications:
` + "```bash" + `
wild app list
wild app fetch nextcloud
wild app add nextcloud
wild app deploy nextcloud
` + "```" + `
## Directory Structure
- ` + "`config.yaml`" + ` - Cluster and application configuration
- ` + "`secrets.yaml`" + ` - Sensitive data (not committed to git)
- ` + "`apps/`" + ` - Application configurations
- ` + "`.wildcloud/`" + ` - Wild Cloud metadata and cache
## Commands
- ` + "`wild config`" + ` - Manage configuration
- ` + "`wild secret`" + ` - Manage secrets
- ` + "`wild setup`" + ` - Set up infrastructure
- ` + "`wild app`" + ` - Manage applications
- ` + "`wild cluster`" + ` - Manage cluster
- ` + "`wild backup`" + ` - Backup system
For more information, run ` + "`wild --help`" + `
`
if err := os.WriteFile(readmePath, []byte(readmeContent), 0644); err != nil {
output.Warning("Failed to create README.md: " + err.Error())
}
output.Success("Wild Cloud project initialized successfully!")
output.Info("")
output.Info("Next steps:")
output.Info(" 1. Configure your cluster: wild config set cluster.name my-cluster")
output.Info(" 2. Set up your cluster: wild setup cluster")
output.Info(" 3. Deploy services: wild setup services")
output.Info("")
output.Info("Project structure created:")
output.Info(" ├── .wildcloud/ # Project metadata")
output.Info(" ├── apps/ # Application configurations")
output.Info(" ├── config.yaml # Cluster configuration")
output.Info(" ├── secrets.yaml # Sensitive data")
output.Info(" ├── .gitignore # Git ignore rules")
output.Info(" └── README.md # Project documentation")
return nil
}

View File

@@ -0,0 +1,564 @@
package setup
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/spf13/cobra"
"github.com/wild-cloud/wild-cli/internal/config"
"github.com/wild-cloud/wild-cli/internal/environment"
"github.com/wild-cloud/wild-cli/internal/external"
"github.com/wild-cloud/wild-cli/internal/output"
)
var (
skipInstall bool
)
func newServicesCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "services",
Short: "Set up cluster services",
Long: `Set up essential cluster services like ingress, DNS, and monitoring.
This command generates service configurations and installs core Kubernetes services
including MetalLB, Traefik, cert-manager, and others.
Examples:
wild setup services
wild setup services --skip-install`,
RunE: runServices,
}
cmd.Flags().BoolVar(&skipInstall, "skip-install", false, "generate service configs but skip installation")
return cmd
}
func runServices(cmd *cobra.Command, args []string) error {
output.Header("Wild Cloud Services Setup")
// Initialize environment
env := environment.New()
if err := env.RequiresProject(); err != nil {
return err
}
// Check external tools
toolManager := external.NewManager()
if err := toolManager.CheckTools(cmd.Context(), []string{"kubectl"}); err != nil {
return fmt.Errorf("required tools not available: %w", err)
}
// Load configuration
configMgr := config.NewManager(env.ConfigPath(), env.SecretsPath())
// Check cluster configuration
clusterName, err := getConfigString(configMgr, "cluster.name")
if err != nil {
return fmt.Errorf("cluster configuration is missing: %w", err)
}
output.Info("Cluster: " + clusterName)
// Check kubectl connectivity
kubectl := toolManager.Kubectl()
if err := checkKubectlConnectivity(cmd.Context(), kubectl); err != nil {
return fmt.Errorf("kubectl is not configured or cluster is not accessible: %w", err)
}
output.Success("Cluster is accessible")
// Phase 1: Generate cluster services setup files
output.Info("\n=== Phase 1: Generating Service Configurations ===")
if err := generateClusterServices(cmd.Context(), env, configMgr); err != nil {
return fmt.Errorf("generating service configurations: %w", err)
}
// Phase 2: Install cluster services
if !skipInstall {
output.Info("\n=== Phase 2: Installing Cluster Services ===")
if err := installClusterServices(cmd.Context(), env, kubectl); err != nil {
return fmt.Errorf("installing cluster services: %w", err)
}
} else {
output.Info("Skipping cluster services installation (--skip-install specified)")
output.Info("You can install them later with: wild cluster services deploy")
}
// Summary output
output.Success("Wild Cloud Services Setup Complete!")
output.Info("")
if !skipInstall {
// Get internal domain for next steps
internalDomain, err := configMgr.Get("cloud.internalDomain")
domain := "your-internal-domain"
if err == nil && internalDomain != nil {
if domainStr, ok := internalDomain.(string); ok {
domain = domainStr
}
}
output.Info("Next steps:")
output.Info(" 1. Access the dashboard at: https://dashboard." + domain)
output.Info(" 2. Get the dashboard token with: wild dashboard token")
output.Info("")
output.Info("To verify components, run:")
output.Info(" - kubectl get pods -n cert-manager")
output.Info(" - kubectl get pods -n externaldns")
output.Info(" - kubectl get pods -n kubernetes-dashboard")
output.Info(" - kubectl get clusterissuers")
} else {
output.Info("Next steps:")
output.Info(" 1. Ensure your cluster is running and kubectl is configured")
output.Info(" 2. Install services with: wild cluster services deploy")
output.Info(" 3. Verify components are running correctly")
}
output.Success("Wild Cloud setup completed!")
return nil
}
// generateClusterServices generates cluster service configurations
func generateClusterServices(ctx context.Context, env *environment.Environment, configMgr *config.Manager) error {
// This function replicates wild-cluster-services-generate functionality
output.Info("Generating cluster services setup files...")
wcRoot := env.WCRoot()
if wcRoot == "" {
return fmt.Errorf("WC_ROOT not set")
}
sourceDir := filepath.Join(wcRoot, "setup", "cluster-services")
destDir := filepath.Join(env.WildCloudDir(), "setup", "cluster-services")
// Check if source directory exists
if _, err := os.Stat(sourceDir); os.IsNotExist(err) {
return fmt.Errorf("cluster setup source directory not found: %s", sourceDir)
}
// Force regeneration, removing existing files
if _, err := os.Stat(destDir); err == nil {
output.Info("Force regeneration enabled, removing existing files...")
if err := os.RemoveAll(destDir); err != nil {
return fmt.Errorf("removing existing setup directory: %w", err)
}
}
// Create destination directory
setupBaseDir := filepath.Join(env.WildCloudDir(), "setup")
if err := os.MkdirAll(setupBaseDir, 0755); err != nil {
return fmt.Errorf("creating setup directory: %w", err)
}
// Copy README if it doesn't exist
readmePath := filepath.Join(setupBaseDir, "README.md")
if _, err := os.Stat(readmePath); os.IsNotExist(err) {
sourceReadme := filepath.Join(wcRoot, "setup", "README.md")
if _, err := os.Stat(sourceReadme); err == nil {
if err := copyFile(sourceReadme, readmePath); err != nil {
output.Warning("Failed to copy README.md: " + err.Error())
}
}
}
// Create destination directory
if err := os.MkdirAll(destDir, 0755); err != nil {
return fmt.Errorf("creating destination directory: %w", err)
}
// Copy and compile cluster setup files
output.Info("Copying and compiling cluster setup files from repository...")
// First, copy root-level files from setup/cluster-services/
if err := copyRootServiceFiles(sourceDir, destDir); err != nil {
return fmt.Errorf("copying root service files: %w", err)
}
// Then, process each service directory
if err := processServiceDirectories(sourceDir, destDir, configMgr); err != nil {
return fmt.Errorf("processing service directories: %w", err)
}
// Verify required configuration
if err := verifyServiceConfiguration(configMgr); err != nil {
output.Warning("Configuration verification warnings: " + err.Error())
}
output.Success("Cluster setup files copied and compiled")
output.Info("Generated setup directory: " + destDir)
// List available services
services, err := getAvailableServices(destDir)
if err != nil {
return fmt.Errorf("listing available services: %w", err)
}
output.Info("Available services:")
for _, service := range services {
output.Info(" - " + service)
}
return nil
}
// installClusterServices installs the cluster services
func installClusterServices(ctx context.Context, env *environment.Environment, kubectl *external.KubectlTool) error {
setupDir := filepath.Join(env.WildCloudDir(), "setup", "cluster-services")
// Check if cluster setup directory exists
if _, err := os.Stat(setupDir); os.IsNotExist(err) {
return fmt.Errorf("cluster services setup directory not found: %s", setupDir)
}
output.Info("Installing cluster services...")
// Install services in dependency order
servicesToInstall := []string{
"metallb",
"longhorn",
"traefik",
"coredns",
"cert-manager",
"externaldns",
"kubernetes-dashboard",
"nfs",
"docker-registry",
}
// Filter to only include services that actually exist
existingServices := []string{}
for _, service := range servicesToInstall {
installScript := filepath.Join(setupDir, service, "install.sh")
if _, err := os.Stat(installScript); err == nil {
existingServices = append(existingServices, service)
}
}
if len(existingServices) == 0 {
return fmt.Errorf("no installable services found")
}
output.Info(fmt.Sprintf("Services to install: %s", strings.Join(existingServices, ", ")))
// Install services
installedCount := 0
failedCount := 0
for _, service := range existingServices {
output.Info(fmt.Sprintf("\n--- Installing %s ---", service))
installScript := filepath.Join(setupDir, service, "install.sh")
if err := runServiceInstaller(ctx, setupDir, service, installScript); err != nil {
output.Error(fmt.Sprintf("%s installation failed: %v", service, err))
failedCount++
} else {
output.Success(fmt.Sprintf("%s installed successfully", service))
installedCount++
}
}
// Summary
output.Info("\nInstallation Summary:")
output.Success(fmt.Sprintf("Successfully installed: %d services", installedCount))
if failedCount > 0 {
output.Warning(fmt.Sprintf("Failed to install: %d services", failedCount))
}
if failedCount == 0 {
output.Success("All cluster services installed successfully!")
} else {
return fmt.Errorf("some services failed to install")
}
return nil
}
// copyRootServiceFiles copies root-level files from source to destination
func copyRootServiceFiles(sourceDir, destDir string) error {
entries, err := os.ReadDir(sourceDir)
if err != nil {
return err
}
for _, entry := range entries {
if !entry.IsDir() {
srcPath := filepath.Join(sourceDir, entry.Name())
dstPath := filepath.Join(destDir, entry.Name())
output.Info(" Copying: " + entry.Name())
if err := copyFile(srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}
// processServiceDirectories processes each service directory
func processServiceDirectories(sourceDir, destDir string, configMgr *config.Manager) error {
entries, err := os.ReadDir(sourceDir)
if err != nil {
return err
}
// Create template engine
engine, err := config.NewTemplateEngine(configMgr)
if err != nil {
return fmt.Errorf("creating template engine: %w", err)
}
for _, entry := range entries {
if !entry.IsDir() {
continue
}
serviceName := entry.Name()
serviceDir := filepath.Join(sourceDir, serviceName)
destServiceDir := filepath.Join(destDir, serviceName)
output.Info("Processing service: " + serviceName)
// Create destination service directory
if err := os.MkdirAll(destServiceDir, 0755); err != nil {
return err
}
// Process service files
if err := processServiceFiles(serviceDir, destServiceDir, engine); err != nil {
return fmt.Errorf("processing service %s: %w", serviceName, err)
}
}
return nil
}
// processServiceFiles processes files in a service directory
func processServiceFiles(serviceDir, destServiceDir string, engine *config.TemplateEngine) error {
entries, err := os.ReadDir(serviceDir)
if err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(serviceDir, entry.Name())
dstPath := filepath.Join(destServiceDir, entry.Name())
if entry.Name() == "kustomize.template" {
// Compile kustomize.template to kustomize directory
if entry.IsDir() {
output.Info(" Compiling kustomize templates")
kustomizeDir := filepath.Join(destServiceDir, "kustomize")
if err := processTemplateDirectory(srcPath, kustomizeDir, engine); err != nil {
return err
}
}
} else if entry.IsDir() {
// Copy other directories recursively
if err := copyDir(srcPath, dstPath); err != nil {
return err
}
} else {
// Process individual files
if err := processServiceFile(srcPath, dstPath, engine); err != nil {
return err
}
}
}
return nil
}
// processServiceFile processes a single service file
func processServiceFile(srcPath, dstPath string, engine *config.TemplateEngine) error {
content, err := os.ReadFile(srcPath)
if err != nil {
return err
}
// Check if file contains template syntax
if strings.Contains(string(content), "{{") {
output.Info(" Compiling: " + filepath.Base(srcPath))
processed, err := engine.Process(string(content))
if err != nil {
return fmt.Errorf("processing template: %w", err)
}
return os.WriteFile(dstPath, []byte(processed), 0644)
} else {
return copyFile(srcPath, dstPath)
}
}
// processTemplateDirectory processes an entire template directory
func processTemplateDirectory(srcDir, dstDir string, engine *config.TemplateEngine) error {
if err := os.RemoveAll(dstDir); err != nil && !os.IsNotExist(err) {
return err
}
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
if err != nil {
return err
}
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return err
}
dstPath := filepath.Join(dstDir, relPath)
if info.IsDir() {
return os.MkdirAll(dstPath, info.Mode())
}
content, err := os.ReadFile(path)
if err != nil {
return err
}
// Process template content
if strings.Contains(string(content), "{{") {
processed, err := engine.Process(string(content))
if err != nil {
return fmt.Errorf("processing template %s: %w", relPath, err)
}
content = []byte(processed)
}
// Create parent directory
if err := os.MkdirAll(filepath.Dir(dstPath), 0755); err != nil {
return err
}
return os.WriteFile(dstPath, content, info.Mode())
})
}
// verifyServiceConfiguration verifies required configuration
func verifyServiceConfiguration(configMgr *config.Manager) error {
missingConfig := []string{}
// Check essential configuration values
requiredConfigs := []string{
"cluster.name",
"cloud.domain",
"cluster.ipAddressPool",
"operator.email",
}
for _, configPath := range requiredConfigs {
if value, err := configMgr.Get(configPath); err != nil || value == nil {
missingConfig = append(missingConfig, configPath)
}
}
if len(missingConfig) > 0 {
return fmt.Errorf("missing required configuration values: %s", strings.Join(missingConfig, ", "))
}
return nil
}
// getAvailableServices returns list of available services
func getAvailableServices(setupDir string) ([]string, error) {
var services []string
entries, err := os.ReadDir(setupDir)
if err != nil {
return nil, err
}
for _, entry := range entries {
if entry.IsDir() {
installScript := filepath.Join(setupDir, entry.Name(), "install.sh")
if _, err := os.Stat(installScript); err == nil {
services = append(services, entry.Name())
}
}
}
return services, nil
}
// checkKubectlConnectivity checks if kubectl can connect to the cluster
func checkKubectlConnectivity(ctx context.Context, kubectl *external.KubectlTool) error {
// Try to get cluster info
_, err := kubectl.Execute(ctx, "cluster-info")
if err != nil {
return fmt.Errorf("cluster not accessible: %w", err)
}
return nil
}
// runServiceInstaller runs a service installation script
func runServiceInstaller(ctx context.Context, setupDir, serviceName, installScript string) error {
// Change to the service directory and run install.sh
serviceDir := filepath.Join(setupDir, serviceName)
// Execute the install script using bash
bashTool := external.NewBaseTool("bash", "bash")
// Change to the service directory by setting working directory in the execution context
oldDir, err := os.Getwd()
if err != nil {
return fmt.Errorf("getting current directory: %w", err)
}
if err := os.Chdir(serviceDir); err != nil {
return fmt.Errorf("changing to service directory: %w", err)
}
defer func() {
_ = os.Chdir(oldDir)
}()
_, err = bashTool.Execute(ctx, "install.sh")
if err != nil {
return fmt.Errorf("install script failed: %w", err)
}
return nil
}
// copyFile copies a single file
func copyFile(src, dst string) error {
data, err := os.ReadFile(src)
if err != nil {
return err
}
return os.WriteFile(dst, data, 0644)
}
// copyDir recursively copies a directory
func copyDir(src, dst string) error {
entries, err := os.ReadDir(src)
if err != nil {
return err
}
if err := os.MkdirAll(dst, 0755); err != nil {
return err
}
for _, entry := range entries {
srcPath := filepath.Join(src, entry.Name())
dstPath := filepath.Join(dst, entry.Name())
if entry.IsDir() {
if err := copyDir(srcPath, dstPath); err != nil {
return err
}
} else {
if err := copyFile(srcPath, dstPath); err != nil {
return err
}
}
}
return nil
}

View File

@@ -0,0 +1,30 @@
package setup
import (
"github.com/spf13/cobra"
)
// NewSetupCommand creates the setup command and its subcommands
func NewSetupCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "setup",
Short: "Set up Wild Cloud infrastructure",
Long: `Set up Wild Cloud infrastructure components.
This command provides the setup workflow for initializing and configuring
your Wild Cloud personal infrastructure.`,
}
// Add subcommands
cmd.AddCommand(
newScaffoldCommand(),
newClusterCommand(),
newServicesCommand(),
)
return cmd
}
// newScaffoldCommand is implemented in scaffold.go
// newClusterCommand is implemented in cluster.go
// newServicesCommand is implemented in services.go