224 lines
6.1 KiB
Go
224 lines
6.1 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/spf13/cobra"
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/wild-cloud/wild-cli/internal/config"
|
|
"github.com/wild-cloud/wild-cli/internal/environment"
|
|
"github.com/wild-cloud/wild-cli/internal/external"
|
|
"github.com/wild-cloud/wild-cli/internal/output"
|
|
)
|
|
|
|
var (
|
|
force bool
|
|
dryRun bool
|
|
)
|
|
|
|
func newDeployCommand() *cobra.Command {
|
|
cmd := &cobra.Command{
|
|
Use: "deploy <name>",
|
|
Short: "Deploy an application to the cluster",
|
|
Long: `Deploy an application to the Kubernetes cluster.
|
|
|
|
This processes the app templates with current configuration and
|
|
deploys them using kubectl and kustomize.
|
|
|
|
Examples:
|
|
wild app deploy nextcloud
|
|
wild app deploy postgresql --force
|
|
wild app deploy myapp --dry-run`,
|
|
Args: cobra.ExactArgs(1),
|
|
RunE: runDeploy,
|
|
}
|
|
|
|
cmd.Flags().BoolVar(&force, "force", false, "force deployment (replace existing resources)")
|
|
cmd.Flags().BoolVar(&dryRun, "dry-run", false, "show what would be deployed without making changes")
|
|
|
|
return cmd
|
|
}
|
|
|
|
func runDeploy(cmd *cobra.Command, args []string) error {
|
|
appName := args[0]
|
|
|
|
output.Header("Deploying Application")
|
|
output.Info("App: " + appName)
|
|
|
|
// Initialize environment
|
|
env := environment.New()
|
|
if err := env.RequiresProject(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Check if app exists in project
|
|
appDir := filepath.Join(env.AppsDir(), appName)
|
|
if _, err := os.Stat(appDir); os.IsNotExist(err) {
|
|
return fmt.Errorf("app '%s' not found in project. Run 'wild app add %s' first", appName, appName)
|
|
}
|
|
|
|
// Check external tools
|
|
toolManager := external.NewManager()
|
|
if err := toolManager.CheckTools(cmd.Context(), []string{"kubectl"}); err != nil {
|
|
return fmt.Errorf("required tools not available: %w", err)
|
|
}
|
|
|
|
// Load configuration
|
|
configMgr := config.NewManager(env.ConfigPath(), env.SecretsPath())
|
|
|
|
// Check if app is enabled
|
|
enabledValue, err := configMgr.Get("apps." + appName + ".enabled")
|
|
if err != nil || enabledValue == nil {
|
|
output.Warning("App '" + appName + "' is not configured")
|
|
output.Info("Run: wild config set apps." + appName + ".enabled true")
|
|
return nil
|
|
}
|
|
|
|
enabled, ok := enabledValue.(bool)
|
|
if !ok || !enabled {
|
|
output.Warning("App '" + appName + "' is disabled")
|
|
output.Info("Run: wild config set apps." + appName + ".enabled true")
|
|
return nil
|
|
}
|
|
|
|
// Process templates with configuration
|
|
output.Info("Processing templates...")
|
|
processedDir := filepath.Join(env.WildCloudDir(), "processed", appName)
|
|
if err := os.RemoveAll(processedDir); err != nil {
|
|
return fmt.Errorf("cleaning processed directory: %w", err)
|
|
}
|
|
|
|
if err := processAppTemplates(appDir, processedDir, configMgr); err != nil {
|
|
return fmt.Errorf("processing templates: %w", err)
|
|
}
|
|
|
|
// Deploy secrets if required
|
|
if err := deployAppSecrets(cmd.Context(), appName, appDir, configMgr, toolManager.Kubectl()); err != nil {
|
|
return fmt.Errorf("deploying secrets: %w", err)
|
|
}
|
|
|
|
// Deploy using kubectl + kustomize
|
|
output.Info("Deploying to cluster...")
|
|
kubectl := toolManager.Kubectl()
|
|
|
|
if err := kubectl.ApplyKustomize(cmd.Context(), processedDir, "", dryRun); err != nil {
|
|
return fmt.Errorf("deploying with kubectl: %w", err)
|
|
}
|
|
|
|
if dryRun {
|
|
output.Success("Dry run completed - no changes made")
|
|
} else {
|
|
output.Success("App '" + appName + "' deployed successfully")
|
|
}
|
|
|
|
// Show next steps
|
|
output.Info("")
|
|
output.Info("Monitor deployment:")
|
|
output.Info(" kubectl get pods -n " + appName)
|
|
output.Info(" kubectl logs -f deployment/" + appName + " -n " + appName)
|
|
|
|
return nil
|
|
}
|
|
|
|
// processAppTemplates processes app templates with configuration
|
|
func processAppTemplates(appDir, processedDir string, configMgr *config.Manager) error {
|
|
// Create template engine
|
|
engine, err := config.NewTemplateEngine(configMgr)
|
|
if err != nil {
|
|
return fmt.Errorf("creating template engine: %w", err)
|
|
}
|
|
|
|
// Walk through app directory
|
|
return filepath.Walk(appDir, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Calculate relative path
|
|
relPath, err := filepath.Rel(appDir, path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
destPath := filepath.Join(processedDir, relPath)
|
|
|
|
if info.IsDir() {
|
|
return os.MkdirAll(destPath, info.Mode())
|
|
}
|
|
|
|
// Read file content
|
|
content, err := os.ReadFile(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Process as template if it's a YAML file
|
|
if strings.HasSuffix(path, ".yaml") || strings.HasSuffix(path, ".yml") {
|
|
processed, err := engine.Process(string(content))
|
|
if err != nil {
|
|
return fmt.Errorf("processing template %s: %w", relPath, err)
|
|
}
|
|
content = []byte(processed)
|
|
}
|
|
|
|
// Write processed content
|
|
if err := os.MkdirAll(filepath.Dir(destPath), 0755); err != nil {
|
|
return err
|
|
}
|
|
|
|
return os.WriteFile(destPath, content, info.Mode())
|
|
})
|
|
}
|
|
|
|
// deployAppSecrets deploys application secrets
|
|
func deployAppSecrets(ctx context.Context, appName, appDir string, configMgr *config.Manager, kubectl *external.KubectlTool) error {
|
|
// Check for manifest.yaml with required secrets
|
|
manifestPath := filepath.Join(appDir, "manifest.yaml")
|
|
manifestData, err := os.ReadFile(manifestPath)
|
|
if os.IsNotExist(err) {
|
|
return nil // No manifest, no secrets needed
|
|
}
|
|
if err != nil {
|
|
return fmt.Errorf("reading manifest: %w", err)
|
|
}
|
|
|
|
var manifest struct {
|
|
RequiredSecrets []string `yaml:"requiredSecrets"`
|
|
}
|
|
|
|
if err := yaml.Unmarshal(manifestData, &manifest); err != nil {
|
|
return fmt.Errorf("parsing manifest: %w", err)
|
|
}
|
|
|
|
if len(manifest.RequiredSecrets) == 0 {
|
|
return nil // No secrets required
|
|
}
|
|
|
|
output.Info("Deploying secrets...")
|
|
|
|
// Collect secret data
|
|
secretData := make(map[string]string)
|
|
for _, secretPath := range manifest.RequiredSecrets {
|
|
value, err := configMgr.GetSecret(secretPath)
|
|
if err != nil || value == nil {
|
|
return fmt.Errorf("required secret '%s' not found", secretPath)
|
|
}
|
|
|
|
secretData[secretPath] = fmt.Sprintf("%v", value)
|
|
}
|
|
|
|
// Create secret in cluster
|
|
secretName := appName + "-secrets"
|
|
if err := kubectl.CreateSecret(ctx, secretName, appName, secretData); err != nil {
|
|
return fmt.Errorf("creating secret: %w", err)
|
|
}
|
|
|
|
output.Success("Secrets deployed")
|
|
return nil
|
|
}
|