sync

package
v0.0.0-...-cbbac0e Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 25, 2026 License: MIT Imports: 14 Imported by: 0

Documentation

Overview

Package sync provides repository synchronization for StreamSpace templates and plugins.

The sync service enables StreamSpace to:

  • Clone and pull from external Git repositories
  • Parse template and plugin manifests
  • Update the catalog database with discovered resources
  • Run periodic background synchronization

Architecture:

  • SyncService: Orchestrates the sync process
  • GitClient: Handles Git operations (clone, pull, authentication)
  • TemplateParser: Parses template YAML manifests
  • PluginParser: Parses plugin JSON manifests

Workflow:

  1. Administrator adds repository via API
  2. Sync service clones repository to work directory
  3. Parsers discover manifests in repository
  4. Catalog database is updated with new/updated resources
  5. Users can browse and install from catalog
  6. Periodic sync keeps catalog up-to-date

Example repositories:

Configuration:

  • SYNC_WORK_DIR: Directory for cloned repositories (default: /tmp/streamspace-repos)
  • SYNC_INTERVAL: Time between automatic syncs (default: 1h)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AuthConfig

type AuthConfig struct {
	Type   string // none, ssh, token, basic
	Secret string // Secret value (SSH key, token, or password)
}

AuthConfig represents authentication configuration for Git

type GitClient

type GitClient struct {
	// contains filtered or unexported fields
}

GitClient handles Git repository operations for StreamSpace repository synchronization.

The client provides:

  • Repository cloning with shallow fetch (--depth 1)
  • Pulling latest changes (fetch + reset --hard)
  • Authentication support (SSH keys, tokens, basic auth)
  • Commit hash retrieval
  • Git availability validation

Authentication types:

  • "none": Public repositories (no credentials)
  • "ssh": Private repositories with SSH keys
  • "token": GitHub/GitLab personal access tokens
  • "basic": Username/password authentication

Security features:

  • SSH keys written to temporary files with 0600 permissions
  • StrictHostKeyChecking disabled for automation
  • No interactive prompts (GIT_TERMINAL_PROMPT=0)
  • Credentials injected via URL or environment variables

Example usage:

client := NewGitClient()
auth := &AuthConfig{Type: "token", Secret: "ghp_xxx"}
err := client.Clone(ctx, "https://github.com/user/repo", "/tmp/repo", "main", auth)

func NewGitClient

func NewGitClient() *GitClient

NewGitClient creates a new Git client with default settings.

Default configuration:

  • timeout: 5 minutes (prevents hanging on large repos)

Example:

client := NewGitClient()
err := client.Clone(ctx, repoURL, localPath, "main", nil)

func (*GitClient) Clone

func (g *GitClient) Clone(ctx context.Context, url, path, branch string, auth *AuthConfig) error

Clone clones a Git repository to a local path.

The clone operation:

  1. Removes existing directory if present (fresh clone)
  2. Performs shallow clone with --depth 1 (faster, smaller)
  3. Checks out specified branch (or default branch)
  4. Applies authentication if provided

Authentication is applied via:

  • SSH: GIT_SSH_COMMAND with temporary key file
  • Token: Injected into URL (https://token@github.com/...)
  • Basic: Username:password in URL

Parameters:

  • ctx: Context for cancellation and timeout
  • url: Git repository URL (HTTPS or SSH)
  • path: Local filesystem path for clone
  • branch: Branch name to checkout (empty for default)
  • auth: Authentication configuration (nil for public repos)

Returns an error if:

  • Directory removal fails
  • Git clone command fails
  • Authentication is invalid

Example:

auth := &AuthConfig{Type: "token", Secret: "ghp_xxxxx"}
err := client.Clone(ctx, "https://github.com/user/repo", "/tmp/repo", "main", auth)

func (*GitClient) GetCommitHash

func (g *GitClient) GetCommitHash(ctx context.Context, path string) (string, error)

GetCommitHash returns the current commit hash of a repository.

This retrieves the full SHA-1 hash of the current HEAD commit. The hash can be used to:

  • Track which version of a repository was synced
  • Detect when updates are available
  • Record sync history in database

Parameters:

  • ctx: Context for cancellation and timeout
  • path: Local repository path

Returns:

  • Full SHA-1 commit hash (40 characters)
  • Error if repository is invalid or git command fails

Example:

hash, err := client.GetCommitHash(ctx, "/tmp/repo")
// hash = "7092ff4a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q"

func (*GitClient) Pull

func (g *GitClient) Pull(ctx context.Context, path, branch string, auth *AuthConfig) error

Pull pulls the latest changes from a Git repository.

The pull operation:

  1. Fetches latest changes from origin
  2. Hard resets to origin/branch (discards local changes)
  3. Cleans untracked files (git clean -fd)

This is a destructive operation that:

  • Discards any local modifications
  • Removes untracked files
  • Ensures repository matches remote exactly

This behavior is intentional for repository sync, where the remote is the source of truth and local changes should never occur.

Parameters:

  • ctx: Context for cancellation and timeout
  • path: Local repository path
  • branch: Branch name to reset to (empty for "main")
  • auth: Authentication configuration (nil for public repos)

Returns an error if:

  • Fetch fails (network issues, auth problems)
  • Reset fails (branch doesn't exist)
  • Clean fails (permission issues)

Example:

err := client.Pull(ctx, "/tmp/repo", "main", auth)

func (*GitClient) Validate

func (g *GitClient) Validate() error

Validate validates that Git is installed and accessible.

This check should be performed on service startup to fail fast if Git is not available, rather than failing later during sync operations.

Validation steps:

  1. Execute "git --version" command
  2. Verify command succeeds (exit code 0)
  3. Verify output contains "git version"

Returns an error if:

  • Git command not found (not installed or not in PATH)
  • Git command fails to execute
  • Output doesn't match expected format

Example:

client := NewGitClient()
if err := client.Validate(); err != nil {
    log.Fatal("Git is not available:", err)
}

type ParsedPlugin

type ParsedPlugin struct {
	// Name is the unique plugin identifier.
	// Format: lowercase, hyphens, no spaces
	// Example: "streamspace-analytics-advanced", "streamspace-billing"
	Name string

	// Version is the semantic version.
	// Format: MAJOR.MINOR.PATCH (e.g., "1.2.0", "2.0.0-beta.1")
	Version string

	// DisplayName is the human-readable plugin name shown in UI.
	// Example: "Advanced Analytics", "Billing Integration"
	DisplayName string

	// Description explains what this plugin does.
	Description string

	// Category organizes plugins in the catalog.
	// Examples: "Analytics", "Security", "Integrations", "UI Enhancements"
	Category string

	// PluginType indicates the plugin's architecture.
	// Valid values: "extension", "webhook", "api", "ui", "theme"
	PluginType string

	// Icon is the URL to the plugin's icon image.
	// Can be relative (in repo) or absolute (CDN)
	Icon string

	// Manifest is the full manifest.json encoded as JSON string.
	// Stored in database for plugin installation and configuration.
	Manifest string

	// Tags are keywords for search and filtering.
	// Example: ["analytics", "reporting", "metrics"]
	Tags []string
}

ParsedPlugin represents a plugin extracted from a repository manifest.

This structure contains metadata for catalog database insertion. The full manifest is stored as JSON for configuration and installation.

Field mappings:

  • Name: Unique plugin identifier (lowercase, hyphens)
  • Version: Semantic version (MAJOR.MINOR.PATCH)
  • DisplayName: Human-readable name for UI
  • Description: Plugin purpose and features
  • Category: Catalog organization (Analytics, Security, etc.)
  • PluginType: Architecture type (extension, webhook, api, ui, theme)
  • Icon: URL to icon image
  • Manifest: Full manifest.json as JSON string
  • Tags: Keywords for search/filtering

Example:

plugin := &ParsedPlugin{
    Name: "streamspace-analytics-advanced",
    Version: "1.2.0",
    DisplayName: "Advanced Analytics",
    PluginType: "api",
    Tags: []string{"analytics", "reporting"},
}

type ParsedTemplate

type ParsedTemplate struct {
	// Name is the unique identifier from metadata.name.
	// Format: lowercase, hyphens, no spaces
	// Example: "firefox-browser", "vscode-dev"
	Name string

	// DisplayName is the human-readable name shown in UI.
	// Example: "Firefox Web Browser", "Visual Studio Code"
	DisplayName string

	// Description explains what this template provides.
	// Markdown formatting is supported.
	Description string

	// Category organizes templates in the catalog.
	// Examples: "Web Browsers", "Development", "Design"
	Category string

	// AppType indicates the application streaming type.
	// Valid values: "desktop" (VNC), "webapp" (HTTP)
	AppType string

	// Icon is the URL to the template's icon image.
	// Can be relative (in repo) or absolute (CDN)
	Icon string

	// Manifest is the full YAML manifest encoded as JSON.
	// Stored in database for template instantiation.
	Manifest string

	// Tags are keywords for search and filtering.
	// Example: ["browser", "web", "privacy"]
	Tags []string
}

ParsedTemplate represents a template extracted from a repository manifest.

This structure contains metadata for catalog database insertion. The full manifest is stored as JSON for future reference and validation.

Field mappings:

  • Name: metadata.name from YAML
  • DisplayName: spec.displayName (UI-friendly name)
  • Description: spec.description (markdown supported)
  • Category: spec.category (for catalog grouping)
  • AppType: "desktop" (VNC) or "webapp" (HTTP)
  • Icon: URL to icon image
  • Manifest: Full YAML manifest as JSON string
  • Tags: Keywords for search/filtering

Example:

template := &ParsedTemplate{
    Name: "firefox-browser",
    DisplayName: "Firefox Web Browser",
    Category: "Web Browsers",
    AppType: "desktop",
    Tags: []string{"browser", "web", "privacy"},
}

type PluginManifest

type PluginManifest struct {
	// Name is the unique plugin identifier (required).
	// Format: lowercase, hyphens, no spaces
	Name string `json:"name"`

	// Version is the semantic version (required).
	// Format: MAJOR.MINOR.PATCH
	Version string `json:"version"`

	// DisplayName is the human-readable name (required).
	DisplayName string `json:"displayName"`

	// Description explains the plugin's purpose (required).
	Description string `json:"description"`

	// Author is the plugin developer/organization.
	Author string `json:"author"`

	// Homepage is a URL to the plugin's website or documentation.
	Homepage string `json:"homepage,omitempty"`

	// Repository is the source code repository URL.
	Repository string `json:"repository,omitempty"`

	// License is the SPDX license identifier (e.g., "MIT", "Apache-2.0").
	License string `json:"license,omitempty"`

	// Type is the plugin architecture type (required).
	// Valid values: "extension", "webhook", "api", "ui", "theme"
	Type string `json:"type"`

	// Category organizes plugins in the catalog.
	Category string `json:"category,omitempty"`

	// Tags are keywords for search and filtering.
	Tags []string `json:"tags,omitempty"`

	// Icon is a relative path to the icon file in the plugin directory.
	Icon string `json:"icon,omitempty"`

	// Requirements specifies platform version and dependency requirements.
	// Example: {"streamspaceVersion": ">=0.2.0"}
	Requirements map[string]string `json:"requirements,omitempty"`

	// Entrypoints define where to load plugin code.
	// Example: {"main": "index.js", "api": "api/routes.js"}
	Entrypoints map[string]string `json:"entrypoints,omitempty"`

	// ConfigSchema is a JSON Schema defining valid configuration.
	// Used to generate UI forms and validate config on save.
	ConfigSchema map[string]interface{} `json:"configSchema,omitempty"`

	// DefaultConfig provides default values for configuration.
	DefaultConfig map[string]interface{} `json:"defaultConfig,omitempty"`

	// Permissions lists required API permissions.
	// Examples: "sessions:read", "sessions:write", "analytics:write"
	Permissions []string `json:"permissions,omitempty"`

	// Dependencies lists other required plugins with version constraints.
	// Format: {"plugin-name": ">=1.0.0", "other-plugin": "^2.0.0"}
	Dependencies map[string]string `json:"dependencies,omitempty"`
}

PluginManifest represents the complete JSON structure of a plugin manifest.

This structure is read from manifest.json files in plugin repositories. It defines all metadata, configuration schema, and requirements for a plugin.

Example manifest.json:

{
  "name": "streamspace-analytics-advanced",
  "version": "1.2.0",
  "displayName": "Advanced Analytics",
  "description": "Comprehensive analytics and reporting",
  "author": "StreamSpace Team",
  "license": "MIT",
  "type": "api",
  "category": "Analytics",
  "configSchema": {
    "retentionDays": {"type": "number", "default": 90},
    "exportFormat": {"type": "string", "enum": ["json", "csv"]}
  },
  "permissions": ["sessions:read", "analytics:write"]
}

type PluginParser

type PluginParser struct{}

PluginParser parses plugin manifests from Git repositories.

The parser discovers and validates plugin manifest.json files. Unlike templates (YAML), plugins use JSON manifests with a different structure optimized for extension system metadata.

Manifest discovery:

  • Searches for files named "manifest.json"
  • Validates required fields (name, version, displayName, type)
  • Validates plugin type (extension, webhook, api, ui, theme)
  • Skips .git directories

Plugin types:

  • extension: General-purpose plugin (most common)
  • webhook: Responds to webhook events
  • api: Adds new API endpoints
  • ui: Adds UI components or pages
  • theme: Visual theme customization

Example usage:

parser := NewPluginParser()
plugins, err := parser.ParseRepository("/tmp/streamspace-plugins")
for _, p := range plugins {
    fmt.Printf("Found plugin: %s v%s\n", p.DisplayName, p.Version)
}

func NewPluginParser

func NewPluginParser() *PluginParser

NewPluginParser creates a new plugin parser instance.

The parser is stateless and can be reused for multiple repositories.

Example:

parser := NewPluginParser()
plugins1, _ := parser.ParseRepository("/tmp/official-plugins")
plugins2, _ := parser.ParseRepository("/tmp/community-plugins")

func (*PluginParser) ParsePluginFile

func (p *PluginParser) ParsePluginFile(filePath string) (*ParsedPlugin, error)

ParsePluginFile parses a single plugin manifest.json file.

Parsing steps:

  1. Read file from disk
  2. Unmarshal JSON into PluginManifest struct
  3. Validate required fields (name, version, displayName, type)
  4. Validate plugin type is one of: extension, webhook, api, ui, theme
  5. Convert manifest to JSON for database storage

Required fields:

  • name: Unique plugin identifier
  • version: Semantic version
  • displayName: Human-readable name
  • type: Plugin architecture type

Plugin type validation:

  • "extension": General-purpose extension (most common)
  • "webhook": Responds to webhook events
  • "api": Adds new API endpoints
  • "ui": Adds UI components or pages
  • "theme": Visual theme customization

Parameters:

  • filePath: Absolute path to manifest.json file

Returns:

  • ParsedPlugin with extracted metadata
  • Error if file cannot be read, parsed, or validated

Example:

plugin, err := parser.ParsePluginFile("/tmp/repo/analytics/manifest.json")
if err != nil {
    log.Printf("Invalid plugin: %v", err)
    return nil, err
}
fmt.Printf("Parsed: %s v%s\n", plugin.DisplayName, plugin.Version)

func (*PluginParser) ParseRepository

func (p *PluginParser) ParseRepository(repoPath string) ([]*ParsedPlugin, error)

ParseRepository parses all plugin manifests in a Git repository.

Discovery process:

  1. Walk all directories in repository
  2. Find files named "manifest.json"
  3. Parse JSON and validate structure
  4. Extract metadata and validate required fields
  5. Skip invalid files (continue processing others)

Behavior:

  • Skips .git directory (performance)
  • Only processes files named exactly "manifest.json"
  • Logs parse errors but continues (partial success)
  • Returns all successfully parsed plugins

Parameters:

  • repoPath: Local filesystem path to Git repository

Returns:

  • Array of parsed plugins (may be empty)
  • Error only if directory walk fails (not for individual parse errors)

Example:

parser := NewPluginParser()
plugins, err := parser.ParseRepository("/tmp/streamspace-plugins")
if err != nil {
    log.Fatal("Failed to walk repository:", err)
}
log.Printf("Found %d plugins", len(plugins))

func (*PluginParser) ValidatePluginManifest

func (p *PluginParser) ValidatePluginManifest(jsonContent string) error

ValidatePluginManifest validates a plugin manifest structure

type Repository

type Repository struct {
	ID         int
	Name       string
	URL        string
	Branch     string
	AuthConfig *AuthConfig
}

Repository represents a template repository

type SyncService

type SyncService struct {
	// contains filtered or unexported fields
}

SyncService manages template and plugin repository synchronization.

The service handles:

  • Git repository cloning and pulling
  • Manifest parsing (templates and plugins)
  • Catalog database updates
  • Scheduled background synchronization
  • Error handling and retry logic

Thread safety:

  • Safe for concurrent SyncRepository calls (different repos)
  • Uses database transactions to prevent conflicts
  • Git operations are isolated per repository directory

Example usage:

syncService, err := sync.NewSyncService(database)
if err != nil {
    log.Fatal(err)
}

// Sync specific repository
err = syncService.SyncRepository(ctx, repoID)

// Or sync all repositories
err = syncService.SyncAllRepositories(ctx)

// Start background sync (every 1 hour)
go syncService.StartScheduledSync(ctx, 1*time.Hour)

func NewSyncService

func NewSyncService(database *db.Database) (*SyncService, error)

NewSyncService creates a new sync service instance.

The service is initialized with:

  • Database connection for catalog updates
  • Work directory for cloning repositories
  • Git client for repository operations
  • Template and plugin parsers

Environment variables:

  • SYNC_WORK_DIR: Override default work directory (/tmp/streamspace-repos)

Returns an error if:

  • Work directory cannot be created
  • Permissions are insufficient

Example:

syncService, err := NewSyncService(database)
if err != nil {
    log.Fatalf("Failed to create sync service: %v", err)
}

func (*SyncService) StartScheduledSync

func (s *SyncService) StartScheduledSync(ctx context.Context, interval time.Duration)

StartScheduledSync starts the scheduled sync loop

func (*SyncService) SyncAllRepositories

func (s *SyncService) SyncAllRepositories(ctx context.Context) error

SyncAllRepositories synchronizes all enabled repositories.

This method:

  1. Queries all repositories from database
  2. Filters out repositories currently syncing
  3. Syncs each repository sequentially
  4. Logs success/failure counts

Behavior:

  • Skips repositories with status="syncing" (avoid concurrent syncs)
  • Continues on individual failures (doesn't abort entire sync)
  • Returns nil even if some repositories fail
  • Logs detailed results for each repository

Use cases:

  • Manual "Sync All" button in admin UI
  • Scheduled background sync (every hour)
  • Initial platform setup

Performance:

  • Sequential processing (one repo at a time)
  • Can be slow with many large repositories
  • Consider running in background goroutine

Example:

// Sync all repositories in background
go func() {
    err := syncService.SyncAllRepositories(context.Background())
    if err != nil {
        log.Printf("Sync all failed: %v", err)
    }
}()

func (*SyncService) SyncRepository

func (s *SyncService) SyncRepository(ctx context.Context, repoID int) error

SyncRepository synchronizes a single repository.

The sync process:

  1. Fetch repository details from database
  2. Update status to "syncing"
  3. Clone repository (if new) or pull latest changes
  4. Parse template manifests (YAML files)
  5. Parse plugin manifests (plugin.json files)
  6. Update catalog database with parsed resources
  7. Update repository status to "synced" or "failed"
  8. Record sync timestamp and resource counts

Git operations:

  • First sync: git clone <url> <work-dir>/repo-<id>
  • Subsequent syncs: git pull in <work-dir>/repo-<id>
  • Supports authentication (SSH keys, tokens)

Parsing:

  • Templates: Searches for *.yaml files with template metadata
  • Plugins: Searches for plugin.json manifest files
  • Invalid manifests are logged but don't fail the sync

Database updates:

  • Existing resources are updated (upsert)
  • Missing resources are removed (cleanup)
  • Transactions ensure consistency

Error handling:

  • Git errors: Mark repository as "failed", log details
  • Parse errors: Log warnings, continue with valid resources
  • Database errors: Roll back transaction, return error

Parameters:

  • ctx: Context for cancellation and timeouts
  • repoID: Database ID of the repository to sync

Returns an error if:

  • Repository not found in database
  • Git clone/pull fails
  • Catalog update fails

Example:

err := syncService.SyncRepository(ctx, 1)
if err != nil {
    log.Printf("Sync failed: %v", err)
}

type TemplateManifest

type TemplateManifest struct {
	APIVersion string `yaml:"apiVersion" json:"apiVersion"`
	Kind       string `yaml:"kind" json:"kind"`
	Metadata   struct {
		Name      string            `yaml:"name" json:"name"`
		Namespace string            `yaml:"namespace,omitempty" json:"namespace,omitempty"`
		Labels    map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"`
	} `yaml:"metadata" json:"metadata"`
	Spec struct {
		DisplayName      string            `yaml:"displayName" json:"displayName"`
		Description      string            `yaml:"description" json:"description"`
		Category         string            `yaml:"category" json:"category"`
		AppType          string            `yaml:"appType,omitempty" json:"appType,omitempty"`
		Icon             string            `yaml:"icon,omitempty" json:"icon,omitempty"`
		BaseImage        string            `yaml:"baseImage" json:"baseImage"`
		DefaultResources map[string]string `yaml:"defaultResources,omitempty" json:"defaultResources,omitempty"`
		Ports            []struct {
			Name          string `yaml:"name" json:"name"`
			ContainerPort int    `yaml:"containerPort" json:"containerPort"`
			Protocol      string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
		} `yaml:"ports,omitempty" json:"ports,omitempty"`
		Env          []map[string]interface{} `yaml:"env,omitempty" json:"env,omitempty"`
		VolumeMounts []map[string]interface{} `yaml:"volumeMounts,omitempty" json:"volumeMounts,omitempty"`
		VNC          *struct {
			Enabled    bool   `yaml:"enabled" json:"enabled"`
			Port       int    `yaml:"port" json:"port"`
			Protocol   string `yaml:"protocol,omitempty" json:"protocol,omitempty"`
			Encryption bool   `yaml:"encryption,omitempty" json:"encryption,omitempty"`
		} `yaml:"vnc,omitempty" json:"vnc,omitempty"`
		WebApp *struct {
			Enabled     bool   `yaml:"enabled" json:"enabled"`
			Port        int    `yaml:"port" json:"port"`
			Path        string `yaml:"path,omitempty" json:"path,omitempty"`
			HealthCheck string `yaml:"healthCheck,omitempty" json:"healthCheck,omitempty"`
		} `yaml:"webapp,omitempty" json:"webapp,omitempty"`
		Capabilities []string `yaml:"capabilities,omitempty" json:"capabilities,omitempty"`
		Tags         []string `yaml:"tags,omitempty" json:"tags,omitempty"`
	} `yaml:"spec" json:"spec"`
}

TemplateManifest represents the complete YAML structure of a Template resource.

This structure mirrors the Kubernetes Template CRD defined in:

controller/api/v1alpha1/template_types.go

The manifest is parsed from YAML files in repositories and validated before being stored in the catalog database as JSON.

Example YAML:

apiVersion: stream.space/v1alpha1
kind: Template
metadata:
  name: chrome-selkies
spec:
  displayName: Google Chrome
  description: Chrome browser streamed via Selkies-GStreamer
  category: Web Browsers
  baseImage: ghcr.io/streamspace-dev/chrome-selkies:latest
  streamingProtocol: selkies
  ports:
    - name: selkies
      containerPort: 8080
  tags: [browser, web, selkies]

type TemplateParser

type TemplateParser struct{}

TemplateParser parses Kubernetes Template manifests from Git repositories.

The parser discovers and validates Template resources in YAML format. It walks repository directories, identifies Template manifests, and extracts metadata for catalog indexing.

Manifest discovery:

  • Searches for *.yaml and *.yml files
  • Validates kind: Template and apiVersion
  • Skips non-Template YAML files (no errors)
  • Skips .git directories

Validation:

  • Required fields: name, displayName, baseImage
  • API version: stream.space/v1alpha1 (or stream.streamspace.io/v1alpha1 for backward compatibility)
  • App type inference: desktop (VNC) or webapp (HTTP)

Example usage:

parser := NewTemplateParser()
templates, err := parser.ParseRepository("/tmp/streamspace-templates")
for _, t := range templates {
    fmt.Printf("Found template: %s (%s)\n", t.DisplayName, t.Category)
}

func NewTemplateParser

func NewTemplateParser() *TemplateParser

NewTemplateParser creates a new template parser instance.

The parser is stateless and can be reused for multiple repositories.

Example:

parser := NewTemplateParser()
templates1, _ := parser.ParseRepository("/tmp/repo1")
templates2, _ := parser.ParseRepository("/tmp/repo2")

func (*TemplateParser) ParseRepository

func (p *TemplateParser) ParseRepository(repoPath string) ([]*ParsedTemplate, error)

ParseRepository parses all Template manifests in a Git repository.

Discovery process:

  1. Walk all directories in repository
  2. Find files with .yaml or .yml extension
  3. Parse YAML and check if kind: Template
  4. Extract metadata and validate
  5. Skip invalid files (continue processing others)

Behavior:

  • Skips .git directory (performance)
  • Skips non-Template YAML files (no error)
  • Logs parse errors but continues (partial success)
  • Returns all successfully parsed templates

Parameters:

  • repoPath: Local filesystem path to Git repository

Returns:

  • Array of parsed templates (may be empty)
  • Error only if directory walk fails (not for individual parse errors)

Example:

parser := NewTemplateParser()
templates, err := parser.ParseRepository("/tmp/streamspace-templates")
if err != nil {
    log.Fatal("Failed to walk repository:", err)
}
log.Printf("Found %d templates", len(templates))

func (*TemplateParser) ParseTemplateFile

func (p *TemplateParser) ParseTemplateFile(filePath string) (*ParsedTemplate, error)

ParseTemplateFile parses a single Template YAML file.

Parsing steps:

  1. Read file from disk
  2. Unmarshal YAML into TemplateManifest struct
  3. Validate kind == "Template"
  4. Validate apiVersion == "stream.streamspace.io/v1alpha1"
  5. Validate required fields (name, displayName, baseImage)
  6. Infer appType from VNC/WebApp config if not specified
  7. Convert manifest to JSON for database storage

App type inference:

  • If spec.webapp.enabled: appType = "webapp"
  • Otherwise: appType = "desktop" (VNC-based)

Parameters:

  • filePath: Absolute path to YAML file

Returns:

  • ParsedTemplate with extracted metadata
  • Error if file cannot be read, parsed, or validated

Example:

template, err := parser.ParseTemplateFile("/tmp/repo/browsers/firefox.yaml")
if err != nil {
    log.Printf("Invalid template: %v", err)
    return nil, err
}
fmt.Printf("Parsed: %s\n", template.DisplayName)

func (*TemplateParser) ParseTemplateFromString

func (p *TemplateParser) ParseTemplateFromString(yamlContent string) (*ParsedTemplate, error)

ParseTemplateFromString parses a template from a YAML string

func (*TemplateParser) ValidateTemplateManifest

func (p *TemplateParser) ValidateTemplateManifest(yamlContent string) error

ValidateTemplateManifest validates a template manifest structure

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL