Files
wild-cloud/wild-cli/cmd/wild/app/fetch.go

169 lines
4.4 KiB
Go

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 <name>",
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
}