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:
- Administrator adds repository via API
- Sync service clones repository to work directory
- Parsers discover manifests in repository
- Catalog database is updated with new/updated resources
- Users can browse and install from catalog
- Periodic sync keeps catalog up-to-date
Example repositories:
- https://github.com/JoshuaAFerguson/streamspace-templates (official templates)
- https://github.com/JoshuaAFerguson/streamspace-plugins (official plugins)
Configuration:
- SYNC_WORK_DIR: Directory for cloned repositories (default: /tmp/streamspace-repos)
- SYNC_INTERVAL: Time between automatic syncs (default: 1h)
Index ¶
- type AuthConfig
- type GitClient
- func (g *GitClient) Clone(ctx context.Context, url, path, branch string, auth *AuthConfig) error
- func (g *GitClient) GetCommitHash(ctx context.Context, path string) (string, error)
- func (g *GitClient) Pull(ctx context.Context, path, branch string, auth *AuthConfig) error
- func (g *GitClient) Validate() error
- type ParsedPlugin
- type ParsedTemplate
- type PluginManifest
- type PluginParser
- type Repository
- type SyncService
- type TemplateManifest
- type TemplateParser
- func (p *TemplateParser) ParseRepository(repoPath string) ([]*ParsedTemplate, error)
- func (p *TemplateParser) ParseTemplateFile(filePath string) (*ParsedTemplate, error)
- func (p *TemplateParser) ParseTemplateFromString(yamlContent string) (*ParsedTemplate, error)
- func (p *TemplateParser) ValidateTemplateManifest(yamlContent string) error
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 ¶
Clone clones a Git repository to a local path.
The clone operation:
- Removes existing directory if present (fresh clone)
- Performs shallow clone with --depth 1 (faster, smaller)
- Checks out specified branch (or default branch)
- 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 ¶
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 ¶
Pull pulls the latest changes from a Git repository.
The pull operation:
- Fetches latest changes from origin
- Hard resets to origin/branch (discards local changes)
- 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 ¶
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:
- Execute "git --version" command
- Verify command succeeds (exit code 0)
- 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:
- Read file from disk
- Unmarshal JSON into PluginManifest struct
- Validate required fields (name, version, displayName, type)
- Validate plugin type is one of: extension, webhook, api, ui, theme
- 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:
- Walk all directories in repository
- Find files named "manifest.json"
- Parse JSON and validate structure
- Extract metadata and validate required fields
- 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:
- Queries all repositories from database
- Filters out repositories currently syncing
- Syncs each repository sequentially
- 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:
- Fetch repository details from database
- Update status to "syncing"
- Clone repository (if new) or pull latest changes
- Parse template manifests (YAML files)
- Parse plugin manifests (plugin.json files)
- Update catalog database with parsed resources
- Update repository status to "synced" or "failed"
- 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: firefox-browser
spec:
displayName: Firefox Web Browser
description: Modern, privacy-focused web browser
category: Web Browsers
baseImage: lscr.io/linuxserver/firefox:latest
vnc:
enabled: true
port: 3000
tags: [browser, web, privacy]
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:
- Walk all directories in repository
- Find files with .yaml or .yml extension
- Parse YAML and check if kind: Template
- Extract metadata and validate
- 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:
- Read file from disk
- Unmarshal YAML into TemplateManifest struct
- Validate kind == "Template"
- Validate apiVersion == "stream.streamspace.io/v1alpha1"
- Validate required fields (name, displayName, baseImage)
- Infer appType from VNC/WebApp config if not specified
- 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