Get templates from embedded package.
This commit is contained in:
157
README.md
157
README.md
@@ -10,159 +10,4 @@ make dev
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Batch Configuration Update Endpoint
|
TBD
|
||||||
|
|
||||||
#### Overview
|
|
||||||
|
|
||||||
The batch configuration update endpoint allows updating multiple configuration values in a single atomic request.
|
|
||||||
|
|
||||||
#### Endpoint
|
|
||||||
|
|
||||||
```
|
|
||||||
PATCH /api/v1/instances/{name}/config
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Request Format
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"updates": [
|
|
||||||
{"path": "string", "value": "any"},
|
|
||||||
{"path": "string", "value": "any"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Response Format
|
|
||||||
|
|
||||||
Success (200 OK):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Configuration updated successfully",
|
|
||||||
"updated": 3
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Error (400 Bad Request / 404 Not Found / 500 Internal Server Error):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "error message"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Usage Examples
|
|
||||||
|
|
||||||
##### Example 1: Update Basic Configuration Values
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PATCH http://localhost:8080/api/v1/instances/my-cloud/config \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"updates": [
|
|
||||||
{"path": "baseDomain", "value": "example.com"},
|
|
||||||
{"path": "domain", "value": "wild.example.com"},
|
|
||||||
{"path": "internalDomain", "value": "int.wild.example.com"}
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Response:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"message": "Configuration updated successfully",
|
|
||||||
"updated": 3
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Example 2: Update Nested Configuration Values
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PATCH http://localhost:8080/api/v1/instances/my-cloud/config \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"updates": [
|
|
||||||
{"path": "cluster.name", "value": "prod-cluster"},
|
|
||||||
{"path": "cluster.loadBalancerIp", "value": "192.168.1.100"},
|
|
||||||
{"path": "cluster.ipAddressPool", "value": "192.168.1.100-192.168.1.200"}
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Example 3: Update Array Values
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PATCH http://localhost:8080/api/v1/instances/my-cloud/config \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"updates": [
|
|
||||||
{"path": "cluster.nodes.activeNodes[0]", "value": "node-1"},
|
|
||||||
{"path": "cluster.nodes.activeNodes[1]", "value": "node-2"}
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Example 4: Error Handling - Invalid Instance
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PATCH http://localhost:8080/api/v1/instances/nonexistent/config \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"updates": [
|
|
||||||
{"path": "baseDomain", "value": "example.com"}
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (404):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "Instance not found: instance nonexistent does not exist"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Example 5: Error Handling - Empty Updates
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PATCH http://localhost:8080/api/v1/instances/my-cloud/config \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"updates": []
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (400):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "updates array is required and cannot be empty"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Example 6: Error Handling - Missing Path
|
|
||||||
|
|
||||||
```bash
|
|
||||||
curl -X PATCH http://localhost:8080/api/v1/instances/my-cloud/config \
|
|
||||||
-H "Content-Type: application/json" \
|
|
||||||
-d '{
|
|
||||||
"updates": [
|
|
||||||
{"path": "", "value": "example.com"}
|
|
||||||
]
|
|
||||||
}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Response (400):
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"error": "update[0]: path is required"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Configuration Path Syntax
|
|
||||||
|
|
||||||
The `path` field uses YAML path syntax as implemented by the `yq` tool:
|
|
||||||
|
|
||||||
- Simple fields: `baseDomain`
|
|
||||||
- Nested fields: `cluster.name`
|
|
||||||
- Array elements: `cluster.nodes.activeNodes[0]`
|
|
||||||
- Array append: `cluster.nodes.activeNodes[+]`
|
|
||||||
|
|
||||||
Refer to the yq documentation for advanced path syntax.
|
|
||||||
|
|||||||
@@ -22,8 +22,7 @@ import (
|
|||||||
// API holds all dependencies for API handlers
|
// API holds all dependencies for API handlers
|
||||||
type API struct {
|
type API struct {
|
||||||
dataDir string
|
dataDir string
|
||||||
directoryPath string // Path to Wild Cloud Directory
|
appsDir string // Path to external apps directory
|
||||||
appsDir string
|
|
||||||
config *config.Manager
|
config *config.Manager
|
||||||
secrets *secrets.Manager
|
secrets *secrets.Manager
|
||||||
context *context.Manager
|
context *context.Manager
|
||||||
@@ -32,19 +31,16 @@ type API struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// NewAPI creates a new API handler with all dependencies
|
// NewAPI creates a new API handler with all dependencies
|
||||||
func NewAPI(dataDir, directoryPath string) (*API, error) {
|
// Note: Setup files (cluster-services, cluster-nodes, etc.) are now embedded in the binary
|
||||||
|
func NewAPI(dataDir, appsDir string) (*API, error) {
|
||||||
// Ensure base directories exist
|
// Ensure base directories exist
|
||||||
instancesDir := filepath.Join(dataDir, "instances")
|
instancesDir := filepath.Join(dataDir, "instances")
|
||||||
if err := os.MkdirAll(instancesDir, 0755); err != nil {
|
if err := os.MkdirAll(instancesDir, 0755); err != nil {
|
||||||
return nil, fmt.Errorf("failed to create instances directory: %w", err)
|
return nil, fmt.Errorf("failed to create instances directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apps directory is now in Wild Cloud Directory
|
|
||||||
appsDir := filepath.Join(directoryPath, "apps")
|
|
||||||
|
|
||||||
return &API{
|
return &API{
|
||||||
dataDir: dataDir,
|
dataDir: dataDir,
|
||||||
directoryPath: directoryPath,
|
|
||||||
appsDir: appsDir,
|
appsDir: appsDir,
|
||||||
config: config.NewManager(),
|
config: config.NewManager(),
|
||||||
secrets: secrets.NewManager(),
|
secrets: secrets.NewManager(),
|
||||||
@@ -427,7 +423,7 @@ func (api *API) SetContext(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// StatusHandler returns daemon status information
|
// StatusHandler returns daemon status information
|
||||||
func (api *API) StatusHandler(w http.ResponseWriter, r *http.Request, startTime time.Time, dataDir, directoryPath string) {
|
func (api *API) StatusHandler(w http.ResponseWriter, r *http.Request, startTime time.Time, dataDir, appsDir string) {
|
||||||
// Get list of instances
|
// Get list of instances
|
||||||
instances, err := api.instance.ListInstances()
|
instances, err := api.instance.ListInstances()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -443,7 +439,8 @@ func (api *API) StatusHandler(w http.ResponseWriter, r *http.Request, startTime
|
|||||||
"uptime": uptime.String(),
|
"uptime": uptime.String(),
|
||||||
"uptimeSeconds": int(uptime.Seconds()),
|
"uptimeSeconds": int(uptime.Seconds()),
|
||||||
"dataDir": dataDir,
|
"dataDir": dataDir,
|
||||||
"directoryPath": directoryPath,
|
"appsDir": appsDir,
|
||||||
|
"setupFiles": "embedded", // Indicate that setup files are now embedded
|
||||||
"instances": map[string]interface{}{
|
"instances": map[string]interface{}{
|
||||||
"count": len(instances),
|
"count": len(instances),
|
||||||
"names": instances,
|
"names": instances,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func (api *API) ServicesList(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// List services
|
// List services
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
svcList, err := servicesMgr.List(instanceName)
|
svcList, err := servicesMgr.List(instanceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list services: %v", err))
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to list services: %v", err))
|
||||||
@@ -52,7 +52,7 @@ func (api *API) ServicesGet(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get service
|
// Get service
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
service, err := servicesMgr.Get(instanceName, serviceName)
|
service, err := servicesMgr.Get(instanceName, serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Service not found: %v", err))
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Service not found: %v", err))
|
||||||
@@ -109,7 +109,7 @@ func (api *API) ServicesInstall(w http.ResponseWriter, r *http.Request) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
fmt.Printf("[DEBUG] Service install goroutine started: service=%s instance=%s opID=%s\n", req.Name, instanceName, opID)
|
fmt.Printf("[DEBUG] Service install goroutine started: service=%s instance=%s opID=%s\n", req.Name, instanceName, opID)
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
opsMgr.UpdateStatus(instanceName, opID, "running")
|
opsMgr.UpdateStatus(instanceName, opID, "running")
|
||||||
|
|
||||||
if err := servicesMgr.Install(instanceName, req.Name, req.Fetch, req.Deploy, opID, api.broadcaster); err != nil {
|
if err := servicesMgr.Install(instanceName, req.Name, req.Fetch, req.Deploy, opID, api.broadcaster); err != nil {
|
||||||
@@ -159,7 +159,7 @@ func (api *API) ServicesInstallAll(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Install in background
|
// Install in background
|
||||||
go func() {
|
go func() {
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
opsMgr.UpdateStatus(instanceName, opID, "running")
|
opsMgr.UpdateStatus(instanceName, opID, "running")
|
||||||
|
|
||||||
if err := servicesMgr.InstallAll(instanceName, req.Fetch, req.Deploy, opID, api.broadcaster); err != nil {
|
if err := servicesMgr.InstallAll(instanceName, req.Fetch, req.Deploy, opID, api.broadcaster); err != nil {
|
||||||
@@ -197,7 +197,7 @@ func (api *API) ServicesDelete(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// Delete in background
|
// Delete in background
|
||||||
go func() {
|
go func() {
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
opsMgr.UpdateStatus(instanceName, opID, "running")
|
opsMgr.UpdateStatus(instanceName, opID, "running")
|
||||||
|
|
||||||
if err := servicesMgr.Delete(instanceName, serviceName); err != nil {
|
if err := servicesMgr.Delete(instanceName, serviceName); err != nil {
|
||||||
@@ -226,7 +226,7 @@ func (api *API) ServicesGetStatus(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get status
|
// Get status
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
status, err := servicesMgr.GetStatus(instanceName, serviceName)
|
status, err := servicesMgr.GetStatus(instanceName, serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get status: %v", err))
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to get status: %v", err))
|
||||||
@@ -241,7 +241,7 @@ func (api *API) ServicesGetManifest(w http.ResponseWriter, r *http.Request) {
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
serviceName := vars["service"]
|
serviceName := vars["service"]
|
||||||
|
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
manifest, err := servicesMgr.GetManifest(serviceName)
|
manifest, err := servicesMgr.GetManifest(serviceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(w, http.StatusNotFound, fmt.Sprintf("Service not found: %v", err))
|
respondError(w, http.StatusNotFound, fmt.Sprintf("Service not found: %v", err))
|
||||||
@@ -256,7 +256,7 @@ func (api *API) ServicesGetConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
vars := mux.Vars(r)
|
vars := mux.Vars(r)
|
||||||
serviceName := vars["service"]
|
serviceName := vars["service"]
|
||||||
|
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
|
|
||||||
// Get manifest
|
// Get manifest
|
||||||
manifest, err := servicesMgr.GetManifest(serviceName)
|
manifest, err := servicesMgr.GetManifest(serviceName)
|
||||||
@@ -286,7 +286,7 @@ func (api *API) ServicesGetInstanceConfig(w http.ResponseWriter, r *http.Request
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
|
|
||||||
// Get manifest to know which config paths to read
|
// Get manifest to know which config paths to read
|
||||||
manifest, err := servicesMgr.GetManifest(serviceName)
|
manifest, err := servicesMgr.GetManifest(serviceName)
|
||||||
@@ -364,7 +364,7 @@ func (api *API) ServicesFetch(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch service files
|
// Fetch service files
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
if err := servicesMgr.Fetch(instanceName, serviceName); err != nil {
|
if err := servicesMgr.Fetch(instanceName, serviceName); err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to fetch service: %v", err))
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to fetch service: %v", err))
|
||||||
return
|
return
|
||||||
@@ -388,7 +388,7 @@ func (api *API) ServicesCompile(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compile templates
|
// Compile templates
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
if err := servicesMgr.Compile(instanceName, serviceName); err != nil {
|
if err := servicesMgr.Compile(instanceName, serviceName); err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to compile templates: %v", err))
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to compile templates: %v", err))
|
||||||
return
|
return
|
||||||
@@ -412,7 +412,7 @@ func (api *API) ServicesDeploy(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deploy service (without operation tracking for standalone deploy)
|
// Deploy service (without operation tracking for standalone deploy)
|
||||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
servicesMgr := services.NewManager(api.dataDir)
|
||||||
if err := servicesMgr.Deploy(instanceName, serviceName, "", nil); err != nil {
|
if err := servicesMgr.Deploy(instanceName, serviceName, "", nil); err != nil {
|
||||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to deploy service: %v", err))
|
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to deploy service: %v", err))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/wild-cloud/wild-central/daemon/internal/config"
|
"github.com/wild-cloud/wild-central/daemon/internal/config"
|
||||||
|
"github.com/wild-cloud/wild-central/daemon/internal/setup"
|
||||||
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -335,11 +336,11 @@ func (m *Manager) Apply(instanceName, nodeIdentifier string, opts ApplyOptions)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always auto-fetch templates if they don't exist
|
// Always auto-extract templates from embedded files if they don't exist
|
||||||
templatesDir := filepath.Join(setupDir, "patch.templates")
|
templatesDir := filepath.Join(setupDir, "patch.templates")
|
||||||
if !m.templatesExist(templatesDir) {
|
if !m.templatesExist(templatesDir) {
|
||||||
if err := m.copyTemplatesFromDirectory(templatesDir); err != nil {
|
if err := m.extractEmbeddedTemplates(templatesDir); err != nil {
|
||||||
return fmt.Errorf("failed to copy templates: %w", err)
|
return fmt.Errorf("failed to extract templates: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,36 +505,31 @@ func (m *Manager) templatesExist(templatesDir string) bool {
|
|||||||
return err1 == nil && err2 == nil
|
return err1 == nil && err2 == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// copyTemplatesFromDirectory copies patch templates from directory/ to instance
|
// extractEmbeddedTemplates extracts patch templates from embedded files to instance directory
|
||||||
func (m *Manager) copyTemplatesFromDirectory(destDir string) error {
|
func (m *Manager) extractEmbeddedTemplates(destDir string) error {
|
||||||
// Find the directory/setup/cluster-nodes/patch.templates directory
|
|
||||||
// It should be in the same parent as the data directory
|
|
||||||
sourceDir := filepath.Join(filepath.Dir(m.dataDir), "directory", "setup", "cluster-nodes", "patch.templates")
|
|
||||||
|
|
||||||
// Check if source directory exists
|
|
||||||
if _, err := os.Stat(sourceDir); err != nil {
|
|
||||||
return fmt.Errorf("source templates directory not found: %s", sourceDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create destination directory
|
// Create destination directory
|
||||||
if err := os.MkdirAll(destDir, 0755); err != nil {
|
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||||
return fmt.Errorf("failed to create templates directory: %w", err)
|
return fmt.Errorf("failed to create templates directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy controlplane.yaml
|
// Get embedded template files
|
||||||
if err := m.copyFile(
|
controlplaneData, err := setup.GetClusterNodesFile("patch.templates/controlplane.yaml")
|
||||||
filepath.Join(sourceDir, "controlplane.yaml"),
|
if err != nil {
|
||||||
filepath.Join(destDir, "controlplane.yaml"),
|
return fmt.Errorf("failed to get controlplane template: %w", err)
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy controlplane template: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy worker.yaml
|
workerData, err := setup.GetClusterNodesFile("patch.templates/worker.yaml")
|
||||||
if err := m.copyFile(
|
if err != nil {
|
||||||
filepath.Join(sourceDir, "worker.yaml"),
|
return fmt.Errorf("failed to get worker template: %w", err)
|
||||||
filepath.Join(destDir, "worker.yaml"),
|
}
|
||||||
); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy worker template: %w", err)
|
// Write templates
|
||||||
|
if err := os.WriteFile(filepath.Join(destDir, "controlplane.yaml"), controlplaneData, 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write controlplane template: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.WriteFile(filepath.Join(destDir, "worker.yaml"), workerData, 0644); err != nil {
|
||||||
|
return fmt.Errorf("failed to write worker template: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -660,9 +656,9 @@ func (m *Manager) Update(instanceName string, hostname string, updates map[strin
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FetchTemplates copies patch templates from directory/ to instance
|
// FetchTemplates extracts patch templates from embedded files to instance
|
||||||
func (m *Manager) FetchTemplates(instanceName string) error {
|
func (m *Manager) FetchTemplates(instanceName string) error {
|
||||||
instancePath := m.GetInstancePath(instanceName)
|
instancePath := m.GetInstancePath(instanceName)
|
||||||
destDir := filepath.Join(instancePath, "setup", "cluster-nodes", "patch.templates")
|
destDir := filepath.Join(instancePath, "setup", "cluster-nodes", "patch.templates")
|
||||||
return m.copyTemplatesFromDirectory(destDir)
|
return m.extractEmbeddedTemplates(destDir)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package services
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"github.com/wild-cloud/wild-central/daemon/internal/operations"
|
"github.com/wild-cloud/wild-central/daemon/internal/operations"
|
||||||
|
"github.com/wild-cloud/wild-central/daemon/internal/setup"
|
||||||
"github.com/wild-cloud/wild-central/daemon/internal/storage"
|
"github.com/wild-cloud/wild-central/daemon/internal/storage"
|
||||||
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
"github.com/wild-cloud/wild-central/daemon/internal/tools"
|
||||||
)
|
)
|
||||||
@@ -17,23 +19,33 @@ import (
|
|||||||
// Manager handles base service operations
|
// Manager handles base service operations
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
dataDir string
|
dataDir string
|
||||||
servicesDir string // Path to services directory
|
|
||||||
manifests map[string]*ServiceManifest // Cached service manifests
|
manifests map[string]*ServiceManifest // Cached service manifests
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager creates a new services manager
|
// NewManager creates a new services manager
|
||||||
func NewManager(dataDir, servicesDir string) *Manager {
|
// Note: Service definitions are now loaded from embedded setup files
|
||||||
|
func NewManager(dataDir string) *Manager {
|
||||||
m := &Manager{
|
m := &Manager{
|
||||||
dataDir: dataDir,
|
dataDir: dataDir,
|
||||||
servicesDir: servicesDir,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load all service manifests
|
// Load all service manifests from embedded files
|
||||||
manifests, err := LoadAllManifests(servicesDir)
|
manifests := make(map[string]*ServiceManifest)
|
||||||
if err != nil {
|
services, err := setup.ListServices()
|
||||||
// Log error but continue - services without manifests will fall back to hardcoded map
|
if err == nil {
|
||||||
fmt.Printf("Warning: failed to load service manifests: %v\n", err)
|
for _, serviceName := range services {
|
||||||
manifests = make(map[string]*ServiceManifest)
|
manifest, err := setup.GetManifest(serviceName)
|
||||||
|
if err == nil {
|
||||||
|
// Convert setup.ServiceManifest to services.ServiceManifest
|
||||||
|
manifests[serviceName] = &ServiceManifest{
|
||||||
|
Name: manifest.Name,
|
||||||
|
Description: manifest.Description,
|
||||||
|
Category: manifest.Category,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("Warning: failed to load service manifests from embedded files: %v\n", err)
|
||||||
}
|
}
|
||||||
m.manifests = manifests
|
m.manifests = manifests
|
||||||
|
|
||||||
@@ -124,18 +136,13 @@ func (m *Manager) checkServiceStatus(instanceName, serviceName string) string {
|
|||||||
func (m *Manager) List(instanceName string) ([]Service, error) {
|
func (m *Manager) List(instanceName string) ([]Service, error) {
|
||||||
services := []Service{}
|
services := []Service{}
|
||||||
|
|
||||||
// Discover services from the services directory
|
// Discover services from embedded setup files
|
||||||
entries, err := os.ReadDir(m.servicesDir)
|
serviceNames, err := setup.ListServices()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to read services directory: %w", err)
|
return nil, fmt.Errorf("failed to list services from embedded files: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range entries {
|
for _, name := range serviceNames {
|
||||||
if !entry.IsDir() {
|
|
||||||
continue // Skip non-directories like README.md
|
|
||||||
}
|
|
||||||
|
|
||||||
name := entry.Name()
|
|
||||||
|
|
||||||
// Get service info from manifest if available
|
// Get service info from manifest if available
|
||||||
var namespace, description, version string
|
var namespace, description, version string
|
||||||
@@ -232,11 +239,17 @@ func (m *Manager) InstallAll(instanceName string, fetch, deploy bool, opID strin
|
|||||||
func (m *Manager) Delete(instanceName, serviceName string) error {
|
func (m *Manager) Delete(instanceName, serviceName string) error {
|
||||||
kubeconfigPath := tools.GetKubeconfigPath(m.dataDir, instanceName)
|
kubeconfigPath := tools.GetKubeconfigPath(m.dataDir, instanceName)
|
||||||
|
|
||||||
serviceDir := filepath.Join(m.servicesDir, serviceName)
|
// Check if service exists in embedded files
|
||||||
manifestsFile := filepath.Join(serviceDir, "manifests.yaml")
|
if !setup.ServiceExists(serviceName) {
|
||||||
|
return fmt.Errorf("service %s not found", serviceName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get manifests file from embedded setup or instance directory
|
||||||
|
instanceServiceDir := filepath.Join(m.dataDir, "instances", instanceName, "setup", "cluster-services", serviceName)
|
||||||
|
manifestsFile := filepath.Join(instanceServiceDir, "manifests.yaml")
|
||||||
|
|
||||||
if !storage.FileExists(manifestsFile) {
|
if !storage.FileExists(manifestsFile) {
|
||||||
return fmt.Errorf("service %s not found", serviceName)
|
return fmt.Errorf("service manifests not found - service may not be installed")
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command("kubectl", "delete", "-f", manifestsFile)
|
cmd := exec.Command("kubectl", "delete", "-f", manifestsFile)
|
||||||
@@ -292,12 +305,11 @@ func (m *Manager) GetConfigReferences(serviceName string) ([]string, error) {
|
|||||||
return manifest.ConfigReferences, nil
|
return manifest.ConfigReferences, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch copies service files from directory to instance
|
// Fetch extracts service files from embedded setup to instance
|
||||||
func (m *Manager) Fetch(instanceName, serviceName string) error {
|
func (m *Manager) Fetch(instanceName, serviceName string) error {
|
||||||
// 1. Validate service exists in directory
|
// 1. Validate service exists in embedded files
|
||||||
sourceDir := filepath.Join(m.servicesDir, serviceName)
|
if !setup.ServiceExists(serviceName) {
|
||||||
if !dirExists(sourceDir) {
|
return fmt.Errorf("service %s not found in embedded files", serviceName)
|
||||||
return fmt.Errorf("service %s not found in directory", serviceName)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Create instance service directory
|
// 2. Create instance service directory
|
||||||
@@ -307,31 +319,36 @@ func (m *Manager) Fetch(instanceName, serviceName string) error {
|
|||||||
return fmt.Errorf("failed to create service directory: %w", err)
|
return fmt.Errorf("failed to create service directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Copy files:
|
// 3. Extract files from embedded setup:
|
||||||
// - README.md (if exists, optional)
|
// - README.md (if exists, optional)
|
||||||
// - install.sh (if exists, optional)
|
// - install.sh (if exists, optional)
|
||||||
|
// - wild-manifest.yaml
|
||||||
// - kustomize.template/* (if exists, optional)
|
// - kustomize.template/* (if exists, optional)
|
||||||
|
|
||||||
// Copy README.md
|
// Extract README.md if it exists
|
||||||
copyFileIfExists(filepath.Join(sourceDir, "README.md"),
|
if readmeData, err := setup.GetServiceFile(serviceName, "README.md"); err == nil {
|
||||||
filepath.Join(instanceDir, "README.md"))
|
os.WriteFile(filepath.Join(instanceDir, "README.md"), readmeData, 0644)
|
||||||
|
|
||||||
// Copy install.sh (optional)
|
|
||||||
installSh := filepath.Join(sourceDir, "install.sh")
|
|
||||||
if fileExists(installSh) {
|
|
||||||
if err := copyFile(installSh, filepath.Join(instanceDir, "install.sh")); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy install.sh: %w", err)
|
|
||||||
}
|
|
||||||
// Make install.sh executable
|
|
||||||
os.Chmod(filepath.Join(instanceDir, "install.sh"), 0755)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy kustomize.template directory if it exists
|
// Extract install.sh if it exists
|
||||||
templateDir := filepath.Join(sourceDir, "kustomize.template")
|
if installData, err := setup.GetServiceFile(serviceName, "install.sh"); err == nil {
|
||||||
if dirExists(templateDir) {
|
installPath := filepath.Join(instanceDir, "install.sh")
|
||||||
|
if err := os.WriteFile(installPath, installData, 0755); err != nil {
|
||||||
|
return fmt.Errorf("failed to write install.sh: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract wild-manifest.yaml
|
||||||
|
if manifestData, err := setup.GetServiceFile(serviceName, "wild-manifest.yaml"); err == nil {
|
||||||
|
os.WriteFile(filepath.Join(instanceDir, "wild-manifest.yaml"), manifestData, 0644)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract kustomize.template directory
|
||||||
|
templateFS, err := setup.GetKustomizeTemplate(serviceName)
|
||||||
|
if err == nil {
|
||||||
destTemplateDir := filepath.Join(instanceDir, "kustomize.template")
|
destTemplateDir := filepath.Join(instanceDir, "kustomize.template")
|
||||||
if err := copyDir(templateDir, destTemplateDir); err != nil {
|
if err := extractFS(templateFS, destTemplateDir); err != nil {
|
||||||
return fmt.Errorf("failed to copy templates: %w", err)
|
return fmt.Errorf("failed to extract templates: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -404,6 +421,32 @@ func copyDir(src, dst string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// extractFS extracts files from an fs.FS to a destination directory
|
||||||
|
func extractFS(fsys fs.FS, dst string) error {
|
||||||
|
return fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create destination path
|
||||||
|
dstPath := filepath.Join(dst, path)
|
||||||
|
|
||||||
|
if d.IsDir() {
|
||||||
|
// Create directory
|
||||||
|
return os.MkdirAll(dstPath, 0755)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read file from embedded FS
|
||||||
|
data, err := fs.ReadFile(fsys, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write file to destination
|
||||||
|
return os.WriteFile(dstPath, data, 0644)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// Compile processes gomplate templates into final Kubernetes manifests
|
// Compile processes gomplate templates into final Kubernetes manifests
|
||||||
func (m *Manager) Compile(instanceName, serviceName string) error {
|
func (m *Manager) Compile(instanceName, serviceName string) error {
|
||||||
instanceDir := filepath.Join(m.dataDir, "instances", instanceName)
|
instanceDir := filepath.Join(m.dataDir, "instances", instanceName)
|
||||||
|
|||||||
21
main.go
21
main.go
@@ -24,14 +24,21 @@ func main() {
|
|||||||
dataDir = "/var/lib/wild-central"
|
dataDir = "/var/lib/wild-central"
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get directory path from environment (required)
|
// Get apps directory from environment or use default
|
||||||
directoryPath := os.Getenv("WILD_DIRECTORY")
|
// Note: Setup files (cluster-services, cluster-nodes, etc.) are now embedded in the binary
|
||||||
if directoryPath == "" {
|
appsDir := os.Getenv("WILD_DIRECTORY")
|
||||||
log.Fatal("WILD_DIRECTORY environment variable is required")
|
if appsDir == "" {
|
||||||
|
// Default apps directory
|
||||||
|
appsDir = "/opt/wild-cloud/apps"
|
||||||
|
log.Printf("WILD_DIRECTORY not set, using default apps directory: %s", appsDir)
|
||||||
|
} else {
|
||||||
|
// If WILD_DIRECTORY is set, use it as-is for backward compatibility
|
||||||
|
// (it might point to the old directory structure with apps/ subdirectory)
|
||||||
|
log.Printf("Using WILD_DIRECTORY for apps: %s", appsDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create API handler with all dependencies
|
// Create API handler with all dependencies
|
||||||
api, err := v1.NewAPI(dataDir, directoryPath)
|
api, err := v1.NewAPI(dataDir, appsDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to initialize API: %v", err)
|
log.Fatalf("Failed to initialize API: %v", err)
|
||||||
}
|
}
|
||||||
@@ -51,7 +58,7 @@ func main() {
|
|||||||
|
|
||||||
// Status endpoint
|
// Status endpoint
|
||||||
router.HandleFunc("/api/v1/status", func(w http.ResponseWriter, r *http.Request) {
|
router.HandleFunc("/api/v1/status", func(w http.ResponseWriter, r *http.Request) {
|
||||||
api.StatusHandler(w, r, startTime, dataDir, directoryPath)
|
api.StatusHandler(w, r, startTime, dataDir, appsDir)
|
||||||
}).Methods("GET")
|
}).Methods("GET")
|
||||||
|
|
||||||
// Default server settings
|
// Default server settings
|
||||||
@@ -61,7 +68,7 @@ func main() {
|
|||||||
addr := fmt.Sprintf("%s:%d", host, port)
|
addr := fmt.Sprintf("%s:%d", host, port)
|
||||||
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("Wild Cloud Directory: %s", directoryPath)
|
log.Printf("Apps directory: %s", appsDir)
|
||||||
|
|
||||||
if err := http.ListenAndServe(addr, router); err != nil {
|
if err := http.ListenAndServe(addr, router); err != nil {
|
||||||
log.Fatal("Server failed to start:", err)
|
log.Fatal("Server failed to start:", err)
|
||||||
|
|||||||
Reference in New Issue
Block a user