Adds CORS.
This commit is contained in:
29
README.md
29
README.md
@@ -4,10 +4,35 @@ The Wild Central API is a lightweight service that runs on a local machine (e.g.
|
|||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
|
Start the development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
make dev
|
make dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## Usage
|
The API will be available at `http://localhost:5055`.
|
||||||
|
|
||||||
TBD
|
### Environment Variables
|
||||||
|
|
||||||
|
- `WILD_API_DATA_DIR` - Directory for instance data (default: `/var/lib/wild-central`)
|
||||||
|
- `WILD_DIRECTORY` - Path to Wild Cloud apps directory (default: `/opt/wild-cloud/apps`)
|
||||||
|
- `WILD_API_DNSMASQ_CONFIG_PATH` - Path to dnsmasq config file (default: `/etc/dnsmasq.d/wild-cloud.conf`)
|
||||||
|
- `WILD_CORS_ORIGINS` - Comma-separated list of allowed CORS origins for production (default: localhost development origins)
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
The API provides the following endpoint categories:
|
||||||
|
|
||||||
|
- **Instances** - Create, list, get, and delete Wild Cloud instances
|
||||||
|
- **Configuration** - Manage instance config.yaml
|
||||||
|
- **Secrets** - Manage instance secrets.yaml (redacted by default)
|
||||||
|
- **Nodes** - Discover, configure, and manage cluster nodes
|
||||||
|
- **Cluster** - Bootstrap and manage Talos/Kubernetes clusters
|
||||||
|
- **Services** - Install and manage base infrastructure services
|
||||||
|
- **Apps** - Deploy and manage Wild Cloud applications
|
||||||
|
- **PXE** - Manage PXE boot assets for network installation
|
||||||
|
- **Operations** - Track and stream long-running operations
|
||||||
|
- **Utilities** - Helper functions and status endpoints
|
||||||
|
- **dnsmasq** - Configure and manage dnsmasq for network services
|
||||||
|
|
||||||
|
See the API handler files in `internal/api/v1/` for detailed endpoint documentation.
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -6,3 +6,5 @@ require (
|
|||||||
github.com/gorilla/mux v1.8.1
|
github.com/gorilla/mux v1.8.1
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require github.com/rs/cors v1.11.1 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -1,5 +1,7 @@
|
|||||||
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
|
||||||
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
|
||||||
|
github.com/rs/cors v1.11.1 h1:eU3gRzXLRK57F5rKMGMZURNdIG4EoAmX8k94r9wXWHA=
|
||||||
|
github.com/rs/cors v1.11.1/go.mod h1:XyqrcTp5zjWr1wsJ8PIRZssZ8b/WMcMf71DJnit4EMU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@@ -139,28 +139,11 @@ func (m *Manager) ListDeployed(instanceName string) ([]DeployedApp, error) {
|
|||||||
|
|
||||||
appName := entry.Name()
|
appName := entry.Name()
|
||||||
|
|
||||||
// Check if namespace exists in cluster
|
// Initialize app with basic info
|
||||||
checkCmd := exec.Command("kubectl", "get", "namespace", appName, "-o", "json")
|
|
||||||
tools.WithKubeconfig(checkCmd, kubeconfigPath)
|
|
||||||
output, err := checkCmd.CombinedOutput()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
// Namespace doesn't exist - app not deployed
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse namespace status
|
|
||||||
var ns struct {
|
|
||||||
Status struct {
|
|
||||||
Phase string `json:"phase"`
|
|
||||||
} `json:"status"`
|
|
||||||
}
|
|
||||||
if err := yaml.Unmarshal(output, &ns); err == nil && ns.Status.Phase == "Active" {
|
|
||||||
// App is deployed - get more details
|
|
||||||
app := DeployedApp{
|
app := DeployedApp{
|
||||||
Name: appName,
|
Name: appName,
|
||||||
Namespace: appName,
|
Namespace: appName,
|
||||||
Status: "deployed",
|
Status: "added", // Default status: added but not deployed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to get version from manifest
|
// Try to get version from manifest
|
||||||
@@ -175,8 +158,25 @@ func (m *Manager) ListDeployed(instanceName string) ([]DeployedApp, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
apps = append(apps, app)
|
// Check if namespace exists in cluster
|
||||||
|
checkCmd := exec.Command("kubectl", "get", "namespace", appName, "-o", "json")
|
||||||
|
tools.WithKubeconfig(checkCmd, kubeconfigPath)
|
||||||
|
output, err := checkCmd.CombinedOutput()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Namespace exists - parse status
|
||||||
|
var ns struct {
|
||||||
|
Status struct {
|
||||||
|
Phase string `json:"phase"`
|
||||||
|
} `json:"status"`
|
||||||
}
|
}
|
||||||
|
if yaml.Unmarshal(output, &ns) == nil && ns.Status.Phase == "Active" {
|
||||||
|
// Namespace is active - app is deployed
|
||||||
|
app.Status = "deployed"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apps = append(apps, app)
|
||||||
}
|
}
|
||||||
|
|
||||||
return apps, nil
|
return apps, nil
|
||||||
|
|||||||
66
main.go
66
main.go
@@ -5,15 +5,29 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
|
"github.com/rs/cors"
|
||||||
|
|
||||||
v1 "github.com/wild-cloud/wild-central/daemon/internal/api/v1"
|
v1 "github.com/wild-cloud/wild-central/daemon/internal/api/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
var startTime time.Time
|
var startTime time.Time
|
||||||
|
|
||||||
|
// splitAndTrim splits a string by delimiter and trims whitespace from each part
|
||||||
|
func splitAndTrim(s string, sep string) []string {
|
||||||
|
parts := strings.Split(s, sep)
|
||||||
|
result := make([]string, 0, len(parts))
|
||||||
|
for _, part := range parts {
|
||||||
|
if trimmed := strings.TrimSpace(part); trimmed != "" {
|
||||||
|
result = append(result, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Record start time
|
// Record start time
|
||||||
startTime = time.Now()
|
startTime = time.Now()
|
||||||
@@ -61,6 +75,55 @@ func main() {
|
|||||||
api.StatusHandler(w, r, startTime, dataDir, appsDir)
|
api.StatusHandler(w, r, startTime, dataDir, appsDir)
|
||||||
}).Methods("GET")
|
}).Methods("GET")
|
||||||
|
|
||||||
|
// Configure CORS
|
||||||
|
// Default to development origins
|
||||||
|
allowedOrigins := []string{
|
||||||
|
"http://localhost:5173", // Vite dev server
|
||||||
|
"http://localhost:5174", // Alternative port
|
||||||
|
"http://localhost:3000", // Common React dev port
|
||||||
|
"http://127.0.0.1:5173",
|
||||||
|
"http://127.0.0.1:5174",
|
||||||
|
"http://127.0.0.1:3000",
|
||||||
|
}
|
||||||
|
|
||||||
|
// Override with production origins if set
|
||||||
|
if corsOrigins := os.Getenv("WILD_CORS_ORIGINS"); corsOrigins != "" {
|
||||||
|
// Split comma-separated origins
|
||||||
|
allowedOrigins = []string{}
|
||||||
|
for _, origin := range splitAndTrim(corsOrigins, ",") {
|
||||||
|
allowedOrigins = append(allowedOrigins, origin)
|
||||||
|
}
|
||||||
|
log.Printf("CORS configured for production origins: %v", allowedOrigins)
|
||||||
|
} else {
|
||||||
|
log.Printf("CORS configured for development origins")
|
||||||
|
}
|
||||||
|
|
||||||
|
corsHandler := cors.New(cors.Options{
|
||||||
|
AllowedOrigins: allowedOrigins,
|
||||||
|
AllowedMethods: []string{
|
||||||
|
http.MethodGet,
|
||||||
|
http.MethodPost,
|
||||||
|
http.MethodPut,
|
||||||
|
http.MethodPatch,
|
||||||
|
http.MethodDelete,
|
||||||
|
http.MethodOptions,
|
||||||
|
},
|
||||||
|
AllowedHeaders: []string{
|
||||||
|
"Accept",
|
||||||
|
"Authorization",
|
||||||
|
"Content-Type",
|
||||||
|
"X-CSRF-Token",
|
||||||
|
},
|
||||||
|
ExposedHeaders: []string{
|
||||||
|
"Link",
|
||||||
|
},
|
||||||
|
AllowCredentials: true,
|
||||||
|
MaxAge: 300, // 5 minutes
|
||||||
|
})
|
||||||
|
|
||||||
|
// Wrap router with CORS middleware
|
||||||
|
handler := corsHandler.Handler(router)
|
||||||
|
|
||||||
// Default server settings
|
// Default server settings
|
||||||
host := "0.0.0.0"
|
host := "0.0.0.0"
|
||||||
port := 5055
|
port := 5055
|
||||||
@@ -69,8 +132,9 @@ func main() {
|
|||||||
log.Printf("Starting wild-central daemon on %s", addr)
|
log.Printf("Starting wild-central daemon on %s", addr)
|
||||||
log.Printf("Data directory: %s", dataDir)
|
log.Printf("Data directory: %s", dataDir)
|
||||||
log.Printf("Apps directory: %s", appsDir)
|
log.Printf("Apps directory: %s", appsDir)
|
||||||
|
log.Printf("CORS enabled for development origins")
|
||||||
|
|
||||||
if err := http.ListenAndServe(addr, router); err != nil {
|
if err := http.ListenAndServe(addr, handler); err != nil {
|
||||||
log.Fatal("Server failed to start:", err)
|
log.Fatal("Server failed to start:", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user