chore(io): migrate pkg/cli and pkg/container to io.Local abstraction
Continue io.Medium migration for the remaining packages: - pkg/cli/daemon.go: PIDFile Acquire/Release now use io.Local.Read, Delete, and Write for managing daemon PID files. - pkg/container/state.go: LoadState and SaveState use io.Local for JSON state persistence. EnsureLogsDir uses io.Local.EnsureDir. - pkg/container/templates.go: Template loading and directory scanning now use io.Local.IsFile, IsDir, Read, and List. - pkg/container/linuxkit.go: Image validation uses io.Local.IsFile, log file check uses io.Local.IsFile. Streaming log file creation (os.Create) remains unchanged as io.Local doesn't support streaming. Closes #105, closes #107 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
40451538d6
commit
1adebbdba1
4 changed files with 56 additions and 45 deletions
|
|
@ -13,6 +13,7 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/host-uk/core/pkg/io"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
|
|
@ -88,9 +89,14 @@ func (p *PIDFile) Acquire() error {
|
|||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
absPath, err := filepath.Abs(p.path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to resolve PID file path: %w", err)
|
||||
}
|
||||
|
||||
// Check if PID file exists
|
||||
if data, err := os.ReadFile(p.path); err == nil {
|
||||
pid, err := strconv.Atoi(string(data))
|
||||
if content, err := io.Local.Read(absPath); err == nil {
|
||||
pid, err := strconv.Atoi(content)
|
||||
if err == nil && pid > 0 {
|
||||
// Check if process is still running
|
||||
if process, err := os.FindProcess(pid); err == nil {
|
||||
|
|
@ -100,19 +106,12 @@ func (p *PIDFile) Acquire() error {
|
|||
}
|
||||
}
|
||||
// Stale PID file, remove it
|
||||
_ = os.Remove(p.path)
|
||||
_ = io.Local.Delete(absPath)
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if dir := filepath.Dir(p.path); dir != "." {
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
return fmt.Errorf("failed to create PID directory: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Write current PID
|
||||
// Write current PID (io.Local.Write creates parent directories automatically)
|
||||
pid := os.Getpid()
|
||||
if err := os.WriteFile(p.path, []byte(strconv.Itoa(pid)), 0644); err != nil {
|
||||
if err := io.Local.Write(absPath, strconv.Itoa(pid)); err != nil {
|
||||
return fmt.Errorf("failed to write PID file: %w", err)
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +122,11 @@ func (p *PIDFile) Acquire() error {
|
|||
func (p *PIDFile) Release() error {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return os.Remove(p.path)
|
||||
absPath, err := filepath.Abs(p.path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return io.Local.Delete(absPath)
|
||||
}
|
||||
|
||||
// Path returns the PID file path.
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@ import (
|
|||
"bufio"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
goio "io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/host-uk/core/pkg/io"
|
||||
)
|
||||
|
||||
// LinuxKitManager implements the Manager interface for LinuxKit VMs.
|
||||
|
|
@ -51,7 +53,7 @@ func NewLinuxKitManagerWithHypervisor(state *State, hypervisor Hypervisor) *Linu
|
|||
// Run starts a new LinuxKit VM from the given image.
|
||||
func (m *LinuxKitManager) Run(ctx context.Context, image string, opts RunOptions) (*Container, error) {
|
||||
// Validate image exists
|
||||
if _, err := os.Stat(image); err != nil {
|
||||
if !io.Local.IsFile(image) {
|
||||
return nil, fmt.Errorf("image not found: %s", image)
|
||||
}
|
||||
|
||||
|
|
@ -190,12 +192,12 @@ func (m *LinuxKitManager) Run(ctx context.Context, image string, opts RunOptions
|
|||
|
||||
// Copy output to both log and stdout
|
||||
go func() {
|
||||
mw := io.MultiWriter(logFile, os.Stdout)
|
||||
_, _ = io.Copy(mw, stdout)
|
||||
mw := goio.MultiWriter(logFile, os.Stdout)
|
||||
_, _ = goio.Copy(mw, stdout)
|
||||
}()
|
||||
go func() {
|
||||
mw := io.MultiWriter(logFile, os.Stderr)
|
||||
_, _ = io.Copy(mw, stderr)
|
||||
mw := goio.MultiWriter(logFile, os.Stderr)
|
||||
_, _ = goio.Copy(mw, stderr)
|
||||
}()
|
||||
|
||||
// Wait for the process to complete
|
||||
|
|
@ -310,7 +312,7 @@ func isProcessRunning(pid int) bool {
|
|||
}
|
||||
|
||||
// Logs returns a reader for the container's log output.
|
||||
func (m *LinuxKitManager) Logs(ctx context.Context, id string, follow bool) (io.ReadCloser, error) {
|
||||
func (m *LinuxKitManager) Logs(ctx context.Context, id string, follow bool) (goio.ReadCloser, error) {
|
||||
_, ok := m.state.Get(id)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("container not found: %s", id)
|
||||
|
|
@ -321,11 +323,8 @@ func (m *LinuxKitManager) Logs(ctx context.Context, id string, follow bool) (io.
|
|||
return nil, fmt.Errorf("failed to determine log path: %w", err)
|
||||
}
|
||||
|
||||
if _, err := os.Stat(logPath); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, fmt.Errorf("no logs available for container: %s", id)
|
||||
}
|
||||
return nil, err
|
||||
if !io.Local.IsFile(logPath) {
|
||||
return nil, fmt.Errorf("no logs available for container: %s", id)
|
||||
}
|
||||
|
||||
if !follow {
|
||||
|
|
@ -337,7 +336,7 @@ func (m *LinuxKitManager) Logs(ctx context.Context, id string, follow bool) (io.
|
|||
return newFollowReader(ctx, logPath)
|
||||
}
|
||||
|
||||
// followReader implements io.ReadCloser for following log files.
|
||||
// followReader implements goio.ReadCloser for following log files.
|
||||
type followReader struct {
|
||||
file *os.File
|
||||
ctx context.Context
|
||||
|
|
@ -352,7 +351,7 @@ func newFollowReader(ctx context.Context, path string) (*followReader, error) {
|
|||
}
|
||||
|
||||
// Seek to end
|
||||
_, _ = file.Seek(0, io.SeekEnd)
|
||||
_, _ = file.Seek(0, goio.SeekEnd)
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
|
||||
|
|
@ -368,7 +367,7 @@ func (f *followReader) Read(p []byte) (int, error) {
|
|||
for {
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
return 0, io.EOF
|
||||
return 0, goio.EOF
|
||||
default:
|
||||
}
|
||||
|
||||
|
|
@ -376,14 +375,14 @@ func (f *followReader) Read(p []byte) (int, error) {
|
|||
if n > 0 {
|
||||
return n, nil
|
||||
}
|
||||
if err != nil && err != io.EOF {
|
||||
if err != nil && err != goio.EOF {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// No data available, wait a bit and try again
|
||||
select {
|
||||
case <-f.ctx.Done():
|
||||
return 0, io.EOF
|
||||
return 0, goio.EOF
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
// Reset reader to pick up new data
|
||||
f.reader.Reset(f.file)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/host-uk/core/pkg/io"
|
||||
)
|
||||
|
||||
// State manages persistent container state.
|
||||
|
|
@ -56,7 +58,12 @@ func NewState(filePath string) *State {
|
|||
func LoadState(filePath string) (*State, error) {
|
||||
state := NewState(filePath)
|
||||
|
||||
data, err := os.ReadFile(filePath)
|
||||
absPath, err := filepath.Abs(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
content, err := io.Local.Read(absPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return state, nil
|
||||
|
|
@ -64,7 +71,7 @@ func LoadState(filePath string) (*State, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(data, state); err != nil {
|
||||
if err := json.Unmarshal([]byte(content), state); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -76,9 +83,8 @@ func (s *State) SaveState() error {
|
|||
s.mu.RLock()
|
||||
defer s.mu.RUnlock()
|
||||
|
||||
// Ensure the directory exists
|
||||
dir := filepath.Dir(s.filePath)
|
||||
if err := os.MkdirAll(dir, 0755); err != nil {
|
||||
absPath, err := filepath.Abs(s.filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -87,7 +93,8 @@ func (s *State) SaveState() error {
|
|||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(s.filePath, data, 0644)
|
||||
// io.Local.Write creates parent directories automatically
|
||||
return io.Local.Write(absPath, string(data))
|
||||
}
|
||||
|
||||
// Add adds a container to the state and persists it.
|
||||
|
|
@ -166,5 +173,5 @@ func EnsureLogsDir() error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return os.MkdirAll(logsDir, 0755)
|
||||
return io.Local.EnsureDir(logsDir)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import (
|
|||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/host-uk/core/pkg/io"
|
||||
)
|
||||
|
||||
//go:embed templates/*.yml
|
||||
|
|
@ -71,12 +73,12 @@ func GetTemplate(name string) (string, error) {
|
|||
userTemplatesDir := getUserTemplatesDir()
|
||||
if userTemplatesDir != "" {
|
||||
templatePath := filepath.Join(userTemplatesDir, name+".yml")
|
||||
if _, err := os.Stat(templatePath); err == nil {
|
||||
content, err := os.ReadFile(templatePath)
|
||||
if io.Local.IsFile(templatePath) {
|
||||
content, err := io.Local.Read(templatePath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read user template %s: %w", name, err)
|
||||
}
|
||||
return string(content), nil
|
||||
return content, nil
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +196,7 @@ func getUserTemplatesDir() string {
|
|||
cwd, err := os.Getwd()
|
||||
if err == nil {
|
||||
wsDir := filepath.Join(cwd, ".core", "linuxkit")
|
||||
if info, err := os.Stat(wsDir); err == nil && info.IsDir() {
|
||||
if io.Local.IsDir(wsDir) {
|
||||
return wsDir
|
||||
}
|
||||
}
|
||||
|
|
@ -206,7 +208,7 @@ func getUserTemplatesDir() string {
|
|||
}
|
||||
|
||||
homeDir := filepath.Join(home, ".core", "linuxkit")
|
||||
if info, err := os.Stat(homeDir); err == nil && info.IsDir() {
|
||||
if io.Local.IsDir(homeDir) {
|
||||
return homeDir
|
||||
}
|
||||
|
||||
|
|
@ -217,7 +219,7 @@ func getUserTemplatesDir() string {
|
|||
func scanUserTemplates(dir string) []Template {
|
||||
var templates []Template
|
||||
|
||||
entries, err := os.ReadDir(dir)
|
||||
entries, err := io.Local.List(dir)
|
||||
if err != nil {
|
||||
return templates
|
||||
}
|
||||
|
|
@ -266,12 +268,12 @@ func scanUserTemplates(dir string) []Template {
|
|||
// extractTemplateDescription reads the first comment block from a YAML file
|
||||
// to use as a description.
|
||||
func extractTemplateDescription(path string) string {
|
||||
content, err := os.ReadFile(path)
|
||||
content, err := io.Local.Read(path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
lines := strings.Split(string(content), "\n")
|
||||
lines := strings.Split(content, "\n")
|
||||
var descLines []string
|
||||
|
||||
for _, line := range lines {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue