First commit of golang CLI.
This commit is contained in:
265
wild-cli/cmd/wild/setup/cluster.go
Normal file
265
wild-cli/cmd/wild/setup/cluster.go
Normal 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
|
||||
}
|
Reference in New Issue
Block a user