First commit of golang CLI.
This commit is contained in:
319
wild-cli/cmd/wild/app/list.go
Normal file
319
wild-cli/cmd/wild/app/list.go
Normal file
@@ -0,0 +1,319 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/wild-cloud/wild-cli/internal/environment"
|
||||
"github.com/wild-cloud/wild-cli/internal/output"
|
||||
)
|
||||
|
||||
// AppManifest represents the structure of manifest.yaml files
|
||||
type AppManifest struct {
|
||||
Name string `yaml:"name"`
|
||||
Version string `yaml:"version"`
|
||||
Description string `yaml:"description"`
|
||||
Install bool `yaml:"install"`
|
||||
Icon string `yaml:"icon"`
|
||||
Requires []struct {
|
||||
Name string `yaml:"name"`
|
||||
} `yaml:"requires"`
|
||||
}
|
||||
|
||||
// AppInfo represents an installable app with its status
|
||||
type AppInfo struct {
|
||||
Name string
|
||||
Version string
|
||||
Description string
|
||||
Icon string
|
||||
Requires []string
|
||||
Installed bool
|
||||
InstalledVersion string
|
||||
}
|
||||
|
||||
var (
|
||||
searchQuery string
|
||||
verbose bool
|
||||
outputFormat string
|
||||
)
|
||||
|
||||
func newListCommand() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List available applications",
|
||||
Long: `List all available Wild Cloud apps with their metadata.
|
||||
|
||||
This command shows applications from the Wild Cloud installation directory.
|
||||
Apps are read from WC_ROOT/apps and filtered to show only installable ones.
|
||||
|
||||
Examples:
|
||||
wild app list
|
||||
wild app list --search database
|
||||
wild app list --verbose`,
|
||||
RunE: runList,
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&searchQuery, "search", "", "search applications by name or description")
|
||||
cmd.Flags().BoolVarP(&verbose, "verbose", "v", false, "show additional metadata (icon, requires)")
|
||||
cmd.Flags().StringVar(&outputFormat, "format", "table", "output format: table, json, yaml")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func runList(cmd *cobra.Command, args []string) error {
|
||||
// Initialize environment
|
||||
env := environment.New()
|
||||
if err := env.RequiresInstallation(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Get apps directory from WC_ROOT
|
||||
appsDir := filepath.Join(env.WCRoot(), "apps")
|
||||
if _, err := os.Stat(appsDir); os.IsNotExist(err) {
|
||||
return fmt.Errorf("apps directory not found at %s", appsDir)
|
||||
}
|
||||
|
||||
// Get project apps directory if available
|
||||
var projectAppsDir string
|
||||
if env.WCHome() != "" {
|
||||
projectAppsDir = filepath.Join(env.WCHome(), "apps")
|
||||
}
|
||||
|
||||
// Read all installable apps
|
||||
apps, err := getInstallableApps(appsDir, projectAppsDir)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read apps: %w", err)
|
||||
}
|
||||
|
||||
// Filter by search query
|
||||
if searchQuery != "" {
|
||||
apps = filterApps(apps, searchQuery)
|
||||
}
|
||||
|
||||
if len(apps) == 0 {
|
||||
output.Warning("No applications found matching criteria")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Display results based on format
|
||||
switch outputFormat {
|
||||
case "json":
|
||||
return outputJSON(apps)
|
||||
case "yaml":
|
||||
return outputYAML(apps)
|
||||
default:
|
||||
return outputTable(apps, verbose)
|
||||
}
|
||||
}
|
||||
|
||||
// getInstallableApps reads apps from WC_ROOT/apps directory and checks installation status
|
||||
func getInstallableApps(appsDir, projectAppsDir string) ([]AppInfo, error) {
|
||||
var apps []AppInfo
|
||||
|
||||
entries, err := os.ReadDir(appsDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("reading apps directory: %w", err)
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() {
|
||||
continue
|
||||
}
|
||||
|
||||
appName := entry.Name()
|
||||
appDir := filepath.Join(appsDir, appName)
|
||||
manifestPath := filepath.Join(appDir, "manifest.yaml")
|
||||
|
||||
// Skip if no manifest.yaml
|
||||
if _, err := os.Stat(manifestPath); os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Parse manifest
|
||||
manifestData, err := os.ReadFile(manifestPath)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
var manifest AppManifest
|
||||
if err := yaml.Unmarshal(manifestData, &manifest); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip if not installable
|
||||
if !manifest.Install {
|
||||
continue
|
||||
}
|
||||
|
||||
// Extract requires list
|
||||
var requires []string
|
||||
for _, req := range manifest.Requires {
|
||||
requires = append(requires, req.Name)
|
||||
}
|
||||
|
||||
// Check installation status
|
||||
installed := false
|
||||
installedVersion := ""
|
||||
if projectAppsDir != "" {
|
||||
projectManifestPath := filepath.Join(projectAppsDir, appName, "manifest.yaml")
|
||||
if projectManifestData, err := os.ReadFile(projectManifestPath); err == nil {
|
||||
var projectManifest AppManifest
|
||||
if err := yaml.Unmarshal(projectManifestData, &projectManifest); err == nil {
|
||||
installed = true
|
||||
installedVersion = projectManifest.Version
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app := AppInfo{
|
||||
Name: manifest.Name,
|
||||
Version: manifest.Version,
|
||||
Description: manifest.Description,
|
||||
Icon: manifest.Icon,
|
||||
Requires: requires,
|
||||
Installed: installed,
|
||||
InstalledVersion: installedVersion,
|
||||
}
|
||||
|
||||
apps = append(apps, app)
|
||||
}
|
||||
|
||||
return apps, nil
|
||||
}
|
||||
|
||||
// filterApps filters apps by search query (name or description)
|
||||
func filterApps(apps []AppInfo, query string) []AppInfo {
|
||||
query = strings.ToLower(query)
|
||||
var filtered []AppInfo
|
||||
|
||||
for _, app := range apps {
|
||||
if strings.Contains(strings.ToLower(app.Name), query) ||
|
||||
strings.Contains(strings.ToLower(app.Description), query) {
|
||||
filtered = append(filtered, app)
|
||||
}
|
||||
}
|
||||
|
||||
return filtered
|
||||
}
|
||||
|
||||
// outputTable displays apps in table format
|
||||
func outputTable(apps []AppInfo, verbose bool) error {
|
||||
if verbose {
|
||||
output.Header("Available Wild Cloud Apps (verbose)")
|
||||
output.Printf("%-15s %-10s %-12s %-40s %-15s %s\n", "NAME", "VERSION", "INSTALLED", "DESCRIPTION", "REQUIRES", "ICON")
|
||||
output.Printf("%-15s %-10s %-12s %-40s %-15s %s\n", "----", "-------", "---------", "-----------", "--------", "----")
|
||||
} else {
|
||||
output.Header("Available Wild Cloud Apps")
|
||||
output.Printf("%-15s %-10s %-12s %s\n", "NAME", "VERSION", "INSTALLED", "DESCRIPTION")
|
||||
output.Printf("%-15s %-10s %-12s %s\n", "----", "-------", "---------", "-----------")
|
||||
}
|
||||
|
||||
for _, app := range apps {
|
||||
installedStatus := "NO"
|
||||
if app.Installed {
|
||||
installedStatus = app.InstalledVersion
|
||||
}
|
||||
|
||||
description := app.Description
|
||||
if len(description) > 40 && !verbose {
|
||||
description = description[:37] + "..."
|
||||
}
|
||||
|
||||
if verbose {
|
||||
requiresList := strings.Join(app.Requires, ",")
|
||||
if len(requiresList) > 15 {
|
||||
requiresList = requiresList[:12] + "..."
|
||||
}
|
||||
icon := app.Icon
|
||||
if len(icon) > 30 {
|
||||
icon = icon[:27] + "..."
|
||||
}
|
||||
output.Printf("%-15s %-10s %-12s %-40s %-15s %s\n", app.Name, app.Version, installedStatus, description, requiresList, icon)
|
||||
} else {
|
||||
output.Printf("%-15s %-10s %-12s %s\n", app.Name, app.Version, installedStatus, description)
|
||||
}
|
||||
}
|
||||
|
||||
output.Info("")
|
||||
output.Info(fmt.Sprintf("Total installable apps: %d", len(apps)))
|
||||
output.Info("")
|
||||
output.Info("Usage:")
|
||||
output.Info(" wild app fetch <app> # Fetch app template to project")
|
||||
output.Info(" wild app deploy <app> # Deploy app to Kubernetes")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// outputJSON displays apps in JSON format
|
||||
func outputJSON(apps []AppInfo) error {
|
||||
output.Printf("{\n")
|
||||
output.Printf(" \"apps\": [\n")
|
||||
|
||||
for i, app := range apps {
|
||||
output.Printf(" {\n")
|
||||
output.Printf(" \"name\": \"%s\",\n", app.Name)
|
||||
output.Printf(" \"version\": \"%s\",\n", app.Version)
|
||||
output.Printf(" \"description\": \"%s\",\n", app.Description)
|
||||
output.Printf(" \"icon\": \"%s\",\n", app.Icon)
|
||||
output.Printf(" \"requires\": [")
|
||||
for j, req := range app.Requires {
|
||||
output.Printf("\"%s\"", req)
|
||||
if j < len(app.Requires)-1 {
|
||||
output.Printf(", ")
|
||||
}
|
||||
}
|
||||
output.Printf("],\n")
|
||||
if app.Installed {
|
||||
output.Printf(" \"installed\": \"%s\",\n", app.InstalledVersion)
|
||||
} else {
|
||||
output.Printf(" \"installed\": \"NO\",\n")
|
||||
}
|
||||
output.Printf(" \"installed_version\": \"%s\"\n", app.InstalledVersion)
|
||||
output.Printf(" }")
|
||||
if i < len(apps)-1 {
|
||||
output.Printf(",")
|
||||
}
|
||||
output.Printf("\n")
|
||||
}
|
||||
|
||||
output.Printf(" ],\n")
|
||||
output.Printf(" \"total\": %d\n", len(apps))
|
||||
output.Printf("}\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// outputYAML displays apps in YAML format
|
||||
func outputYAML(apps []AppInfo) error {
|
||||
output.Printf("apps:\n")
|
||||
|
||||
for _, app := range apps {
|
||||
output.Printf("- name: %s\n", app.Name)
|
||||
output.Printf(" version: %s\n", app.Version)
|
||||
output.Printf(" description: %s\n", app.Description)
|
||||
if app.Installed {
|
||||
output.Printf(" installed: %s\n", app.InstalledVersion)
|
||||
} else {
|
||||
output.Printf(" installed: NO\n")
|
||||
}
|
||||
if app.InstalledVersion != "" {
|
||||
output.Printf(" installed_version: %s\n", app.InstalledVersion)
|
||||
}
|
||||
if app.Icon != "" {
|
||||
output.Printf(" icon: %s\n", app.Icon)
|
||||
}
|
||||
if len(app.Requires) > 0 {
|
||||
output.Printf(" requires:\n")
|
||||
for _, req := range app.Requires {
|
||||
output.Printf(" - %s\n", req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
Reference in New Issue
Block a user