diff --git a/internal/api/v1/handlers.go b/internal/api/v1/handlers.go index 8068241..675de1e 100644 --- a/internal/api/v1/handlers.go +++ b/internal/api/v1/handlers.go @@ -96,6 +96,7 @@ func (api *API) RegisterRoutes(r *mux.Router) { r.HandleFunc("/api/v1/instances/{name}/nodes/{node}", api.NodeGet).Methods("GET") r.HandleFunc("/api/v1/instances/{name}/nodes/{node}", api.NodeUpdate).Methods("PUT") r.HandleFunc("/api/v1/instances/{name}/nodes/{node}/apply", api.NodeApply).Methods("POST") + r.HandleFunc("/api/v1/instances/{name}/nodes/{node}/reset", api.NodeReset).Methods("POST") r.HandleFunc("/api/v1/instances/{name}/nodes/{node}", api.NodeDelete).Methods("DELETE") // PXE Asset management (schematic@version composite key) diff --git a/internal/api/v1/handlers_node.go b/internal/api/v1/handlers_node.go index 303feb4..f362cf9 100644 --- a/internal/api/v1/handlers_node.go +++ b/internal/api/v1/handlers_node.go @@ -371,3 +371,28 @@ func (api *API) NodeDiscoveryCancel(w http.ResponseWriter, r *http.Request) { "message": "Discovery cancelled successfully", }) } + +// NodeReset resets a node to maintenance mode +func (api *API) NodeReset(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + instanceName := vars["name"] + nodeIdentifier := vars["node"] + + // Validate instance exists + if err := api.instance.ValidateInstance(instanceName); err != nil { + respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err)) + return + } + + // Reset node + nodeMgr := node.NewManager(api.dataDir, instanceName) + if err := nodeMgr.Reset(instanceName, nodeIdentifier); err != nil { + respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to reset node: %v", err)) + return + } + + respondJSON(w, http.StatusOK, map[string]string{ + "message": "Node reset successfully - now in maintenance mode", + "node": nodeIdentifier, + }) +} diff --git a/internal/node/node.go b/internal/node/node.go index 77f8cb5..80006a2 100644 --- a/internal/node/node.go +++ b/internal/node/node.go @@ -326,7 +326,6 @@ func (m *Manager) detectHardwareWithMode(nodeIP string, insecure bool) (*Hardwar }, nil } - // Apply generates configuration and applies it to node // This follows the wild-node-apply flow: // 1. Auto-fetch templates if missing @@ -587,17 +586,21 @@ func (m *Manager) updateNodeStatus(instanceName string, node *Node) error { } // Update configured flag + configuredValue := "false" if node.Configured { - if err := yq.Set(configPath, basePath+".configured", "true"); err != nil { - return err - } + configuredValue = "true" + } + if err := yq.Set(configPath, basePath+".configured", configuredValue); err != nil { + return err } // Update applied flag + appliedValue := "false" if node.Applied { - if err := yq.Set(configPath, basePath+".applied", "true"); err != nil { - return err - } + appliedValue = "true" + } + if err := yq.Set(configPath, basePath+".applied", appliedValue); err != nil { + return err } return nil @@ -677,3 +680,36 @@ func (m *Manager) FetchTemplates(instanceName string) error { destDir := filepath.Join(instancePath, "setup", "cluster-nodes", "patch.templates") return m.extractEmbeddedTemplates(destDir) } + +// Reset resets a node to maintenance mode +func (m *Manager) Reset(instanceName, nodeIdentifier string) error { + // Get node + node, err := m.Get(instanceName, nodeIdentifier) + if err != nil { + return fmt.Errorf("node not found: %w", err) + } + + // Determine IP to reset + resetIP := node.CurrentIP + if resetIP == "" { + resetIP = node.TargetIP + } + + // Execute reset command with graceful=false and reboot flags + talosconfigPath := tools.GetTalosconfigPath(m.dataDir, instanceName) + cmd := exec.Command("talosctl", "-n", resetIP, "--talosconfig", talosconfigPath, "reset", "--graceful=false", "--reboot") + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to reset node: %w\nOutput: %s", err, string(output)) + } + + // Update node status to maintenance mode + node.Maintenance = true + node.Configured = false + node.Applied = false + if err := m.updateNodeStatus(instanceName, node); err != nil { + return fmt.Errorf("failed to update node status: %w", err) + } + + return nil +}