mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 05:13:01 +00:00
Added delete namespace handler
This commit is contained in:
parent
16eaf9a129
commit
b5109f1ee8
@ -88,6 +88,10 @@ func main() {
|
||||
case "db":
|
||||
cli.HandleDBCommand(args)
|
||||
|
||||
// Namespace management
|
||||
case "namespace":
|
||||
cli.HandleNamespaceCommand(args)
|
||||
|
||||
// Environment management
|
||||
case "env":
|
||||
cli.HandleEnvCommand(args)
|
||||
@ -166,6 +170,9 @@ func showHelp() {
|
||||
fmt.Printf(" db backup <name> - Backup database to IPFS\n")
|
||||
fmt.Printf(" db backups <name> - List database backups\n\n")
|
||||
|
||||
fmt.Printf("🏢 Namespaces:\n")
|
||||
fmt.Printf(" namespace delete - Delete current namespace and all resources\n\n")
|
||||
|
||||
fmt.Printf("🌍 Environments:\n")
|
||||
fmt.Printf(" env list - List all environments\n")
|
||||
fmt.Printf(" env current - Show current environment\n")
|
||||
|
||||
131
pkg/cli/namespace_commands.go
Normal file
131
pkg/cli/namespace_commands.go
Normal file
@ -0,0 +1,131 @@
|
||||
package cli
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/auth"
|
||||
)
|
||||
|
||||
// HandleNamespaceCommand handles namespace management commands
|
||||
func HandleNamespaceCommand(args []string) {
|
||||
if len(args) == 0 {
|
||||
showNamespaceHelp()
|
||||
return
|
||||
}
|
||||
|
||||
subcommand := args[0]
|
||||
switch subcommand {
|
||||
case "delete":
|
||||
var force bool
|
||||
fs := flag.NewFlagSet("namespace delete", flag.ExitOnError)
|
||||
fs.BoolVar(&force, "force", false, "Skip confirmation prompt")
|
||||
_ = fs.Parse(args[1:])
|
||||
handleNamespaceDelete(force)
|
||||
case "help":
|
||||
showNamespaceHelp()
|
||||
default:
|
||||
fmt.Fprintf(os.Stderr, "Unknown namespace command: %s\n", subcommand)
|
||||
showNamespaceHelp()
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func showNamespaceHelp() {
|
||||
fmt.Printf("Namespace Management Commands\n\n")
|
||||
fmt.Printf("Usage: orama namespace <subcommand>\n\n")
|
||||
fmt.Printf("Subcommands:\n")
|
||||
fmt.Printf(" delete - Delete the current namespace and all its resources\n")
|
||||
fmt.Printf(" help - Show this help message\n\n")
|
||||
fmt.Printf("Flags:\n")
|
||||
fmt.Printf(" --force - Skip confirmation prompt\n\n")
|
||||
fmt.Printf("Examples:\n")
|
||||
fmt.Printf(" orama namespace delete\n")
|
||||
fmt.Printf(" orama namespace delete --force\n")
|
||||
}
|
||||
|
||||
func handleNamespaceDelete(force bool) {
|
||||
// Load credentials
|
||||
store, err := auth.LoadEnhancedCredentials()
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to load credentials: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
gatewayURL := getGatewayURL()
|
||||
creds := store.GetDefaultCredential(gatewayURL)
|
||||
|
||||
if creds == nil || !creds.IsValid() {
|
||||
fmt.Fprintf(os.Stderr, "Not authenticated. Run 'orama auth login' first.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
namespace := creds.Namespace
|
||||
if namespace == "" || namespace == "default" {
|
||||
fmt.Fprintf(os.Stderr, "Cannot delete default namespace.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Confirm deletion
|
||||
if !force {
|
||||
fmt.Printf("This will permanently delete namespace '%s' and all its resources:\n", namespace)
|
||||
fmt.Printf(" - RQLite cluster (3 nodes)\n")
|
||||
fmt.Printf(" - Olric cache cluster (3 nodes)\n")
|
||||
fmt.Printf(" - Gateway instances\n")
|
||||
fmt.Printf(" - API keys and credentials\n\n")
|
||||
fmt.Printf("Type the namespace name to confirm: ")
|
||||
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
input := strings.TrimSpace(scanner.Text())
|
||||
|
||||
if input != namespace {
|
||||
fmt.Println("Aborted - namespace name did not match.")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("Deleting namespace '%s'...\n", namespace)
|
||||
|
||||
// Make DELETE request to gateway
|
||||
url := fmt.Sprintf("%s/v1/namespace/delete", gatewayURL)
|
||||
req, err := http.NewRequest(http.MethodDelete, url, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to create request: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
req.Header.Set("Authorization", "Bearer "+creds.APIKey)
|
||||
|
||||
client := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
||||
},
|
||||
}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to connect to gateway: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
var result map[string]interface{}
|
||||
json.NewDecoder(resp.Body).Decode(&result)
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
errMsg := "unknown error"
|
||||
if e, ok := result["error"].(string); ok {
|
||||
errMsg = e
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Failed to delete namespace: %s\n", errMsg)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Namespace '%s' deleted successfully.\n", namespace)
|
||||
fmt.Printf("Run 'orama auth login' to create a new namespace.\n")
|
||||
}
|
||||
@ -114,6 +114,9 @@ type Gateway struct {
|
||||
|
||||
// Namespace instance spawn handler (for distributed provisioning)
|
||||
spawnHandler http.Handler
|
||||
|
||||
// Namespace delete handler
|
||||
namespaceDeleteHandler http.Handler
|
||||
}
|
||||
|
||||
// localSubscriber represents a WebSocket subscriber for local message delivery
|
||||
@ -444,6 +447,11 @@ func (g *Gateway) SetSpawnHandler(h http.Handler) {
|
||||
g.spawnHandler = h
|
||||
}
|
||||
|
||||
// SetNamespaceDeleteHandler sets the handler for namespace deletion requests.
|
||||
func (g *Gateway) SetNamespaceDeleteHandler(h http.Handler) {
|
||||
g.namespaceDeleteHandler = h
|
||||
}
|
||||
|
||||
// GetORMClient returns the RQLite ORM client for external use (e.g., by ClusterManager)
|
||||
func (g *Gateway) GetORMClient() rqlite.Client {
|
||||
return g.ormClient
|
||||
|
||||
108
pkg/gateway/handlers/namespace/delete_handler.go
Normal file
108
pkg/gateway/handlers/namespace/delete_handler.go
Normal file
@ -0,0 +1,108 @@
|
||||
package namespace
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/DeBrosOfficial/network/pkg/gateway/ctxkeys"
|
||||
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// NamespaceDeprovisioner is the interface for deprovisioning namespace clusters
|
||||
type NamespaceDeprovisioner interface {
|
||||
DeprovisionCluster(ctx context.Context, namespaceID int64) error
|
||||
}
|
||||
|
||||
// DeleteHandler handles namespace deletion requests
|
||||
type DeleteHandler struct {
|
||||
deprovisioner NamespaceDeprovisioner
|
||||
ormClient rqlite.Client
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewDeleteHandler creates a new delete handler
|
||||
func NewDeleteHandler(dp NamespaceDeprovisioner, orm rqlite.Client, logger *zap.Logger) *DeleteHandler {
|
||||
return &DeleteHandler{
|
||||
deprovisioner: dp,
|
||||
ormClient: orm,
|
||||
logger: logger.With(zap.String("component", "namespace-delete-handler")),
|
||||
}
|
||||
}
|
||||
|
||||
// ServeHTTP handles DELETE /v1/namespace/delete
|
||||
func (h *DeleteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete && r.Method != http.MethodPost {
|
||||
writeDeleteResponse(w, http.StatusMethodNotAllowed, map[string]interface{}{"error": "method not allowed"})
|
||||
return
|
||||
}
|
||||
|
||||
// Get namespace from context (set by auth middleware — already ownership-verified)
|
||||
ns := ""
|
||||
if v := r.Context().Value(ctxkeys.NamespaceOverride); v != nil {
|
||||
if s, ok := v.(string); ok {
|
||||
ns = s
|
||||
}
|
||||
}
|
||||
if ns == "" || ns == "default" {
|
||||
writeDeleteResponse(w, http.StatusBadRequest, map[string]interface{}{"error": "cannot delete default namespace"})
|
||||
return
|
||||
}
|
||||
|
||||
if h.deprovisioner == nil {
|
||||
writeDeleteResponse(w, http.StatusServiceUnavailable, map[string]interface{}{"error": "cluster provisioning not enabled"})
|
||||
return
|
||||
}
|
||||
|
||||
// Resolve namespace ID
|
||||
var rows []map[string]interface{}
|
||||
if err := h.ormClient.Query(r.Context(), &rows, "SELECT id FROM namespaces WHERE name = ? LIMIT 1", ns); err != nil || len(rows) == 0 {
|
||||
writeDeleteResponse(w, http.StatusNotFound, map[string]interface{}{"error": "namespace not found"})
|
||||
return
|
||||
}
|
||||
|
||||
var namespaceID int64
|
||||
switch v := rows[0]["id"].(type) {
|
||||
case float64:
|
||||
namespaceID = int64(v)
|
||||
case int64:
|
||||
namespaceID = v
|
||||
case int:
|
||||
namespaceID = int64(v)
|
||||
default:
|
||||
writeDeleteResponse(w, http.StatusInternalServerError, map[string]interface{}{"error": "invalid namespace ID type"})
|
||||
return
|
||||
}
|
||||
|
||||
h.logger.Info("Deprovisioning namespace cluster",
|
||||
zap.String("namespace", ns),
|
||||
zap.Int64("namespace_id", namespaceID),
|
||||
)
|
||||
|
||||
// Deprovision the cluster (stops processes, deallocates ports, deletes DB records)
|
||||
if err := h.deprovisioner.DeprovisionCluster(r.Context(), namespaceID); err != nil {
|
||||
h.logger.Error("Failed to deprovision cluster", zap.Error(err))
|
||||
writeDeleteResponse(w, http.StatusInternalServerError, map[string]interface{}{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// Delete API keys, ownership records, and namespace record
|
||||
h.ormClient.Exec(r.Context(), "DELETE FROM wallet_api_keys WHERE namespace_id = ?", namespaceID)
|
||||
h.ormClient.Exec(r.Context(), "DELETE FROM api_keys WHERE namespace_id = ?", namespaceID)
|
||||
h.ormClient.Exec(r.Context(), "DELETE FROM namespace_ownership WHERE namespace_id = ?", namespaceID)
|
||||
h.ormClient.Exec(r.Context(), "DELETE FROM namespaces WHERE id = ?", namespaceID)
|
||||
|
||||
h.logger.Info("Namespace deleted successfully", zap.String("namespace", ns))
|
||||
|
||||
writeDeleteResponse(w, http.StatusOK, map[string]interface{}{
|
||||
"status": "deleted",
|
||||
"namespace": ns,
|
||||
})
|
||||
}
|
||||
|
||||
func writeDeleteResponse(w http.ResponseWriter, status int, resp map[string]interface{}) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
json.NewEncoder(w).Encode(resp)
|
||||
}
|
||||
@ -67,6 +67,11 @@ func (g *Gateway) Routes() http.Handler {
|
||||
// namespace cluster status (public endpoint for polling during provisioning)
|
||||
mux.HandleFunc("/v1/namespace/status", g.namespaceClusterStatusHandler)
|
||||
|
||||
// namespace delete (authenticated — goes through auth middleware)
|
||||
if g.namespaceDeleteHandler != nil {
|
||||
mux.Handle("/v1/namespace/delete", g.namespaceDeleteHandler)
|
||||
}
|
||||
|
||||
// network
|
||||
mux.HandleFunc("/v1/network/status", g.networkStatusHandler)
|
||||
mux.HandleFunc("/v1/network/peers", g.networkPeersHandler)
|
||||
|
||||
@ -84,6 +84,10 @@ func (n *Node) startHTTPGateway(ctx context.Context) error {
|
||||
spawnHandler := namespacehandlers.NewSpawnHandler(rqliteSpawner, olricSpawner, n.logger.Logger)
|
||||
apiGateway.SetSpawnHandler(spawnHandler)
|
||||
|
||||
// Wire namespace delete handler
|
||||
deleteHandler := namespacehandlers.NewDeleteHandler(clusterManager, ormClient, n.logger.Logger)
|
||||
apiGateway.SetNamespaceDeleteHandler(deleteHandler)
|
||||
|
||||
n.logger.ComponentInfo(logging.ComponentNode, "Namespace cluster provisioning enabled",
|
||||
zap.String("base_domain", clusterCfg.BaseDomain),
|
||||
zap.String("base_data_dir", baseDataDir))
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user