Merge branch 'fix/io-migration-container' into new

# Conflicts:
#	pkg/container/state.go
#	pkg/container/templates.go
This commit is contained in:
Snider 2026-02-08 21:28:36 +00:00
commit 15d5aa0fbd
2 changed files with 45 additions and 83 deletions

View file

@ -15,7 +15,6 @@ type State struct {
Containers map[string]*Container `json:"containers"` Containers map[string]*Container `json:"containers"`
mu sync.RWMutex mu sync.RWMutex
medium io.Medium
filePath string filePath string
} }
@ -47,25 +46,19 @@ func DefaultLogsDir() (string, error) {
} }
// NewState creates a new State instance. // NewState creates a new State instance.
func NewState(m io.Medium, filePath string) *State { func NewState(filePath string) *State {
return &State{ return &State{
Containers: make(map[string]*Container), Containers: make(map[string]*Container),
medium: m,
filePath: filePath, filePath: filePath,
} }
} }
// LoadState loads the state from the given file path. // LoadState loads the state from the given file path.
// If the file doesn't exist, returns an empty state. // If the file doesn't exist, returns an empty state.
func LoadState(m io.Medium, filePath string) (*State, error) { func LoadState(filePath string) (*State, error) {
state := NewState(m, filePath) state := NewState(filePath)
absPath, err := filepath.Abs(filePath) dataStr, err := io.Local.Read(filePath)
if err != nil {
return nil, err
}
content, err := m.Read(absPath)
if err != nil { if err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return state, nil return state, nil
@ -73,7 +66,7 @@ func LoadState(m io.Medium, filePath string) (*State, error) {
return nil, err return nil, err
} }
if err := json.Unmarshal([]byte(content), state); err != nil { if err := json.Unmarshal([]byte(dataStr), state); err != nil {
return nil, err return nil, err
} }
@ -85,8 +78,9 @@ func (s *State) SaveState() error {
s.mu.RLock() s.mu.RLock()
defer s.mu.RUnlock() defer s.mu.RUnlock()
absPath, err := filepath.Abs(s.filePath) // Ensure the directory exists
if err != nil { dir := filepath.Dir(s.filePath)
if err := io.Local.EnsureDir(dir); err != nil {
return err return err
} }
@ -95,8 +89,7 @@ func (s *State) SaveState() error {
return err return err
} }
// s.medium.Write creates parent directories automatically return io.Local.Write(s.filePath, string(data))
return s.medium.Write(absPath, string(data))
} }
// Add adds a container to the state and persists it. // Add adds a container to the state and persists it.
@ -170,10 +163,10 @@ func LogPath(id string) (string, error) {
} }
// EnsureLogsDir ensures the logs directory exists. // EnsureLogsDir ensures the logs directory exists.
func EnsureLogsDir(m io.Medium) error { func EnsureLogsDir() error {
logsDir, err := DefaultLogsDir() logsDir, err := DefaultLogsDir()
if err != nil { if err != nil {
return err return err
} }
return m.EnsureDir(logsDir) return io.Local.EnsureDir(logsDir)
} }

View file

