First commit of golang CLI.

This commit is contained in:
2025-08-31 11:51:11 -07:00
parent 4ca06aecb6
commit f0a2098f11
51 changed files with 8840 additions and 0 deletions

View File

@@ -0,0 +1,378 @@
package apps
import (
"fmt"
"os"
"path/filepath"
"strings"
"gopkg.in/yaml.v3"
)
// App represents an application in the catalog
type App struct {
Name string `yaml:"name"`
Description string `yaml:"description"`
Version string `yaml:"version"`
Category string `yaml:"category"`
Homepage string `yaml:"homepage"`
Source string `yaml:"source"`
Tags []string `yaml:"tags"`
Requires []string `yaml:"requires"`
Provides map[string]string `yaml:"provides"`
Config map[string]interface{} `yaml:"config"`
}
// Catalog manages the application catalog
type Catalog struct {
cacheDir string
apps []App
loaded bool
}
// NewCatalog creates a new app catalog
func NewCatalog(cacheDir string) *Catalog {
return &Catalog{
cacheDir: cacheDir,
}
}
// LoadCatalog loads the app catalog from cache or remote source
func (c *Catalog) LoadCatalog() error {
if c.loaded {
return nil
}
// Try to load from cache first
catalogPath := filepath.Join(c.cacheDir, "catalog.yaml")
if err := c.loadFromFile(catalogPath); err == nil {
c.loaded = true
return nil
}
// If cache fails, try to fetch from remote
if err := c.fetchRemoteCatalog(); err != nil {
return fmt.Errorf("failed to load catalog: %w", err)
}
c.loaded = true
return nil
}
// loadFromFile loads catalog from a local file
func (c *Catalog) loadFromFile(path string) error {
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("reading catalog file: %w", err)
}
var catalogData struct {
Apps []App `yaml:"apps"`
}
if err := yaml.Unmarshal(data, &catalogData); err != nil {
return fmt.Errorf("parsing catalog YAML: %w", err)
}
c.apps = catalogData.Apps
return nil
}
// fetchRemoteCatalog fetches catalog from remote source
func (c *Catalog) fetchRemoteCatalog() error {
// For now, create a default catalog
// In production, this would fetch from a remote URL
defaultCatalog := []App{
{
Name: "nextcloud",
Description: "Self-hosted file sync and share platform",
Version: "latest",
Category: "productivity",
Homepage: "https://nextcloud.com",
Source: "https://github.com/wild-cloud/app-nextcloud",
Tags: []string{"files", "sync", "collaboration"},
Requires: []string{"postgresql"},
Provides: map[string]string{"files": "nextcloud"},
},
{
Name: "postgresql",
Description: "Powerful, open source object-relational database",
Version: "15",
Category: "database",
Homepage: "https://postgresql.org",
Source: "https://github.com/wild-cloud/app-postgresql",
Tags: []string{"database", "sql"},
Provides: map[string]string{"database": "postgresql"},
},
{
Name: "traefik",
Description: "Modern HTTP reverse proxy and load balancer",
Version: "v3.0",
Category: "infrastructure",
Homepage: "https://traefik.io",
Source: "https://github.com/wild-cloud/app-traefik",
Tags: []string{"proxy", "loadbalancer", "ingress"},
Provides: map[string]string{"ingress": "traefik"},
},
{
Name: "monitoring",
Description: "Prometheus and Grafana monitoring stack",
Version: "latest",
Category: "infrastructure",
Homepage: "https://prometheus.io",
Source: "https://github.com/wild-cloud/app-monitoring",
Tags: []string{"monitoring", "metrics", "alerting"},
Provides: map[string]string{"monitoring": "prometheus"},
},
}
c.apps = defaultCatalog
// Save to cache
return c.saveCatalogToCache()
}
// saveCatalogToCache saves the catalog to cache
func (c *Catalog) saveCatalogToCache() error {
catalogData := struct {
Apps []App `yaml:"apps"`
}{
Apps: c.apps,
}
data, err := yaml.Marshal(catalogData)
if err != nil {
return fmt.Errorf("marshaling catalog: %w", err)
}
catalogPath := filepath.Join(c.cacheDir, "catalog.yaml")
if err := os.MkdirAll(filepath.Dir(catalogPath), 0755); err != nil {
return fmt.Errorf("creating cache directory: %w", err)
}
if err := os.WriteFile(catalogPath, data, 0644); err != nil {
return fmt.Errorf("writing catalog file: %w", err)
}
return nil
}
// ListApps returns all apps in the catalog
func (c *Catalog) ListApps() ([]App, error) {
if err := c.LoadCatalog(); err != nil {
return nil, err
}
return c.apps, nil
}
// FindApp finds an app by name
func (c *Catalog) FindApp(name string) (*App, error) {
if err := c.LoadCatalog(); err != nil {
return nil, err
}
for _, app := range c.apps {
if app.Name == name {
return &app, nil
}
}
return nil, fmt.Errorf("app '%s' not found in catalog", name)
}
// SearchApps searches for apps by name or tag
func (c *Catalog) SearchApps(query string) ([]App, error) {
if err := c.LoadCatalog(); err != nil {
return nil, err
}
var results []App
query = strings.ToLower(query)
for _, app := range c.apps {
// Check name
if strings.Contains(strings.ToLower(app.Name), query) {
results = append(results, app)
continue
}
// Check description
if strings.Contains(strings.ToLower(app.Description), query) {
results = append(results, app)
continue
}
// Check tags
for _, tag := range app.Tags {
if strings.Contains(strings.ToLower(tag), query) {
results = append(results, app)
break
}
}
}
return results, nil
}
// FetchApp downloads an app template to cache
func (c *Catalog) FetchApp(name string) error {
app, err := c.FindApp(name)
if err != nil {
return err
}
appCacheDir := filepath.Join(c.cacheDir, "apps", name)
if err := os.MkdirAll(appCacheDir, 0755); err != nil {
return fmt.Errorf("creating app cache directory: %w", err)
}
// For now, create a basic app template
// In production, this would clone from app.Source
if err := c.createAppTemplate(app, appCacheDir); err != nil {
return fmt.Errorf("creating app template: %w", err)
}
return nil
}
// createAppTemplate creates a basic app template structure
func (c *Catalog) createAppTemplate(app *App, dir string) error {
// Create manifest.yaml
manifest := map[string]interface{}{
"name": app.Name,
"version": app.Version,
"description": app.Description,
"requires": app.Requires,
"provides": app.Provides,
"config": app.Config,
}
manifestData, err := yaml.Marshal(manifest)
if err != nil {
return fmt.Errorf("marshaling manifest: %w", err)
}
manifestPath := filepath.Join(dir, "manifest.yaml")
if err := os.WriteFile(manifestPath, manifestData, 0644); err != nil {
return fmt.Errorf("writing manifest: %w", err)
}
// Create basic kubernetes manifests
if err := c.createKubernetesManifests(app, dir); err != nil {
return fmt.Errorf("creating kubernetes manifests: %w", err)
}
return nil
}
// createKubernetesManifests creates basic Kubernetes manifest templates
func (c *Catalog) createKubernetesManifests(app *App, dir string) error {
// Create namespace.yaml
namespace := fmt.Sprintf(`apiVersion: v1
kind: Namespace
metadata:
name: %s
labels:
app: %s
`, app.Name, app.Name)
if err := os.WriteFile(filepath.Join(dir, "namespace.yaml"), []byte(namespace), 0644); err != nil {
return fmt.Errorf("writing namespace.yaml: %w", err)
}
// Create basic deployment template
deployment := fmt.Sprintf(`apiVersion: apps/v1
kind: Deployment
metadata:
name: %s
namespace: %s
spec:
replicas: {{.config.%s.replicas | default 1}}
selector:
matchLabels:
app: %s
template:
metadata:
labels:
app: %s
spec:
containers:
- name: %s
image: {{.config.%s.image | default "%s:latest"}}
ports:
- containerPort: 8080
`, app.Name, app.Name, app.Name, app.Name, app.Name, app.Name, app.Name, app.Name)
if err := os.WriteFile(filepath.Join(dir, "deployment.yaml"), []byte(deployment), 0644); err != nil {
return fmt.Errorf("writing deployment.yaml: %w", err)
}
// Create service.yaml
service := fmt.Sprintf(`apiVersion: v1
kind: Service
metadata:
name: %s
namespace: %s
spec:
selector:
app: %s
ports:
- port: 80
targetPort: 8080
type: ClusterIP
`, app.Name, app.Name, app.Name)
if err := os.WriteFile(filepath.Join(dir, "service.yaml"), []byte(service), 0644); err != nil {
return fmt.Errorf("writing service.yaml: %w", err)
}
// Create kustomization.yaml
kustomization := `apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- namespace.yaml
- deployment.yaml
- service.yaml
`
if err := os.WriteFile(filepath.Join(dir, "kustomization.yaml"), []byte(kustomization), 0644); err != nil {
return fmt.Errorf("writing kustomization.yaml: %w", err)
}
return nil
}
// IsAppCached checks if an app is cached locally
func (c *Catalog) IsAppCached(name string) bool {
appCacheDir := filepath.Join(c.cacheDir, "apps", name)
manifestPath := filepath.Join(appCacheDir, "manifest.yaml")
_, err := os.Stat(manifestPath)
return err == nil
}
// GetCachedApps returns list of cached apps
func (c *Catalog) GetCachedApps() ([]string, error) {
appsDir := filepath.Join(c.cacheDir, "apps")
entries, err := os.ReadDir(appsDir)
if err != nil {
if os.IsNotExist(err) {
return []string{}, nil
}
return nil, fmt.Errorf("reading apps directory: %w", err)
}
var cachedApps []string
for _, entry := range entries {
if entry.IsDir() {
manifestPath := filepath.Join(appsDir, entry.Name(), "manifest.yaml")
if _, err := os.Stat(manifestPath); err == nil {
cachedApps = append(cachedApps, entry.Name())
}
}
}
return cachedApps, nil
}