Namespace dashboard token endpoint in an instance.
This commit is contained in:
@@ -42,3 +42,77 @@
|
|||||||
### Features
|
### Features
|
||||||
|
|
||||||
- If WILD_CENTRAL_ENV environment variable is set to "development", the API should run in development mode.
|
- If WILD_CENTRAL_ENV environment variable is set to "development", the API should run in development mode.
|
||||||
|
|
||||||
|
## Patterns
|
||||||
|
|
||||||
|
### Instance-scoped Endpoints
|
||||||
|
|
||||||
|
Instance-scoped endpoints follow a consistent pattern to ensure stateless, RESTful API design. The instance name is always included in the URL path, not retrieved from session state or context.
|
||||||
|
|
||||||
|
#### Route Pattern
|
||||||
|
|
||||||
|
```go
|
||||||
|
// In handlers.go
|
||||||
|
r.HandleFunc("/api/v1/instances/{name}/utilities/dashboard/token", api.UtilitiesDashboardToken).Methods("GET")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Handler Pattern
|
||||||
|
|
||||||
|
```go
|
||||||
|
// In handlers_utilities.go
|
||||||
|
func (api *API) UtilitiesDashboardToken(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 1. Extract instance name from URL path parameters
|
||||||
|
vars := mux.Vars(r)
|
||||||
|
instanceName := vars["name"]
|
||||||
|
|
||||||
|
// 2. Validate instance exists
|
||||||
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||||
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Construct instance-specific paths
|
||||||
|
kubeconfigPath := filepath.Join(api.dataDir, "instances", instanceName, "kubeconfig")
|
||||||
|
|
||||||
|
// 4. Perform instance-specific operations
|
||||||
|
token, err := utilities.GetDashboardToken(kubeconfigPath)
|
||||||
|
if err != nil {
|
||||||
|
respondError(w, http.StatusInternalServerError, "Failed to get dashboard token")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Return response
|
||||||
|
respondJSON(w, http.StatusOK, map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"data": token,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using Kubeconfig with kubectl/talosctl
|
||||||
|
|
||||||
|
When making kubectl or talosctl calls for a specific instance, use the `tools.WithKubeconfig()` helper to set the KUBECONFIG environment variable:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// In utilities.go or similar
|
||||||
|
func GetDashboardToken(kubeconfigPath string) (*DashboardToken, error) {
|
||||||
|
cmd := exec.Command("kubectl", "-n", "kubernetes-dashboard", "create", "token", "dashboard-admin")
|
||||||
|
tools.WithKubeconfig(cmd, kubeconfigPath)
|
||||||
|
output, err := cmd.Output()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create token: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
token := strings.TrimSpace(string(output))
|
||||||
|
return &DashboardToken{Token: token}, nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Key Principles
|
||||||
|
|
||||||
|
1. **Instance name in URL**: Always include instance name as a path parameter (`{name}`)
|
||||||
|
2. **Extract from mux.Vars()**: Get instance name from `mux.Vars(r)["name"]`, not from context
|
||||||
|
3. **Validate instance**: Always validate the instance exists before operations
|
||||||
|
4. **Construct paths explicitly**: Build instance-specific file paths from the instance name
|
||||||
|
5. **Stateless handlers**: Handlers should not depend on session state or current context
|
||||||
|
6. **Use tools helpers**: Use `tools.WithKubeconfig()` for kubectl/talosctl commands
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func (api *API) RegisterRoutes(r *mux.Router) {
|
|||||||
// Utilities
|
// Utilities
|
||||||
r.HandleFunc("/api/v1/utilities/health", api.UtilitiesHealth).Methods("GET")
|
r.HandleFunc("/api/v1/utilities/health", api.UtilitiesHealth).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/instances/{name}/utilities/health", api.InstanceUtilitiesHealth).Methods("GET")
|
r.HandleFunc("/api/v1/instances/{name}/utilities/health", api.InstanceUtilitiesHealth).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/utilities/dashboard/token", api.UtilitiesDashboardToken).Methods("GET")
|
r.HandleFunc("/api/v1/instances/{name}/utilities/dashboard/token", api.UtilitiesDashboardToken).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/utilities/nodes/ips", api.UtilitiesNodeIPs).Methods("GET")
|
r.HandleFunc("/api/v1/utilities/nodes/ips", api.UtilitiesNodeIPs).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/utilities/controlplane/ip", api.UtilitiesControlPlaneIP).Methods("GET")
|
r.HandleFunc("/api/v1/utilities/controlplane/ip", api.UtilitiesControlPlaneIP).Methods("GET")
|
||||||
r.HandleFunc("/api/v1/utilities/secrets/{secret}/copy", api.UtilitiesSecretCopy).Methods("POST")
|
r.HandleFunc("/api/v1/utilities/secrets/{secret}/copy", api.UtilitiesSecretCopy).Methods("POST")
|
||||||
|
|||||||
@@ -50,12 +50,24 @@ func (api *API) InstanceUtilitiesHealth(w http.ResponseWriter, r *http.Request)
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// UtilitiesDashboardToken returns a Kubernetes dashboard token
|
// InstanceUtilitiesDashboardToken returns a Kubernetes dashboard token for a specific instance
|
||||||
func (api *API) UtilitiesDashboardToken(w http.ResponseWriter, r *http.Request) {
|
func (api *API) UtilitiesDashboardToken(w http.ResponseWriter, r *http.Request) {
|
||||||
token, err := utilities.GetDashboardToken()
|
vars := mux.Vars(r)
|
||||||
|
instanceName := vars["name"]
|
||||||
|
|
||||||
|
// Validate instance exists
|
||||||
|
if err := api.instance.ValidateInstance(instanceName); err != nil {
|
||||||
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Instance not found: %v", err))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get kubeconfig path for the instance
|
||||||
|
kubeconfigPath := filepath.Join(api.dataDir, "instances", instanceName, "kubeconfig")
|
||||||
|
|
||||||
|
token, err := utilities.GetDashboardToken(kubeconfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Try fallback method
|
// Try fallback method
|
||||||
token, err = utilities.GetDashboardTokenFromSecret()
|
token, err = utilities.GetDashboardTokenFromSecret(kubeconfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, "Failed to get dashboard token")
|
respondError(w, http.StatusInternalServerError, "Failed to get dashboard token")
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
// HealthStatus represents cluster health information
|
// HealthStatus represents cluster health information
|
||||||
@@ -127,15 +129,17 @@ func checkComponent(kubeconfigPath, namespace, selector string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDashboardToken retrieves or creates a Kubernetes dashboard token
|
// GetDashboardToken retrieves or creates a Kubernetes dashboard token
|
||||||
func GetDashboardToken() (*DashboardToken, error) {
|
func GetDashboardToken(kubeconfigPath string) (*DashboardToken, error) {
|
||||||
// Check if service account exists
|
// Check if service account exists
|
||||||
cmd := exec.Command("kubectl", "get", "serviceaccount", "-n", "kubernetes-dashboard", "dashboard-admin")
|
cmd := exec.Command("kubectl", "get", "serviceaccount", "-n", "kubernetes-dashboard", "dashboard-admin")
|
||||||
|
tools.WithKubeconfig(cmd, kubeconfigPath)
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return nil, fmt.Errorf("dashboard-admin service account not found")
|
return nil, fmt.Errorf("dashboard-admin service account not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create token
|
// Create token
|
||||||
cmd = exec.Command("kubectl", "-n", "kubernetes-dashboard", "create", "token", "dashboard-admin")
|
cmd = exec.Command("kubectl", "-n", "kubernetes-dashboard", "create", "token", "dashboard-admin")
|
||||||
|
tools.WithKubeconfig(cmd, kubeconfigPath)
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to create token: %w", err)
|
return nil, fmt.Errorf("failed to create token: %w", err)
|
||||||
@@ -148,9 +152,10 @@ func GetDashboardToken() (*DashboardToken, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetDashboardTokenFromSecret retrieves dashboard token from secret (fallback method)
|
// GetDashboardTokenFromSecret retrieves dashboard token from secret (fallback method)
|
||||||
func GetDashboardTokenFromSecret() (*DashboardToken, error) {
|
func GetDashboardTokenFromSecret(kubeconfigPath string) (*DashboardToken, error) {
|
||||||
cmd := exec.Command("kubectl", "-n", "kubernetes-dashboard", "get", "secret",
|
cmd := exec.Command("kubectl", "-n", "kubernetes-dashboard", "get", "secret",
|
||||||
"dashboard-admin-token", "-o", "jsonpath={.data.token}")
|
"dashboard-admin-token", "-o", "jsonpath={.data.token}")
|
||||||
|
tools.WithKubeconfig(cmd, kubeconfigPath)
|
||||||
output, err := cmd.Output()
|
output, err := cmd.Output()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to get token secret: %w", err)
|
return nil, fmt.Errorf("failed to get token secret: %w", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user