@ -38,52 +38,17 @@ var builtinTemplates = []Template{
}, },
} }
// TemplateManager manages LinuxKit templates using a storage medium.
type TemplateManager struct {
medium io.Medium
workingDir string
homeDir string
}
// NewTemplateManager creates a new TemplateManager instance.
func NewTemplateManager(m io.Medium) *TemplateManager {
tm := &TemplateManager{medium: m}
// Default working and home directories from local system
// These can be overridden if needed.
if wd, err := os.Getwd(); err == nil {
tm.workingDir = wd
}
if home, err := os.UserHomeDir(); err == nil {
tm.homeDir = home
}
return tm
}
// WithWorkingDir sets the working directory for user template discovery.
func (tm *TemplateManager) WithWorkingDir(wd string) *TemplateManager {
tm.workingDir = wd
return tm
}
// WithHomeDir sets the home directory for user template discovery.
func (tm *TemplateManager) WithHomeDir(home string) *TemplateManager {
tm.homeDir = home
return tm
}
// ListTemplates returns all available LinuxKit templates. // ListTemplates returns all available LinuxKit templates.
// It combines embedded templates with any templates found in the user's // It combines embedded templates with any templates found in the user's
// .core/linuxkit directory. // .core/linuxkit directory.
func (tm *TemplateManager) ListTemplates() []Template { func ListTemplates() []Template {
templates := make([]Template, len(builtinTemplates)) templates := make([]Template, len(builtinTemplates))
copy(templates, builtinTemplates) copy(templates, builtinTemplates)
// Check for user templates in .core/linuxkit/ // Check for user templates in .core/linuxkit/
userTemplatesDir := tm.getUserTemplatesDir() userTemplatesDir := getUserTemplatesDir()
if userTemplatesDir != "" { if userTemplatesDir != "" {
userTemplates := tm.scanUserTemplates(userTemplatesDir) userTemplates := scanUserTemplates(userTemplatesDir)
templates = append(templates, userTemplates...) templates = append(templates, userTemplates...)
} }
@ -92,7 +57,7 @@ func (tm *TemplateManager) ListTemplates() []Template {
// GetTemplate returns the content of a template by name. // GetTemplate returns the content of a template by name.
// It first checks embedded templates, then user templates. // It first checks embedded templates, then user templates.
func (tm *TemplateManager) GetTemplate(name string) (string, error) { func GetTemplate(name string) (string, error) {
// Check embedded templates first // Check embedded templates first
for _, t := range builtinTemplates { for _, t := range builtinTemplates {
if t.Name == name { if t.Name == name {
@ -105,27 +70,27 @@ func (tm *TemplateManager) GetTemplate(name string) (string, error) {
} }
// Check user templates // Check user templates
userTemplatesDir := tm.getUserTemplatesDir() userTemplatesDir := getUserTemplatesDir()
if userTemplatesDir != "" { if userTemplatesDir != "" {
// Check both .yml and .yaml extensions templatePath := filepath.Join(userTemplatesDir, name+".yml")
for _, ext := range []string{".yml", ".yaml"} { if io.Local.IsFile(templatePath) {
templatePath := filepath.Join(userTemplatesDir, name+ext) content, err := io.Local.Read(templatePath)
if tm.medium.IsFile(templatePath) {
content, err := tm.medium.Read(templatePath)
if err != nil { if err != nil {
return "", fmt.Errorf("failed to read user template %s: %w", name, err) return "", fmt.Errorf("failed to read user template %s: %w", name, err)
} }
return content, nil return content, nil
} }
} }
}
return "", fmt.Errorf("template not found: %s", name) return "", fmt.Errorf("template not found: %s", name)
} }
// ApplyTemplate applies variable substitution to a template. // ApplyTemplate applies variable substitution to a template.
func (tm *TemplateManager) ApplyTemplate(name string, vars map[string]string) (string, error) { // It supports two syntaxes:
content, err := tm.GetTemplate(name) // - ${VAR} - required variable, returns error if not provided
// - ${VAR:-default} - variable with default value
func ApplyTemplate(name string, vars map[string]string) (string, error) {
content, err := GetTemplate(name)
if err != nil { if err != nil {
return "", err return "", err
} }
@ -226,31 +191,35 @@ func ExtractVariables(content string) (required []string, optional map[string]st
// getUserTemplatesDir returns the path to user templates directory. // getUserTemplatesDir returns the path to user templates directory.
// Returns empty string if the directory doesn't exist. // Returns empty string if the directory doesn't exist.
func (tm *TemplateManager) getUserTemplatesDir() string { func getUserTemplatesDir() string {
// Try workspace-relative .core/linuxkit first // Try workspace-relative .core/linuxkit first
if tm.workingDir != "" { cwd, err := os.Getwd()
wsDir := filepath.Join(tm.workingDir, ".core", "linuxkit") if err == nil {
if tm.medium.IsDir(wsDir) { wsDir := filepath.Join(cwd, ".core", "linuxkit")
if io.Local.IsDir(wsDir) {
return wsDir return wsDir
} }
} }
// Try home directory // Try home directory
if tm.homeDir != "" { home, err := os.UserHomeDir()
homeDir := filepath.Join(tm.homeDir, ".core", "linuxkit") if err != nil {
if tm.medium.IsDir(homeDir) { return ""
return homeDir
} }
homeDir := filepath.Join(home, ".core", "linuxkit")
if io.Local.IsDir(homeDir) {
return homeDir
} }
return "" return ""
} }
// scanUserTemplates scans a directory for .yml template files. // scanUserTemplates scans a directory for .yml template files.
func (tm *TemplateManager) scanUserTemplates(dir string) []Template { func scanUserTemplates(dir string) []Template {
var templates []Template var templates []Template
entries, err := tm.medium.List(dir) entries, err := io.Local.List(dir)
if err != nil { if err != nil {
return templates return templates
} }
@ -281,7 +250,7 @@ func (tm *TemplateManager) scanUserTemplates(dir string) []Template {
} }
// Read file to extract description from comments // Read file to extract description from comments
description := tm.extractTemplateDescription(filepath.Join(dir, name)) description := extractTemplateDescription(filepath.Join(dir, name))
if description == "" { if description == "" {
description = "User-defined template" description = "User-defined template"
} }
@ -298,8 +267,8 @@ func (tm *TemplateManager) scanUserTemplates(dir string) []Template {
// extractTemplateDescription reads the first comment block from a YAML file // extractTemplateDescription reads the first comment block from a YAML file
// to use as a description. // to use as a description.
func (tm *TemplateManager) extractTemplateDescription(path string) string { func extractTemplateDescription(path string) string {
content, err := tm.medium.Read(path) content, err := io.Local.Read(path)
if err != nil { if err != nil {
return "" return ""
} }