First commit of golang CLI.
This commit is contained in:
223
wild-cli/cmd/wild/app/deploy.go
Normal file
223
wild-cli/cmd/wild/app/deploy.go
Normal file
@@ -0,0 +1,223 @@
|
||||
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
|
||||
}
|
Reference in New Issue
Block a user