Compare commits
2 Commits
92032202f4
...
ff97f14229
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff97f14229 | ||
|
|
89c6a7aa80 |
157
README.md
157
README.md
@@ -10,159 +10,4 @@ make dev
|
||||
|
||||
## Usage
|
||||
|
||||
### Batch Configuration Update Endpoint
|
||||
|
||||
#### 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.
|
||||
TBD
|
||||
|
||||
@@ -21,36 +21,32 @@ import (
|
||||
|
||||
// API holds all dependencies for API handlers
|
||||
type API struct {
|
||||
dataDir string
|
||||
directoryPath string // Path to Wild Cloud Directory
|
||||
appsDir string
|
||||
config *config.Manager
|
||||
secrets *secrets.Manager
|
||||
context *context.Manager
|
||||
instance *instance.Manager
|
||||
broadcaster *operations.Broadcaster // SSE broadcaster for operation output
|
||||
dataDir string
|
||||
appsDir string // Path to external apps directory
|
||||
config *config.Manager
|
||||
secrets *secrets.Manager
|
||||
context *context.Manager
|
||||
instance *instance.Manager
|
||||
broadcaster *operations.Broadcaster // SSE broadcaster for operation output
|
||||
}
|
||||
|
||||
// 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
|
||||
instancesDir := filepath.Join(dataDir, "instances")
|
||||
if err := os.MkdirAll(instancesDir, 0755); err != nil {
|
||||
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{
|
||||
dataDir: dataDir,
|
||||
directoryPath: directoryPath,
|
||||
appsDir: appsDir,
|
||||
config: config.NewManager(),
|
||||
secrets: secrets.NewManager(),
|
||||
context: context.NewManager(dataDir),
|
||||
instance: instance.NewManager(dataDir),
|
||||
broadcaster: operations.NewBroadcaster(),
|
||||
dataDir: dataDir,
|
||||
appsDir: appsDir,
|
||||
config: config.NewManager(),
|
||||
secrets: secrets.NewManager(),
|
||||
context: context.NewManager(dataDir),
|
||||
instance: instance.NewManager(dataDir),
|
||||
broadcaster: operations.NewBroadcaster(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -427,7 +423,7 @@ func (api *API) SetContext(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 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
|
||||
instances, err := api.instance.ListInstances()
|
||||
if err != nil {
|
||||
@@ -443,7 +439,8 @@ func (api *API) StatusHandler(w http.ResponseWriter, r *http.Request, startTime
|
||||
"uptime": uptime.String(),
|
||||
"uptimeSeconds": int(uptime.Seconds()),
|
||||
"dataDir": dataDir,
|
||||
"directoryPath": directoryPath,
|
||||
"appsDir": appsDir,
|
||||
"setupFiles": "embedded", // Indicate that setup files are now embedded
|
||||
"instances": map[string]interface{}{
|
||||
"count": len(instances),
|
||||
"names": instances,
|
||||
|
||||
@@ -27,7 +27,7 @@ func (api *API) ServicesList(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// List services
|
||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
||||
servicesMgr := services.NewManager(api.dataDir)
|
||||
svcList, err := servicesMgr.List(instanceName)
|
||||
if err != nil {
|
||||
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
|
||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
||||
servicesMgr := services.NewManager(api.dataDir)
|
||||
service, err := servicesMgr.Get(instanceName, serviceName)
|
||||
if err != nil {
|
||||
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)
|
||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
||||
servicesMgr := services.NewManager(api.dataDir)
|
||||
opsMgr.UpdateStatus(instanceName, opID, "running")
|
||||
|
||||
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
|
||||
go func() {
|
||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
||||
servicesMgr := services.NewManager(api.dataDir)
|
||||
opsMgr.UpdateStatus(instanceName, opID, "running")
|
||||
|
||||
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
|
||||
go func() {
|
||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
||||
servicesMgr := services.NewManager(api.dataDir)
|
||||
opsMgr.UpdateStatus(instanceName, opID, "running")
|
||||
|
||||
if err := servicesMgr.Delete(instanceName, serviceName); err != nil {
|
||||
@@ -226,7 +226,7 @@ func (api *API) ServicesGetStatus(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
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)
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
serviceName := vars["service"]
|
||||
|
||||
servicesMgr := services.NewManager(api.dataDir, filepath.Join(api.directoryPath, "setup", "cluster-services"))
|
||||
servicesMgr := services.NewManager(api.dataDir)
|
||||
|
||||
// Get manifest
|
||||
manifest, err := servicesMgr.GetManifest(serviceName)
|
||||
@@ -286,7 +286,7 @@ func (api *API) ServicesGetInstanceConfig(w http.ResponseWriter, r *http.Request
|
||||
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
|
||||
manifest, err := servicesMgr.GetManifest(serviceName)
|
||||
@@ -364,7 +364,7 @@ func (api *API) ServicesFetch(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to fetch service: %v", err))
|
||||
return
|
||||
@@ -388,7 +388,7 @@ func (api *API) ServicesCompile(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to compile templates: %v", err))
|
||||
return
|
||||
@@ -412,7 +412,7 @@ func (api *API) ServicesDeploy(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// 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 {
|
||||
respondError(w, http.StatusInternalServerError, fmt.Sprintf("Failed to deploy service: %v", err))
|
||||
return
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
@@ -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")
|
||||
if !m.templatesExist(templatesDir) {
|
||||
if err := m.copyTemplatesFromDirectory(templatesDir); err != nil {
|
||||
return fmt.Errorf("failed to copy templates: %w", err)
|
||||
if err := m.extractEmbeddedTemplates(templatesDir); err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// copyTemplatesFromDirectory copies patch templates from directory/ to instance
|
||||
func (m *Manager) copyTemplatesFromDirectory(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)
|
||||
}
|
||||
|
||||
// extractEmbeddedTemplates extracts patch templates from embedded files to instance directory
|
||||
func (m *Manager) extractEmbeddedTemplates(destDir string) error {
|
||||
// Create destination directory
|
||||
if err := os.MkdirAll(destDir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create templates directory: %w", err)
|
||||
}
|
||||
|
||||
// Copy controlplane.yaml
|
||||
if err := m.copyFile(
|
||||
filepath.Join(sourceDir, "controlplane.yaml"),
|
||||
filepath.Join(destDir, "controlplane.yaml"),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to copy controlplane template: %w", err)
|
||||
// Get embedded template files
|
||||
controlplaneData, err := setup.GetClusterNodesFile("patch.templates/controlplane.yaml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get controlplane template: %w", err)
|
||||
}
|
||||
|
||||
// Copy worker.yaml
|
||||
if err := m.copyFile(
|
||||
filepath.Join(sourceDir, "worker.yaml"),
|
||||
filepath.Join(destDir, "worker.yaml"),
|
||||
); err != nil {
|
||||
return fmt.Errorf("failed to copy worker template: %w", err)
|
||||
workerData, err := setup.GetClusterNodesFile("patch.templates/worker.yaml")
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get 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
|
||||
@@ -660,9 +656,9 @@ func (m *Manager) Update(instanceName string, hostname string, updates map[strin
|
||||
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 {
|
||||
instancePath := m.GetInstancePath(instanceName)
|
||||
destDir := filepath.Join(instancePath, "setup", "cluster-nodes", "patch.templates")
|
||||
return m.copyTemplatesFromDirectory(destDir)
|
||||
return m.extractEmbeddedTemplates(destDir)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
@@ -10,30 +11,41 @@ import (
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"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/tools"
|
||||
)
|
||||
|
||||
// Manager handles base service operations
|
||||
type Manager struct {
|
||||
dataDir string
|
||||
servicesDir string // Path to services directory
|
||||
manifests map[string]*ServiceManifest // Cached service manifests
|
||||
dataDir string
|
||||
manifests map[string]*ServiceManifest // Cached service manifests
|
||||
}
|
||||
|
||||
// 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{
|
||||
dataDir: dataDir,
|
||||
servicesDir: servicesDir,
|
||||
dataDir: dataDir,
|
||||
}
|
||||
|
||||
// Load all service manifests
|
||||
manifests, err := LoadAllManifests(servicesDir)
|
||||
if err != nil {
|
||||
// Log error but continue - services without manifests will fall back to hardcoded map
|
||||
fmt.Printf("Warning: failed to load service manifests: %v\n", err)
|
||||
manifests = make(map[string]*ServiceManifest)
|
||||
// Load all service manifests from embedded files
|
||||
manifests := make(map[string]*ServiceManifest)
|
||||
services, err := setup.ListServices()
|
||||
if err == nil {
|
||||
for _, serviceName := range services {
|
||||
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
|
||||
|
||||
@@ -124,18 +136,13 @@ func (m *Manager) checkServiceStatus(instanceName, serviceName string) string {
|
||||
func (m *Manager) List(instanceName string) ([]Service, error) {
|
||||
services := []Service{}
|
||||
|
||||
// Discover services from the services directory
|
||||
entries, err := os.ReadDir(m.servicesDir)
|
||||
// Discover services from embedded setup files
|
||||
serviceNames, err := setup.ListServices()
|
||||
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 {
|
||||
if !entry.IsDir() {
|
||||
continue // Skip non-directories like README.md
|
||||
}
|
||||
|
||||
name := entry.Name()
|
||||
for _, name := range serviceNames {
|
||||
|
||||
// Get service info from manifest if available
|
||||
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 {
|
||||
kubeconfigPath := tools.GetKubeconfigPath(m.dataDir, instanceName)
|
||||
|
||||
serviceDir := filepath.Join(m.servicesDir, serviceName)
|
||||
manifestsFile := filepath.Join(serviceDir, "manifests.yaml")
|
||||
// Check if service exists in embedded files
|
||||
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) {
|
||||
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)
|
||||
@@ -292,12 +305,11 @@ func (m *Manager) GetConfigReferences(serviceName string) ([]string, error) {
|
||||
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 {
|
||||
// 1. Validate service exists in directory
|
||||
sourceDir := filepath.Join(m.servicesDir, serviceName)
|
||||
if !dirExists(sourceDir) {
|
||||
return fmt.Errorf("service %s not found in directory", serviceName)
|
||||
// 1. Validate service exists in embedded files
|
||||
if !setup.ServiceExists(serviceName) {
|
||||
return fmt.Errorf("service %s not found in embedded files", serviceName)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// 3. Copy files:
|
||||
// 3. Extract files from embedded setup:
|
||||
// - README.md (if exists, optional)
|
||||
// - install.sh (if exists, optional)
|
||||
// - wild-manifest.yaml
|
||||
// - kustomize.template/* (if exists, optional)
|
||||
|
||||
// Copy README.md
|
||||
copyFileIfExists(filepath.Join(sourceDir, "README.md"),
|
||||
filepath.Join(instanceDir, "README.md"))
|
||||
|
||||
// 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)
|
||||
// Extract README.md if it exists
|
||||
if readmeData, err := setup.GetServiceFile(serviceName, "README.md"); err == nil {
|
||||
os.WriteFile(filepath.Join(instanceDir, "README.md"), readmeData, 0644)
|
||||
}
|
||||
|
||||
// Copy kustomize.template directory if it exists
|
||||
templateDir := filepath.Join(sourceDir, "kustomize.template")
|
||||
if dirExists(templateDir) {
|
||||
// Extract install.sh if it exists
|
||||
if installData, err := setup.GetServiceFile(serviceName, "install.sh"); err == nil {
|
||||
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")
|
||||
if err := copyDir(templateDir, destTemplateDir); err != nil {
|
||||
return fmt.Errorf("failed to copy templates: %w", err)
|
||||
if err := extractFS(templateFS, destTemplateDir); err != nil {
|
||||
return fmt.Errorf("failed to extract templates: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -404,6 +421,32 @@ func copyDir(src, dst string) error {
|
||||
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
|
||||
func (m *Manager) Compile(instanceName, serviceName string) error {
|
||||
instanceDir := filepath.Join(m.dataDir, "instances", instanceName)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user