package app import ( "fmt" "io" "os" "path/filepath" "github.com/spf13/cobra" "gopkg.in/yaml.v3" "github.com/wild-cloud/wild-cli/internal/environment" "github.com/wild-cloud/wild-cli/internal/output" ) var ( updateCache bool ) func newFetchCommand() *cobra.Command { cmd := &cobra.Command{ Use: "fetch ", Short: "Fetch an application template", Long: `Fetch an app template from the Wild Cloud repository to cache. This command copies an application template from WC_ROOT/apps to your project's cache directory (.wildcloud/cache/apps) for configuration and deployment. Examples: wild app fetch postgres wild app fetch immich wild app fetch redis --update`, Args: cobra.ExactArgs(1), RunE: runFetch, } cmd.Flags().BoolVar(&updateCache, "update", false, "overwrite existing cached files without confirmation") return cmd } func runFetch(cmd *cobra.Command, args []string) error { appName := args[0] output.Header("Fetching Application") output.Info("App: " + appName) // Initialize environment env := environment.New() if err := env.RequiresInstallation(); err != nil { return err } if err := env.RequiresProject(); err != nil { return err } // Check if source app exists sourceAppDir := filepath.Join(env.WCRoot(), "apps", appName) if _, err := os.Stat(sourceAppDir); os.IsNotExist(err) { return fmt.Errorf("app '%s' not found at %s", appName, sourceAppDir) } // Read app manifest for info manifestPath := filepath.Join(sourceAppDir, "manifest.yaml") if manifestData, err := os.ReadFile(manifestPath); err == nil { var manifest AppManifest if err := yaml.Unmarshal(manifestData, &manifest); err == nil { output.Info("Description: " + manifest.Description) output.Info("Version: " + manifest.Version) } } // Set up cache directory cacheAppDir := filepath.Join(env.WCHome(), ".wildcloud", "cache", "apps", appName) // Create cache directory structure if err := os.MkdirAll(filepath.Join(env.WCHome(), ".wildcloud", "cache", "apps"), 0755); err != nil { return fmt.Errorf("creating cache directory: %w", err) } // Check if already cached if _, err := os.Stat(cacheAppDir); err == nil { if updateCache { output.Info("Updating cached app '" + appName + "'") if err := os.RemoveAll(cacheAppDir); err != nil { return fmt.Errorf("removing existing cache: %w", err) } } else { output.Warning("Cache directory " + cacheAppDir + " already exists") output.Printf("Do you want to overwrite it? (y/N): ") var response string if _, err := fmt.Scanln(&response); err != nil || (response != "y" && response != "Y") { output.Info("Fetch cancelled") return nil } if err := os.RemoveAll(cacheAppDir); err != nil { return fmt.Errorf("removing existing cache: %w", err) } } } output.Info(fmt.Sprintf("Fetching app '%s' from %s to %s", appName, sourceAppDir, cacheAppDir)) // Copy the entire directory structure if err := copyDirFetch(sourceAppDir, cacheAppDir); err != nil { return fmt.Errorf("copying app directory: %w", err) } output.Success("Successfully fetched app '" + appName + "' to cache") output.Info("") output.Info("Next steps:") output.Info(" wild app add " + appName + " # Add to project with configuration") output.Info(" wild app deploy " + appName + " # Deploy to cluster") return nil } // copyDirFetch recursively copies a directory from src to dst func copyDirFetch(src, dst string) error { return filepath.Walk(src, func(path string, info os.FileInfo, err error) error { if err != nil { return err } // Calculate relative path relPath, err := filepath.Rel(src, path) if err != nil { return err } // Calculate destination path dstPath := filepath.Join(dst, relPath) if info.IsDir() { // Create directory return os.MkdirAll(dstPath, info.Mode()) } // Copy file return copyFileFetch(path, dstPath) }) } // copyFileFetch copies a single file from src to dst func copyFileFetch(src, dst string) error { // Create destination directory if it doesn't exist if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil { return err } // Open source file srcFile, err := os.Open(src) if err != nil { return err } defer func() { _ = srcFile.Close() }() // Create destination file dstFile, err := os.Create(dst) if err != nil { return err } defer func() { _ = dstFile.Close() }() // Copy file contents _, err = io.Copy(dstFile, srcFile) return err }